Repository: apache/camel-k
Pull request #3506 (2022-08-03 10:08:55)

Fix the file path in run command


Patch:
diff --git a/.gitignore b/.gitignore
index 75e386502..1702b92a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
 
 # Binary files
 /kamel
+/kamel.*
 /license-check
 /**/platform-check
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 22c5a5347..f28fe2c25 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,11 +6,15 @@
 
 **Closed issues:**
 
+- Passing namespace info for CXF Endpoint in Camel-K YAML DSL [\#3503](https://github.com/apache/camel-k/issues/3503)
 - Trait owner.target-annotations is not propagated to K8S Services [\#3494](https://github.com/apache/camel-k/issues/3494)
+- Release nightly fails [\#3492](https://github.com/apache/camel-k/issues/3492)
 - Kamel install error on Kind [\#3483](https://github.com/apache/camel-k/issues/3483)
+- camel-k ignores changes to traits configured using annotations [\#3479](https://github.com/apache/camel-k/issues/3479)
 - Integration cannot be run from github with different branch [\#3475](https://github.com/apache/camel-k/issues/3475)
 - `make images` is not building the binary [\#3474](https://github.com/apache/camel-k/issues/3474)
 - `install-it` workflow is failing silently [\#3465](https://github.com/apache/camel-k/issues/3465)
+- Declaring multiple traits in KameletBinding fails badly [\#3458](https://github.com/apache/camel-k/issues/3458)
 - `camel-k-operator` pod in `CrashLoopBackOff` [\#3456](https://github.com/apache/camel-k/issues/3456)
 - Option `--maven-repository` on kamel run command has no effect [\#3447](https://github.com/apache/camel-k/issues/3447)
 - `make generate-doc` outputs non-deterministic `resources/traits.yaml` [\#3431](https://github.com/apache/camel-k/issues/3431)
@@ -54,13 +58,17 @@
 - Route trait usage example [\#3146](https://github.com/apache/camel-k/issues/3146)
 - PDB trait usage example [\#3145](https://github.com/apache/camel-k/issues/3145)
 - Logging trait usage example [\#3143](https://github.com/apache/camel-k/issues/3143)
+- Knative trait usage example [\#3142](https://github.com/apache/camel-k/issues/3142)
 - Camel K operator scaling documentation [\#3133](https://github.com/apache/camel-k/issues/3133)
 - Support operator hub bundle format in `make bundle` [\#3128](https://github.com/apache/camel-k/issues/3128)
 - hello yaml example output [\#3124](https://github.com/apache/camel-k/issues/3124)
 - java.lang.ClassNotFoundException: io.quarkus.bootstrap.runner.QuarkusEntryPoint after k8s node update [\#3122](https://github.com/apache/camel-k/issues/3122)
 - E2E failing in release-1.8.x [\#3120](https://github.com/apache/camel-k/issues/3120)
 - cyrus-sasl-lib \(CVE-2022-24407\), CVE-2022-23806 and multiple go related vulnerabilities in Docker Image 1.7.0, 1.8.1, 1.8.2 [\#3113](https://github.com/apache/camel-k/issues/3113)
+- More user-friendly error messages for `kamel local` subcommands [\#3028](https://github.com/apache/camel-k/issues/3028)
 - OLM scorecard tests missing [\#3022](https://github.com/apache/camel-k/issues/3022)
+- cron.concurrency-policy does not work correctly [\#3019](https://github.com/apache/camel-k/issues/3019)
+- kamel uninstall seems to remain some resources in the cluster [\#3013](https://github.com/apache/camel-k/issues/3013)
 - Access channel in different namespace [\#3009](https://github.com/apache/camel-k/issues/3009)
 - Operator creates too many repositories [\#3003](https://github.com/apache/camel-k/issues/3003)
 - Improve message error when IntegrationPlatform failure [\#2996](https://github.com/apache/camel-k/issues/2996)
@@ -72,11 +80,13 @@
 - Using REST as consumer and producer appends PATH to producer URL [\#2867](https://github.com/apache/camel-k/issues/2867)
 - Use deprecated field of yaml schema specification instead of only modifying description [\#2817](https://github.com/apache/camel-k/issues/2817)
 - Adding mavens settings-security.xml to the spec.build.maven is not possible [\#2747](https://github.com/apache/camel-k/issues/2747)
+- Integration won't rollout with changes outside spec [\#2648](https://github.com/apache/camel-k/issues/2648)
 - Kamel 1.5.1 CLI generates broken zsh completions [\#2622](https://github.com/apache/camel-k/issues/2622)
 - ImagePullBackOff when node failed [\#2485](https://github.com/apache/camel-k/issues/2485)
 - Debug seems not to work on knative pods [\#2474](https://github.com/apache/camel-k/issues/2474)
 - Groovy script JSON paring runs into java.util.ServiceConfigurationError: org.apache.groovy.json.FastStringServiceFactory: org.apache.groovy.json.DefaultFastStringServiceFactory not a subtype [\#2418](https://github.com/apache/camel-k/issues/2418)
 - Migrate CronJob to `batch/v1` [\#2408](https://github.com/apache/camel-k/issues/2408)
+- re-deployment strategies [\#2256](https://github.com/apache/camel-k/issues/2256)
 - `kamel local build` doesn't support same dependency notation [\#2213](https://github.com/apache/camel-k/issues/2213)
 - Provide option to have symbolic link for kamel local build --integration-directory \<folder\> [\#2179](https://github.com/apache/camel-k/issues/2179)
 - Global vs local operator  [\#2177](https://github.com/apache/camel-k/issues/2177)
@@ -1353,7 +1363,6 @@
 - Kafka example not building on OpenShit v4.? - latest crc version on Mac [\#918](https://github.com/apache/camel-k/issues/918)
 - Documenting expected behavior with certain potential collisions [\#870](https://github.com/apache/camel-k/issues/870)
 - no matches for kind "BuildConfig" in version "build.openshift.io/v1" [\#739](https://github.com/apache/camel-k/issues/739)
-- Tekton build strategy [\#682](https://github.com/apache/camel-k/issues/682)
 - Add camel-k-runtime to the root readme [\#630](https://github.com/apache/camel-k/issues/630)
 - support versions like 2.21.0.tag-730075-anothertag-00002 in version matching [\#617](https://github.com/apache/camel-k/issues/617)
 - Camel K with Java routes without public class and imports ... [\#605](https://github.com/apache/camel-k/issues/605)
diff --git a/config/crd/bases/camel.apache.org_integrationplatforms.yaml b/config/crd/bases/camel.apache.org_integrationplatforms.yaml
index ffd239464..cda07f954 100644
--- a/config/crd/bases/camel.apache.org_integrationplatforms.yaml
+++ b/config/crd/bases/camel.apache.org_integrationplatforms.yaml
@@ -734,6 +734,28 @@ spec:
                           defaults to 60s.
                         format: int32
                         type: integer
+                      rollingUpdateMaxSurge:
+                        description: 'The maximum number of pods that can be scheduled
+                          above the desired number of pods. Value can be an absolute
+                          number (ex: 5) or a percentage of desired pods (ex: 10%).
+                          This can not be 0 if MaxUnavailable is 0. Absolute number
+                          is calculated from percentage by rounding up. Defaults to
+                          25%.'
+                        type: integer
+                      rollingUpdateMaxUnavailable:
+                        description: 'The maximum number of pods that can be unavailable
+                          during the update. Value can be an absolute number (ex:
+                          5) or a percentage of desired pods (ex: 10%). Absolute number
+                          is calculated from percentage by rounding down. This can
+                          not be 0 if MaxSurge is 0. Defaults to 25%.'
+                        type: integer
+                      strategy:
+                        description: The deployment strategy to use to replace existing
+                          pods with new ones.
+                        enum:
+                        - Recreate
+                        - RollingUpdate
+                        type: string
                     type: object
                   environment:
                     description: The configuration of Environment trait
@@ -2340,6 +2362,28 @@ spec:
                           defaults to 60s.
                         format: int32
                         type: integer
+                      rollingUpdateMaxSurge:
+                        description: 'The maximum number of pods that can be scheduled
+                          above the desired number of pods. Value can be an absolute
+                          number (ex: 5) or a percentage of desired pods (ex: 10%).
+                          This can not be 0 if MaxUnavailable is 0. Absolute number
+                          is calculated from percentage by rounding up. Defaults to
+                          25%.'
+                        type: integer
+                      rollingUpdateMaxUnavailable:
+                        description: 'The maximum number of pods that can be unavailable
+                          during the update. Value can be an absolute number (ex:
+                          5) or a percentage of desired pods (ex: 10%). Absolute number
+                          is calculated from percentage by rounding down. This can
+                          not be 0 if MaxSurge is 0. Defaults to 25%.'
+                        type: integer
+                      strategy:
+                        description: The deployment strategy to use to replace existing
+                          pods with new ones.
+                        enum:
+                        - Recreate
+                        - RollingUpdate
+                        type: string
                     type: object
                   environment:
                     description: The configuration of Environment trait
diff --git a/config/crd/bases/camel.apache.org_integrations.yaml b/config/crd/bases/camel.apache.org_integrations.yaml
index 9ef1f33a9..8108cf337 100644
--- a/config/crd/bases/camel.apache.org_integrations.yaml
+++ b/config/crd/bases/camel.apache.org_integrations.yaml
@@ -6211,6 +6211,28 @@ spec:
                           defaults to 60s.
                         format: int32
                         type: integer
+                      rollingUpdateMaxSurge:
+                        description: 'The maximum number of pods that can be scheduled
+                          above the desired number of pods. Value can be an absolute
+                          number (ex: 5) or a percentage of desired pods (ex: 10%).
+                          This can not be 0 if MaxUnavailable is 0. Absolute number
+                          is calculated from percentage by rounding up. Defaults to
+                          25%.'
+                        type: integer
+                      rollingUpdateMaxUnavailable:
+                        description: 'The maximum number of pods that can be unavailable
+                          during the update. Value can be an absolute number (ex:
+                          5) or a percentage of desired pods (ex: 10%). Absolute number
+                          is calculated from percentage by rounding down. This can
+                          not be 0 if MaxSurge is 0. Defaults to 25%.'
+                        type: integer
+                      strategy:
+                        description: The deployment strategy to use to replace existing
+                          pods with new ones.
+                        enum:
+                        - Recreate
+                        - RollingUpdate
+                        type: string
                     type: object
                   environment:
                     description: The configuration of Environment trait
diff --git a/config/crd/bases/camel.apache.org_kameletbindings.yaml b/config/crd/bases/camel.apache.org_kameletbindings.yaml
index 1da869621..5821108e2 100644
--- a/config/crd/bases/camel.apache.org_kameletbindings.yaml
+++ b/config/crd/bases/camel.apache.org_kameletbindings.yaml
@@ -6497,6 +6497,28 @@ spec:
                               It defaults to 60s.
                             format: int32
                             type: integer
+                          rollingUpdateMaxSurge:
+                            description: 'The maximum number of pods that can be scheduled
+                              above the desired number of pods. Value can be an absolute
+                              number (ex: 5) or a percentage of desired pods (ex:
+                              10%). This can not be 0 if MaxUnavailable is 0. Absolute
+                              number is calculated from percentage by rounding up.
+                              Defaults to 25%.'
+                            type: integer
+                          rollingUpdateMaxUnavailable:
+                            description: 'The maximum number of pods that can be unavailable
+                              during the update. Value can be an absolute number (ex:
+                              5) or a percentage of desired pods (ex: 10%). Absolute
+                              number is calculated from percentage by rounding down.
+                              This can not be 0 if MaxSurge is 0. Defaults to 25%.'
+                            type: integer
+                          strategy:
+                            description: The deployment strategy to use to replace
+                              existing pods with new ones.
+                            enum:
+                            - Recreate
+                            - RollingUpdate
+                            type: string
                         type: object
                       environment:
                         description: The configuration of Environment trait
diff --git a/docs/modules/ROOT/pages/architecture/cr/integration-kit.adoc b/docs/modules/ROOT/pages/architecture/cr/integration-kit.adoc
index 4ae9b47be..55e9df282 100644
--- a/docs/modules/ROOT/pages/architecture/cr/integration-kit.adoc
+++ b/docs/modules/ROOT/pages/architecture/cr/integration-kit.adoc
@@ -1,7 +1,7 @@
 [[integration-kit]]
 = IntegrationKit
 
-The *IntegrationKit* is a fundamental side resource which describe a container image created by the camel-k operator as well as the configurations that need to be applied to every integration that is executed on top of it. An *IntegrationKit* does not include any source code or resource file defined by the **Integration** from which it has been generated.
+The *IntegrationKit* is a fundamental side resource which describes a container image created by the camel-k operator as well as the configurations that need to be applied to every integration that is executed on top of it. An *IntegrationKit* does not include any source code or resource file defined by the **Integration** from which it has been generated.
 
 [source,go]
 ----
diff --git a/docs/modules/ROOT/pages/architecture/operator.adoc b/docs/modules/ROOT/pages/architecture/operator.adoc
index 8897abf95..a6b236820 100644
--- a/docs/modules/ROOT/pages/architecture/operator.adoc
+++ b/docs/modules/ROOT/pages/architecture/operator.adoc
@@ -40,7 +40,7 @@ type Action interface {
 	Handle(ctx context.Context, cr *v1.CR) (*v1.CR, error) // <2>
 }
 ----
-<1> Determine if the action can handle the CR as an example by looking at the phase of the CR which is store as part of the status sub resource.
+<1> Determine if the action can handle the CR as an example by looking at the phase of the CR which is stored as part of the status sub resource.
 <2> Implement the action and return a non `nil` instance to signal to the controller that the CR needs to be updated with the new one, instead, if the method returns a `nil` instance, then nothing will happen and unless the CR changes outside the control of the operator, the same action will be invoked on the next iteration. This is useful when a CR needs to delegate some work to a different controller so the CR won't be moved to the next stage till the sub operation has completed. 
 
 [NOTE]
diff --git a/docs/modules/ROOT/pages/configuration/build-time-properties.adoc b/docs/modules/ROOT/pages/configuration/build-time-properties.adoc
index b1ab7b130..713c3f182 100644
--- a/docs/modules/ROOT/pages/configuration/build-time-properties.adoc
+++ b/docs/modules/ROOT/pages/configuration/build-time-properties.adoc
@@ -18,7 +18,7 @@ from('timer:build-property')
 In order to give a value to the `quarkus.application.name` property you can pass it using the command line with the `--build-property` flag:
 
 ----
-kamel run --build-property=quarkus.application.name=my-super-application build-property-route.groovy --dev
+kamel run --build-property=quarkus.application.name=my-super-application build-property-route.groovy
 ----
 
 You can provide more than one single `build-property` at once by just adding the flag repeatedly (ie, `--build-property=prop1=val1 --build-property=prop2=val2 ...`)
@@ -26,7 +26,7 @@ You can provide more than one single `build-property` at once by just adding the
 [[build-time-props-file]]
 == Property File
 
-Repeating the property flag when you have many *build time configuration* may be cumbersome. Usually you deal with property files instead. You will be able to use the _file_ syntax available for `--build-property` flag. Here, as an example you have a property files with 2 `Quarkus` properties:
+Repeating the `--build-property` flag when you have many *build time configuration* may be cumbersome. Usually you deal with property files instead. You will be able to use the _file_ syntax available for `--build-property` flag. Here, as an example you have a property file with 2 `Quarkus` properties:
 
 [source,properties]
 .quarkus.properties
@@ -45,7 +45,7 @@ from('timer:build-property')
 The `quarkus.banner.enabled` is configured to show the banner during the `Integration` startup. Let's use `--build-property` flag in conjunction with file:
 
 ----
-kamel run --build-property=file:quarkus.properties build-property-file-route.groovy --dev
+kamel run --build-property=file:quarkus.properties build-property-route.groovy
 ----
 
 The property file is parsed and its properties configured on the `Integration`. As soon as the application starts, you will see the log with the expected configuration.
@@ -58,4 +58,4 @@ If you have a property repeated more than once, the general rule is that the las
 [[build-time-runtime-conf]]
 == Run time properties
 
-If you're looking for *runtime properties configuration* you can look at the xref:configuration/runtime-properties.adoc[runtime properties] section.
\ No newline at end of file
+If you're looking for *runtime properties configuration* you can look at the xref:configuration/runtime-properties.adoc[runtime properties] section.
diff --git a/docs/modules/ROOT/pages/configuration/configuration.adoc b/docs/modules/ROOT/pages/configuration/configuration.adoc
index 2a5aa6e43..febf7ef1a 100644
--- a/docs/modules/ROOT/pages/configuration/configuration.adoc
+++ b/docs/modules/ROOT/pages/configuration/configuration.adoc
@@ -17,7 +17,7 @@ from('timer:props?period=1000')
 In order to give a value to the `my.message` property you can pass it using the command line:
 
 ```
-kamel run --property my.message="Hello World" examples/props.js
+kamel run --property my.message="Hello World" props.groovy
 ```
 
-For more details and advanced use cases, see the xref:configuration/runtime-properties.adoc[runtime properties] section.
\ No newline at end of file
+For more details and advanced use cases, see the xref:configuration/runtime-properties.adoc[runtime properties] section.
diff --git a/docs/modules/ROOT/pages/configuration/runtime-properties.adoc b/docs/modules/ROOT/pages/configuration/runtime-properties.adoc
index 69b7d338d..c65b5efb2 100644
--- a/docs/modules/ROOT/pages/configuration/runtime-properties.adoc
+++ b/docs/modules/ROOT/pages/configuration/runtime-properties.adoc
@@ -18,7 +18,7 @@ from('timer:property')
 The simplest way to replace that variable with a real value is to use the `--property` flag (also shortcut by `-p`):
 
 ----
-kamel run -p my.message=test-property property-route.groovy --dev
+kamel run -p my.message=test-property property-route.groovy
 ----
 
 At runtime, that variable will be substituted by the value you've provided. You can provide more than one single `property` at once by just adding the flag repeatedly (ie, `--property prop1=val1 --property prop2=val2 ...`)
@@ -36,7 +36,7 @@ from('timer:property?period={{triggerPeriod}}')
 The simplest way to replace that variable with a real value is to use the `--property` flag (also shortcut by `-p`):
 
 ----
-kamel run -p my.message=test-property -p triggerPeriod=3000 property-route.groovy --dev
+kamel run -p my.message=test-property -p triggerPeriod=3000 property-route.groovy
 ----
 
 [[runtime-props-file]]
@@ -52,7 +52,7 @@ my.key.2=world
 ----
 
 [source,groovy]
-.build-property-route.groovy
+.property-route.groovy
 ----
 from('timer:property-file')
     .routeId('property-file')
@@ -62,7 +62,7 @@ from('timer:property-file')
 You'll need to provide a `property` _file_ flag when launching the application:
 
 ----
-// kamel run --property file:my.properties property-file-route.groovy --dev
+kamel run --property file:my.properties property-route.groovy
 ----
 
 The property file is parsed and its properties configured on the `Integration`. As soon as the application starts, you will see the log with the expected configuration.
diff --git a/docs/modules/ROOT/pages/configuration/runtime-resources.adoc b/docs/modules/ROOT/pages/configuration/runtime-resources.adoc
index 49cbc1f40..30c4a3bb9 100644
--- a/docs/modules/ROOT/pages/configuration/runtime-resources.adoc
+++ b/docs/modules/ROOT/pages/configuration/runtime-resources.adoc
@@ -3,7 +3,7 @@
 
 When you develop an integration with `Camel K` there are many ways you can provide a *resource* to the runtime `Integration`. The most familiar way is to handle local file, but since we are dealing with `Kubernetes` you may need also to use `Configmap` or `Secret`. The `kamel run` command is provided with a `--resource` flag that help you setting any resource your `Integration` may need.
 
-The *runtime resources* are expected to be any resource type (text or binary content). These resources are materialized as files in the `Ìntegration` `Pod`. The destination path can be either a default location or any path specified by the user. Only default resource directory is available within the `Ìntegration` classpath.
+The *runtime resources* are expected to be any resource type (text or binary content). These resources are materialized as files in the `Integration` `Pod`. The destination path can be either a default location or any path specified by the user. Only default resource directory is available within the `Integration` classpath.
 
 NOTE: you'll find `--resource` is very similar to the `--config` run flag. The main difference is that a `resource` can have a binary content and it won't be parsed by the Camel Context.
 
@@ -25,7 +25,7 @@ from('file:/etc/camel/resources/?fileName=resources-data.zip&noop=true&idempoten
 We have this file available locally, so we can use the `--resource` _file_ flag command to copy this file in the `Integration`:
 
 ----
-kamel run --resource file:resources-data.zip resource-file-binary-route.groovy -d camel:zipfile --dev
+kamel run --resource file:resources-data.zip resource-file-binary-route.groovy -d camel:zipfile
 ----
 
 NOTE: when you're providing a resource file, we try to recognize if it's a binary file and process it accordingly creating a binary representation that will be decoded transparently in the `Integration`.
@@ -55,17 +55,17 @@ from('file:/etc/camel/resources/my-cm/?fileName=my-configmap-key&noop=true&idemp
 You can see that we're expecting to use a _my-configmap-key_ file stored in the default resource location (_/etc/camel/resources/_). In order to materialize the `Configmap` will be as easy as running the `--resource` _configmap_ syntax:
 
 ----
-kamel run --resource configmap:my-cm resource-configmap-route.groovy --dev
+kamel run --resource configmap:my-cm resource-configmap-route.groovy
 ----
 
-As soon as the `Integration` starts, the `Camel K` operator will take care to mount a volume with the `Configmap`'s content.
+As soon as the `Integration` starts, the `Camel K` operator will take care to mount a volume with the `Configmap` 's content.
 
 NOTE: you can provide a `Configmap` which is not yet available on the cluster. The `Integration` won't start until the resource will be made available.
 
 [[runtime-resource-secret]]
 == Runtime secret resource
 
-We can apply the very same concept seen in the previous section for the Kubernetes `Secret`s.
+We can apply the very same concept seen in the previous section for the Kubernetes `Secret` 's.
 
 As an example, let's create a `Secret` named _my-sec_ containing certain information. You can alternatively use any `Secret` you've already stored in your cluster:
 
@@ -85,10 +85,10 @@ from('file:/etc/camel/resources/my-sec/?fileName=my-secret-key&noop=true&idempot
 You can see that we're expecting to use a _my-secret-key_ file stored in the default resource location (_/etc/camel/resources/_). In order to materialize the `Secret` will be as easy as running the `--resource` _secret_ syntax:
 
 ----
-kamel run --resource secret:my-sec resource-secret-route.groovy --dev
+kamel run --resource secret:my-sec resource-secret-route.groovy
 ----
 
-As soon as the `Integration` starts, the `Camel K` operator will take care to mount a volume with the `Secret`'s content.
+As soon as the `Integration` starts, the `Camel K` operator will take care to mount a volume with the `Secret` 's content.
 
 NOTE: you can provide a `Secret` which is not yet available on the cluster. The `Integration` won't start until the resource will be made available.
 
@@ -109,10 +109,10 @@ from('file:/tmp/?fileName=input.txt&noop=true&idempotent=false')
 When running the `Integration`, you can specify where to mount the resource content (either a `File`, `Configmap` or `Secret`) with the _@path_ syntax:
 
 ----
-kamel run --resource file:resources-data.txt@/tmp/input.txt resource-file-location-route.groovy --dev
+kamel run --resource file:resources-data.txt@/tmp/input.txt resource-file-location-route.groovy
 ----
 
-You may check in the `Integration` `Pod` and verify the file was mounted in the _tmp/input.txt_ destination.
+You may check in the `Integration` `Pod` and verify that the file was mounted in the _tmp/input.txt_ destination.
 
 [[runtime-resource-keys]]
 == Configmap/Secret key filtering
@@ -137,7 +137,7 @@ from('file:/tmp/app/data/?fileName=test.txt&noop=true&idempotent=false')
 Let's use the _key_ filtering. Also notice that we're combining with the _@path_ syntax to declare where to mount the file:
 
 ----
-kamel run --resource configmap:my-cm-multi/my-configmap-key-2@/tmp/app/data/test.txt resource-configmap-key-location-route.groovy --dev
+kamel run --resource configmap:my-cm-multi/my-configmap-key-2@/tmp/app/data/test.txt resource-configmap-key-location-route.groovy
 ----
 
 You may check in the `Integration` `Pod` that only the _test.txt_ file has been mounted under _/tmp/app/data_ directory containing the information you had in _my-configmap-key-2_.
diff --git a/docs/modules/ROOT/pages/kamelets/kamelets-dev.adoc b/docs/modules/ROOT/pages/kamelets/kamelets-dev.adoc
index 00a12f614..6e52c598d 100644
--- a/docs/modules/ROOT/pages/kamelets/kamelets-dev.adoc
+++ b/docs/modules/ROOT/pages/kamelets/kamelets-dev.adoc
@@ -472,6 +472,7 @@ Let's try sorting out these issues in the route (we publish here the final versi
 // camel-k: language=java
 
 import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.model.ClaimCheckOperation;
 import org.apache.camel.Exchange;
 
 public class Earthquake extends RouteBuilder {
diff --git a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
index 64a43b020..79006bfd8 100644
--- a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
+++ b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
@@ -4216,6 +4216,36 @@ int32
 The maximum time in seconds for the deployment to make progress before it
 is considered to be failed. It defaults to 60s.
 
+|`strategy` +
+*https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#deploymentstrategytype-v1-apps[Kubernetes apps/v1.DeploymentStrategyType]*
+|
+
+
+The deployment strategy to use to replace existing pods with new ones.
+
+|`rollingUpdateMaxUnavailable` +
+int
+|
+
+
+The maximum number of pods that can be unavailable during the update.
+Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
+Absolute number is calculated from percentage by rounding down.
+This can not be 0 if MaxSurge is 0.
+Defaults to 25%.
+
+|`rollingUpdateMaxSurge` +
+int
+|
+
+
+The maximum number of pods that can be scheduled above the desired number of
+pods.
+Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
+This can not be 0 if MaxUnavailable is 0.
+Absolute number is calculated from percentage by rounding up.
+Defaults to 25%.
+
 
 |===
 
diff --git a/docs/modules/traits/pages/deployment.adoc b/docs/modules/traits/pages/deployment.adoc
index 6f1a0a779..6188609e4 100755
--- a/docs/modules/traits/pages/deployment.adoc
+++ b/docs/modules/traits/pages/deployment.adoc
@@ -33,6 +33,27 @@ The following configuration options are available:
 | The maximum time in seconds for the deployment to make progress before it
 is considered to be failed. It defaults to 60s.
 
+| deployment.strategy
+| DeploymentStrategyType
+| The deployment strategy to use to replace existing pods with new ones.
+
+| deployment.rolling-update-max-unavailable
+| int
+| The maximum number of pods that can be unavailable during the update.
+Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
+Absolute number is calculated from percentage by rounding down.
+This can not be 0 if MaxSurge is 0.
+Defaults to 25%.
+
+| deployment.rolling-update-max-surge
+| int
+| The maximum number of pods that can be scheduled above the desired number of
+pods.
+Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
+This can not be 0 if MaxUnavailable is 0.
+Absolute number is calculated from percentage by rounding up.
+Defaults to 25%.
+
 |===
 
 // End of autogenerated code - DO NOT EDIT! (configuration)
diff --git a/helm/camel-k/crds/crd-integration-platform.yaml b/helm/camel-k/crds/crd-integration-platform.yaml
index ffd239464..cda07f954 100644
--- a/helm/camel-k/crds/crd-integration-platform.yaml
+++ b/helm/camel-k/crds/crd-integration-platform.yaml
@@ -734,6 +734,28 @@ spec:
                           defaults to 60s.
                         format: int32
                         type: integer
+                      rollingUpdateMaxSurge:
+                        description: 'The maximum number of pods that can be scheduled
+                          above the desired number of pods. Value can be an absolute
+                          number (ex: 5) or a percentage of desired pods (ex: 10%).
+                          This can not be 0 if MaxUnavailable is 0. Absolute number
+                          is calculated from percentage by rounding up. Defaults to
+                          25%.'
+                        type: integer
+                      rollingUpdateMaxUnavailable:
+                        description: 'The maximum number of pods that can be unavailable
+                          during the update. Value can be an absolute number (ex:
+                          5) or a percentage of desired pods (ex: 10%). Absolute number
+                          is calculated from percentage by rounding down. This can
+                          not be 0 if MaxSurge is 0. Defaults to 25%.'
+                        type: integer
+                      strategy:
+                        description: The deployment strategy to use to replace existing
+                          pods with new ones.
+                        enum:
+                        - Recreate
+                        - RollingUpdate
+                        type: string
                     type: object
                   environment:
                     description: The configuration of Environment trait
@@ -2340,6 +2362,28 @@ spec:
                           defaults to 60s.
                         format: int32
                         type: integer
+                      rollingUpdateMaxSurge:
+                        description: 'The maximum number of pods that can be scheduled
+                          above the desired number of pods. Value can be an absolute
+                          number (ex: 5) or a percentage of desired pods (ex: 10%).
+                          This can not be 0 if MaxUnavailable is 0. Absolute number
+                          is calculated from percentage by rounding up. Defaults to
+                          25%.'
+                        type: integer
+                      rollingUpdateMaxUnavailable:
+                        description: 'The maximum number of pods that can be unavailable
+                          during the update. Value can be an absolute number (ex:
+                          5) or a percentage of desired pods (ex: 10%). Absolute number
+                          is calculated from percentage by rounding down. This can
+                          not be 0 if MaxSurge is 0. Defaults to 25%.'
+                        type: integer
+                      strategy:
+                        description: The deployment strategy to use to replace existing
+                          pods with new ones.
+                        enum:
+                        - Recreate
+                        - RollingUpdate
+                        type: string
                     type: object
                   environment:
                     description: The configuration of Environment trait
diff --git a/helm/camel-k/crds/crd-integration.yaml b/helm/camel-k/crds/crd-integration.yaml
index 9ef1f33a9..8108cf337 100644
--- a/helm/camel-k/crds/crd-integration.yaml
+++ b/helm/camel-k/crds/crd-integration.yaml
@@ -6211,6 +6211,28 @@ spec:
                           defaults to 60s.
                         format: int32
                         type: integer
+                      rollingUpdateMaxSurge:
+                        description: 'The maximum number of pods that can be scheduled
+                          above the desired number of pods. Value can be an absolute
+                          number (ex: 5) or a percentage of desired pods (ex: 10%).
+                          This can not be 0 if MaxUnavailable is 0. Absolute number
+                          is calculated from percentage by rounding up. Defaults to
+                          25%.'
+                        type: integer
+                      rollingUpdateMaxUnavailable:
+                        description: 'The maximum number of pods that can be unavailable
+                          during the update. Value can be an absolute number (ex:
+                          5) or a percentage of desired pods (ex: 10%). Absolute number
+                          is calculated from percentage by rounding down. This can
+                          not be 0 if MaxSurge is 0. Defaults to 25%.'
+                        type: integer
+                      strategy:
+                        description: The deployment strategy to use to replace existing
+                          pods with new ones.
+                        enum:
+                        - Recreate
+                        - RollingUpdate
+                        type: string
                     type: object
                   environment:
                     description: The configuration of Environment trait
diff --git a/helm/camel-k/crds/crd-kamelet-binding.yaml b/helm/camel-k/crds/crd-kamelet-binding.yaml
index 1da869621..5821108e2 100644
--- a/helm/camel-k/crds/crd-kamelet-binding.yaml
+++ b/helm/camel-k/crds/crd-kamelet-binding.yaml
@@ -6497,6 +6497,28 @@ spec:
                               It defaults to 60s.
                             format: int32
                             type: integer
+                          rollingUpdateMaxSurge:
+                            description: 'The maximum number of pods that can be scheduled
+                              above the desired number of pods. Value can be an absolute
+                              number (ex: 5) or a percentage of desired pods (ex:
+                              10%). This can not be 0 if MaxUnavailable is 0. Absolute
+                              number is calculated from percentage by rounding up.
+                              Defaults to 25%.'
+                            type: integer
+                          rollingUpdateMaxUnavailable:
+                            description: 'The maximum number of pods that can be unavailable
+                              during the update. Value can be an absolute number (ex:
+                              5) or a percentage of desired pods (ex: 10%). Absolute
+                              number is calculated from percentage by rounding down.
+                              This can not be 0 if MaxSurge is 0. Defaults to 25%.'
+                            type: integer
+                          strategy:
+                            description: The deployment strategy to use to replace
+                              existing pods with new ones.
+                            enum:
+                            - Recreate
+                            - RollingUpdate
+                            type: string
                         type: object
                       environment:
                         description: The configuration of Environment trait
diff --git a/pkg/apis/camel/v1/trait/deployment.go b/pkg/apis/camel/v1/trait/deployment.go
index f862c66f9..9e96dc218 100644
--- a/pkg/apis/camel/v1/trait/deployment.go
+++ b/pkg/apis/camel/v1/trait/deployment.go
@@ -17,6 +17,10 @@ limitations under the License.
 
 package trait
 
+import (
+	appsv1 "k8s.io/api/apps/v1"
+)
+
 // The Deployment trait is responsible for generating the Kubernetes deployment that will make sure
 // the integration will run in the cluster.
 //
@@ -26,4 +30,20 @@ type DeploymentTrait struct {
 	// The maximum time in seconds for the deployment to make progress before it
 	// is considered to be failed. It defaults to 60s.
 	ProgressDeadlineSeconds *int32 `property:"progress-deadline-seconds" json:"progressDeadlineSeconds,omitempty"`
+	// The deployment strategy to use to replace existing pods with new ones.
+	// +kubebuilder:validation:Enum=Recreate;RollingUpdate
+	Strategy appsv1.DeploymentStrategyType `property:"strategy" json:"strategy,omitempty"`
+	// The maximum number of pods that can be unavailable during the update.
+	// Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
+	// Absolute number is calculated from percentage by rounding down.
+	// This can not be 0 if MaxSurge is 0.
+	// Defaults to 25%.
+	RollingUpdateMaxUnavailable *int `property:"rolling-update-max-unavailable" json:"rollingUpdateMaxUnavailable,omitempty"`
+	// The maximum number of pods that can be scheduled above the desired number of
+	// pods.
+	// Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
+	// This can not be 0 if MaxUnavailable is 0.
+	// Absolute number is calculated from percentage by rounding up.
+	// Defaults to 25%.
+	RollingUpdateMaxSurge *int `property:"rolling-update-max-surge" json:"rollingUpdateMaxSurge,omitempty"`
 }
diff --git a/pkg/apis/camel/v1/trait/zz_generated.deepcopy.go b/pkg/apis/camel/v1/trait/zz_generated.deepcopy.go
index 002296715..cab7a094d 100644
--- a/pkg/apis/camel/v1/trait/zz_generated.deepcopy.go
+++ b/pkg/apis/camel/v1/trait/zz_generated.deepcopy.go
@@ -233,6 +233,16 @@ func (in *DeploymentTrait) DeepCopyInto(out *DeploymentTrait) {
 		*out = new(int32)
 		**out = **in
 	}
+	if in.RollingUpdateMaxUnavailable != nil {
+		in, out := &in.RollingUpdateMaxUnavailable, &out.RollingUpdateMaxUnavailable
+		*out = new(int)
+		**out = **in
+	}
+	if in.RollingUpdateMaxSurge != nil {
+		in, out := &in.RollingUpdateMaxSurge, &out.RollingUpdateMaxSurge
+		*out = new(int)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentTrait.
diff --git a/pkg/cmd/local.go b/pkg/cmd/local.go
index 1a480490f..c5616d5ed 100644
--- a/pkg/cmd/local.go
+++ b/pkg/cmd/local.go
@@ -24,6 +24,14 @@ import (
 	"github.com/spf13/cobra"
 )
 
+// Usage descritions of common flags that are shared across some of the subcommands.
+const (
+	usageImage                = `Full path to integration image including registry, e.g. "docker.io/user/app"`
+	usageIntegrationDirectory = "Directory to hold local integration files"
+	usagePropertyFile         = "Add a property file to the integration"
+	usageProperty             = "Add a Camel property to the integration"
+)
+
 // newCmdLocal -- Add local kamel subcommand with several other subcommands of its own.
 func newCmdLocal(rootCmdOptions *RootCmdOptions) (*cobra.Command, *LocalCmdOptions) {
 	options := LocalCmdOptions{
@@ -41,6 +49,8 @@ func newCmdLocal(rootCmdOptions *RootCmdOptions) (*cobra.Command, *LocalCmdOptio
 	}
 
 	cmd.PersistentFlags().StringArrayVarP(&options.Dependencies, "dependency", "d", nil, usageDependency)
+	cmd.PersistentFlags().StringArrayVar(&options.MavenRepositories, "maven-repository", nil,
+		"Use a maven repository")
 
 	// hidden flags for compatibility with kamel run
 	cmd.PersistentFlags().StringArrayVarP(&options.Traits, "trait", "t", nil, "")
@@ -57,8 +67,9 @@ func newCmdLocal(rootCmdOptions *RootCmdOptions) (*cobra.Command, *LocalCmdOptio
 
 type LocalCmdOptions struct {
 	*RootCmdOptions
-	Dependencies []string `mapstructure:"dependencies"`
-	Traits       []string `mapstructure:"traits"`
+	Dependencies      []string `mapstructure:"dependencies"`
+	MavenRepositories []string `mapstructure:"maven-repositories"`
+	Traits            []string `mapstructure:"traits"`
 }
 
 func (o *LocalCmdOptions) persistentPreRun(cmd *cobra.Command, args []string) error {
@@ -75,6 +86,7 @@ func (o *LocalCmdOptions) persistentPreRun(cmd *cobra.Command, args []string) er
 
 func warnTraitUsages(cmd *cobra.Command, traits []string) {
 	if len(traits) > 0 {
-		fmt.Fprintf(cmd.OutOrStdout(), "Warning: traits are specified but don't take effect for local run: %v\n", traits)
+		fmt.Fprintf(cmd.OutOrStdout(),
+			"Warning: traits are specified but don't take effect for local run: %v\n", traits)
 	}
 }
diff --git a/pkg/cmd/local/container.go b/pkg/cmd/local/container.go
new file mode 100644
index 000000000..8174176d8
--- /dev/null
+++ b/pkg/cmd/local/container.go
@@ -0,0 +1,348 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package local
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"os/signal"
+	"strings"
+	"syscall"
+
+	"github.com/apache/camel-k/pkg/util"
+	"github.com/apache/camel-k/pkg/util/docker"
+	"github.com/pkg/errors"
+)
+
+// Local Docker file system management functions.
+
+// CreateDockerBaseWorkingDirectory creates local docker base directory.
+func CreateDockerBaseWorkingDirectory() error {
+	temporaryDirectory, err := ioutil.TempDir(os.TempDir(), "docker-base-")
+	if err != nil {
+		return err
+	}
+
+	// Set the Docker base directory to the default value.
+	docker.BaseWorkingDirectory = temporaryDirectory
+
+	return nil
+}
+
+// DeleteDockerBaseWorkingDirectory removes directory used for computing the base dependencies.
+func DeleteDockerBaseWorkingDirectory() error {
+	return os.RemoveAll(docker.BaseWorkingDirectory)
+}
+
+// CreateDockerWorkingDirectory creates local docker directory.
+func CreateDockerWorkingDirectory() error {
+	temporaryDirectory, err := ioutil.TempDir(os.TempDir(), "docker-")
+	if err != nil {
+		return err
+	}
+
+	// Set the Docker base directory to the default value.
+	docker.IntegrationWorkingDirectory = temporaryDirectory
+
+	return nil
+}
+
+// DeleteDockerWorkingDirectory removes directory used for computing the integration dependencies.
+func DeleteDockerWorkingDirectory() error {
+	return os.RemoveAll(docker.IntegrationWorkingDirectory)
+}
+
+func SetDockerNetworkName(networkName string) {
+	if networkName != "" {
+		docker.NetworkName = networkName
+	}
+}
+
+func SetDockerEnvVars(envVars []string) {
+	if len(envVars) > 0 {
+		util.CLIEnvVars = envVars
+	}
+}
+
+func createAndBuildBaseImage(ctx context.Context, stdout, stderr io.Writer) error {
+	// This ensures the Dockerfile for the base image will not end up in an undesired location.
+	if docker.BaseWorkingDirectory == "" {
+		return errors.New("base directory that holds the base image Dockerfile has not been set correctly")
+	}
+
+	// Create the base image Docker file.
+	if err := docker.CreateBaseImageDockerFile(); err != nil {
+		return err
+	}
+
+	return buildBaseImage(ctx, stdout, stderr)
+}
+
+func buildBaseImage(ctx context.Context, stdout, stderr io.Writer) error {
+	// Get the Docker command arguments for building the base image and create the command.
+	args := docker.BuildBaseImageArgs()
+	cmd := exec.CommandContext(ctx, "docker", args...)
+
+	// Set stdout and stderr.
+	cmd.Stdout = stdout
+	cmd.Stderr = stderr
+
+	// Output executed command.
+	fmt.Fprintln(cmd.Stdout, "Executing:", strings.Join(cmd.Args, " "))
+
+	// Run the command.
+	if err := cmd.Run(); err != nil {
+		return errors.Errorf("base image containerization did not run successfully: %v", err)
+	}
+
+	return nil
+}
+
+func setupDockerRegistry(containerRegistry string, image string, justBaseImage bool) error {
+	docker.RegistryName = containerRegistry
+	// If we build a normal image, i.e. not the base image, we need to parse
+	// the location where images will be pushed.
+	if !justBaseImage {
+		registryName, err := docker.ExtractRegistryName(image)
+		if err != nil {
+			return err
+		}
+		docker.RegistryName = registryName
+	}
+
+	return nil
+}
+
+func CreateAndBuildIntegrationImage(ctx context.Context, containerRegistry string, justBaseImage bool, image string,
+	propertyFiles []string, dependencies []string, routes []string, startsFromLocalFolder bool,
+	stdout, stderr io.Writer) error {
+	if err := setupDockerRegistry(containerRegistry, image, justBaseImage); err != nil {
+		return err
+	}
+
+	// Create the Dockerfile and build the base image.
+	if err := createAndBuildBaseImage(ctx, stdout, stderr); err != nil {
+		return err
+	}
+
+	if justBaseImage {
+		return nil
+	}
+
+	if docker.IntegrationWorkingDirectory == "" {
+		return errors.New("integration directory that holds the image Dockerfile has not been set correctly")
+	}
+
+	// Create integration image if integration files were provided.
+	// Copy dependencies to a dependencies folder under a local directory.
+	if err := updateIntegrationDependencies(dependencies); err != nil {
+		return err
+	}
+
+	// Copy routes to a routes folder under a local directory.
+	if err := updateIntegrationRoutes(routes); err != nil {
+		return err
+	}
+
+	// Copy quarkus files in maven subdirectory
+	if err := updateQuarkusDirectory(); err != nil {
+		return err
+	}
+
+	// Copy app files in maven subdirectory
+	if err := updateAppDirectory(); err != nil {
+		return err
+	}
+
+	// Copy lib files in maven subdirectory
+	if err := updateLibDirectory(); err != nil {
+		return err
+	}
+
+	// Get integration run command to be run inside the container. This means the command
+	// has to be created with the paths which will be valid inside the container.
+	containerCmd, err := getContainerIntegrationRunCommand(ctx, propertyFiles, dependencies, routes, stdout, stderr)
+	if err != nil {
+		return err
+	}
+
+	// Create the integration image Docker file.
+	if err := docker.CreateIntegrationImageDockerFile(containerCmd, startsFromLocalFolder); err != nil {
+		return err
+	}
+
+	return buildIntegrationImage(ctx, image, stdout, stderr)
+}
+
+func buildIntegrationImage(ctx context.Context, image string, stdout, stderr io.Writer) error {
+	// Get the Docker command arguments for building the base image and create the command.
+	args := docker.BuildIntegrationImageArgs(image, MavenWorkingDirectory)
+	cmd := exec.CommandContext(ctx, "docker", args...)
+
+	// Set stdout and stderr.
+	cmd.Stderr = stderr
+	cmd.Stdout = stdout
+
+	// Output executed command.
+	fmt.Fprintln(cmd.Stdout, "Executing:", strings.Join(cmd.Args, " "))
+
+	// Run the command.
+	if err := cmd.Run(); err != nil {
+		return errors.Errorf("integration image containerization did not run successfully: %v", err)
+	}
+
+	return nil
+}
+
+func RunIntegrationImage(ctx context.Context, image string, stdout, stderr io.Writer) error {
+	// Stop the child process before exiting
+	dockerCtx, cancel := context.WithCancel(ctx)
+	cs := make(chan os.Signal, 1)
+	signal.Notify(cs, os.Interrupt, syscall.SIGTERM)
+	go func() {
+		<-cs
+		cancel()
+	}()
+
+	// Get the docker command line argument for running an image.
+	args, err := docker.RunIntegrationImageArgs(image)
+	if err != nil {
+		return err
+	}
+
+	cmd := exec.CommandContext(dockerCtx, "docker", args...)
+
+	// Set stdout and stderr.
+	cmd.Stderr = stderr
+	cmd.Stdout = stdout
+
+	// Output executed command.
+	fmt.Fprintln(cmd.Stdout, "Executing:", strings.Join(cmd.Args, " "))
+
+	// Run the command.
+	if err := cmd.Run(); err != nil {
+		return errors.Errorf("integration image did not run successfully: %v", err)
+	}
+
+	return nil
+}
+
+// getContainerIntegrationRunCommand returns the integration command which will be run inside the container.
+func getContainerIntegrationRunCommand(ctx context.Context, properties []string, dependencies []string, routes []string,
+	stdout, stderr io.Writer) (*exec.Cmd, error) {
+	// All paths need to be valid container paths.
+	// Update property file paths.
+	containerProperties := docker.ContainerizeFilePaths(properties, docker.GetContainerPropertiesDir())
+	containerDependencies := docker.ContainerizeDependencyPaths(dependencies, docker.GetContainerDependenciesDir())
+	containerRoutes := docker.ContainerizeFilePaths(routes, docker.GetContainerRoutesDir())
+
+	return assembleIntegrationRunCommand(ctx, containerProperties, containerDependencies, containerRoutes,
+		docker.GetContainerPropertiesDir(), stdout, stderr, false)
+}
+
+func RunLocalIntegration(ctx context.Context, properties []string, dependencies []string, routes []string,
+	propertiesDir string, stdout, stderr io.Writer) error {
+	cmd, err := assembleIntegrationRunCommand(ctx, properties, dependencies, routes,
+		propertiesDir, stdout, stderr, true)
+	if err != nil {
+		return err
+	}
+
+	// Output command we are about to run.
+	fmt.Fprintln(cmd.Stdout, "Executing:", strings.Join(cmd.Args, " "))
+
+	// Run integration locally.
+	return cmd.Run()
+}
+
+func assembleIntegrationRunCommand(ctx context.Context, properties []string, dependencies []string, routes []string,
+	propertiesDir string, stdout, stderr io.Writer, local bool) (*exec.Cmd, error) {
+	// Create classpath value.
+	classpathValue := assembleClasspathArgValue(properties, dependencies, routes)
+
+	// Create java command that runs the integration.
+	javaCmd := "java"
+
+	// Create java command arguments.
+	args := make([]string, 0)
+	args = append(args, "-cp")
+	args = append(args, classpathValue)
+	args = append(args, "io.quarkus.bootstrap.runner.QuarkusEntryPoint")
+
+	cmd := exec.CommandContext(ctx, javaCmd, args...)
+
+	// Add directory where the properties files reside. The directory is the local properties directory
+	// or the properties directory inside the container.
+	cmd.Env = append(cmd.Env, "CAMEL_K_CONF_D="+propertiesDir)
+
+	// Add files to the command line under the CAMEL_K_ROUTES flag.
+	cmd.Env = append(cmd.Env, "CAMEL_K_ROUTES="+strings.Join(formatRoutes(routes), ","))
+
+	// Add any lazily evaluated environment variables.
+	if local {
+		// If we are running locally then this is as late as we can evaluate the
+		// lazy environment variables since we are going to run the command
+		// immediately after the generation of these arguments.
+		setEnvVars, err := util.EvaluateCLIAndLazyEnvVars()
+		if err != nil {
+			return nil, err
+		}
+		cmd.Env = append(cmd.Env, setEnvVars...)
+	} else {
+		// If we are running in containerized or just building an image, we should
+		// not evaluate the variables at this point since we are only generating the
+		// run command and not actually running it.
+		for _, lazyEnvVar := range util.ListOfLazyEvaluatedEnvVars {
+			cmd.Env = append(cmd.Env, lazyEnvVar+"={{env:"+lazyEnvVar+"}}")
+		}
+	}
+
+	// Set stdout and stderr.
+	cmd.Stderr = stderr
+	cmd.Stdout = stdout
+
+	return cmd, nil
+}
+
+func assembleClasspathArgValue(properties []string, dependencies []string, routes []string) string {
+	classpathContents := []string{}
+	classpathContents = append(classpathContents, properties...)
+	classpathContents = append(classpathContents, routes...)
+	classpathContents = append(classpathContents, dependencies...)
+	return strings.Join(classpathContents, string(os.PathListSeparator))
+}
+
+func formatRoutes(files []string) []string {
+	routes := []string{}
+	for _, route := range files {
+		// Split route path.
+		a := strings.Split(route, ".")
+
+		// Extract extension.
+		extension := a[len(a)-1]
+
+		// Add file with extension.
+		routes = append(routes, "file:"+route+"?language="+extension)
+	}
+
+	return routes
+}
diff --git a/pkg/cmd/local_util.go b/pkg/cmd/local/local.go
similarity index 63%
rename from pkg/cmd/local_util.go
rename to pkg/cmd/local/local.go
index c9b04feda..8f150ad6c 100644
--- a/pkg/cmd/local_util.go
+++ b/pkg/cmd/local/local.go
@@ -15,10 +15,11 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package cmd
+package local
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -31,6 +32,7 @@ import (
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/builder"
+	"github.com/apache/camel-k/pkg/cmd/source"
 	"github.com/apache/camel-k/pkg/trait"
 	"github.com/apache/camel-k/pkg/util"
 	"github.com/apache/camel-k/pkg/util/camel"
@@ -49,13 +51,13 @@ func GetDependencies(ctx context.Context, args []string, additionalDependencies
 	// Fetch existing catalog or create new one if one does not already exist
 	catalog, err := createCamelCatalog(ctx)
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "failed to create Camel catalog")
 	}
 
 	// Get top-level dependencies
 	dependencies, err := getTopLevelDependencies(ctx, catalog, args)
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "failed to get top-level dependencies")
 	}
 
 	// Add additional user-provided dependencies
@@ -71,9 +73,10 @@ func GetDependencies(ctx context.Context, args []string, additionalDependencies
 
 		dependencies, err = getTransitiveDependencies(ctx, catalog, dependencies, repositories)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "failed to compute transitive dependencies")
 		}
 	}
+
 	return dependencies, nil
 }
 
@@ -82,15 +85,15 @@ func getTopLevelDependencies(ctx context.Context, catalog *camel.RuntimeCatalog,
 	dependencies := strset.New()
 
 	// Invoke the dependency inspector code for each source file
-	for _, source := range args {
-		data, _, _, err := loadTextContent(ctx, source, false)
+	for _, src := range args {
+		data, _, _, err := source.LoadTextContent(ctx, src, false)
 		if err != nil {
 			return []string{}, err
 		}
 
 		sourceSpec := v1.SourceSpec{
 			DataSpec: v1.DataSpec{
-				Name:        path.Base(source),
+				Name:        path.Base(src),
 				Content:     data,
 				Compression: false,
 			},
@@ -114,7 +117,7 @@ func getTransitiveDependencies(ctx context.Context, catalog *camel.RuntimeCatalo
 		return nil, err
 	}
 
-	mc := maven.NewContext(util.MavenWorkingDirectory)
+	mc := maven.NewContext(MavenWorkingDirectory)
 	mc.LocalRepository = ""
 
 	if len(repositories) > 0 {
@@ -150,43 +153,47 @@ func getTransitiveDependencies(ctx context.Context, catalog *camel.RuntimeCatalo
 	return transitiveDependencies, nil
 }
 
-func getRegularFilesInDir(directory string) ([]string, error) {
+func getRegularFilesInDir(directory string, dirnameInPath bool) ([]string, error) {
 	var dirFiles []string
 	files, err := ioutil.ReadDir(directory)
+	if err != nil {
+		return nil, err
+	}
+
 	for _, file := range files {
 		fileName := file.Name()
 
 		// Do not include hidden files or sub-directories.
 		if !file.IsDir() && !strings.HasPrefix(fileName, ".") {
-			dirFiles = append(dirFiles, path.Join(directory, fileName))
+			if dirnameInPath {
+				dirFiles = append(dirFiles, path.Join(directory, fileName))
+			} else {
+				dirFiles = append(dirFiles, fileName)
+			}
 		}
 	}
 
-	if err != nil {
-		return nil, err
-	}
-
 	return dirFiles, nil
 }
 
-func getLocalBuildDependencies(integrationDirectory string) ([]string, error) {
-	locallyBuiltDependencies, err := getRegularFilesInDir(getCustomDependenciesDir(integrationDirectory))
+func GetBuildDependencies(integrationDirectory string) ([]string, error) {
+	locallyBuiltDependencies, err := getRegularFilesInDir(GetCustomDependenciesDir(integrationDirectory), true)
 	if err != nil {
 		return nil, err
 	}
 	return locallyBuiltDependencies, nil
 }
 
-func getLocalBuildProperties(integrationDirectory string) ([]string, error) {
-	locallyBuiltProperties, err := getRegularFilesInDir(getCustomPropertiesDir(integrationDirectory))
+func GetBuildProperties(integrationDirectory string) ([]string, error) {
+	locallyBuiltProperties, err := getRegularFilesInDir(GetCustomPropertiesDir(integrationDirectory), true)
 	if err != nil {
 		return nil, err
 	}
 	return locallyBuiltProperties, nil
 }
 
-func getLocalBuildRoutes(integrationDirectory string) ([]string, error) {
-	locallyBuiltRoutes, err := getRegularFilesInDir(getCustomRoutesDir(integrationDirectory))
+func GetBuildRoutes(integrationDirectory string) ([]string, error) {
+	locallyBuiltRoutes, err := getRegularFilesInDir(GetCustomRoutesDir(integrationDirectory), true)
 	if err != nil {
 		return nil, err
 	}
@@ -230,10 +237,9 @@ func createCamelCatalog(ctx context.Context) (*camel.RuntimeCatalog, error) {
 	return catalog, nil
 }
 
-func outputDependencies(dependencies []string, format string, cmd *cobra.Command) error {
+func OutputDependencies(dependencies []string, format string, cmd *cobra.Command) error {
 	if format != "" {
-		err := printDependencies(format, dependencies, cmd)
-		if err != nil {
+		if err := printDependencies(format, dependencies, cmd); err != nil {
 			return err
 		}
 	} else {
@@ -250,23 +256,38 @@ func outputDependencies(dependencies []string, format string, cmd *cobra.Command
 func printDependencies(format string, dependencies []string, cmd *cobra.Command) error {
 	switch format {
 	case "yaml":
-		data, err := util.DependenciesToYAML(dependencies)
+		data, err := dependenciesToYAML(dependencies)
 		if err != nil {
 			return err
 		}
 		fmt.Fprint(cmd.OutOrStdout(), string(data))
 	case "json":
-		data, err := util.DependenciesToJSON(dependencies)
+		data, err := dependenciesToJSON(dependencies)
 		if err != nil {
 			return err
 		}
 		fmt.Fprint(cmd.OutOrStdout(), string(data))
 	default:
-		return errors.New("unknown output format: " + format)
+		return errors.Errorf("unknown output format: %s", format)
 	}
 	return nil
 }
 
+func dependenciesToJSON(list []string) ([]byte, error) {
+	jsondata := map[string]interface{}{}
+	jsondata["dependencies"] = list
+	return json.Marshal(jsondata)
+}
+
+func dependenciesToYAML(list []string) ([]byte, error) {
+	data, err := dependenciesToJSON(list)
+	if err != nil {
+		return nil, err
+	}
+
+	return util.JSONToYAML(data)
+}
+
 func validateFile(file string) error {
 	fileExists, err := util.FileExists(file)
 	if err != nil {
@@ -280,7 +301,8 @@ func validateFile(file string) error {
 	return nil
 }
 
-func validateFiles(args []string) error {
+// ValidateFiles ensures existence of given files.
+func ValidateFiles(args []string) error {
 	// Ensure source files exist
 	for _, arg := range args {
 		err := validateFile(arg)
@@ -292,9 +314,9 @@ func validateFiles(args []string) error {
 	return nil
 }
 
-// validateDependencies validates list of additional dependencies i.e. makes sure
+// ValidateDependencies validates list of additional dependencies i.e. makes sure
 // that each dependency has a valid type.
-func validateDependencies(dependencies []string) error {
+func ValidateDependencies(dependencies []string) error {
 	for _, dependency := range dependencies {
 		depType := strings.Split(dependency, ":")[0]
 		if !util.StringSliceExists(acceptedDependencyTypes, depType) {
@@ -305,21 +327,7 @@ func validateDependencies(dependencies []string) error {
 	return nil
 }
 
-func validateIntegrationFiles(args []string) error {
-	// If no source files have been provided there is nothing to inspect.
-	if len(args) == 0 {
-		return errors.New("no integration files have been provided")
-	}
-
-	// Validate integration files.
-	if err := validateFiles(args); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func validatePropertyFiles(propertyFiles []string) error {
+func ValidatePropertyFiles(propertyFiles []string) error {
 	for _, fileName := range propertyFiles {
 		if err := validatePropertyFile(fileName); err != nil {
 			return err
@@ -343,20 +351,19 @@ func validatePropertyFile(fileName string) error {
 	return nil
 }
 
-func updateIntegrationProperties(properties []string, propertyFiles []string, hasIntegrationDir bool) ([]string, error) {
+func UpdateIntegrationProperties(properties []string, propertyFiles []string, hasIntegrationDir bool) ([]string, error) {
 	// Create properties directory under Maven working directory.
 	// This ensures that property files of different integrations do not clash.
-	err := util.CreateLocalPropertiesDirectory()
-	if err != nil {
+	if err := CreateLocalPropertiesDirectory(); err != nil {
 		return nil, err
 	}
 
 	// Relocate properties files to this integration's property directory.
 	relocatedPropertyFiles := []string{}
+	dir := GetLocalPropertiesDir()
 	for _, propertyFile := range propertyFiles {
-		relocatedPropertyFile := path.Join(util.GetLocalPropertiesDir(), path.Base(propertyFile))
-		_, err = util.CopyFile(propertyFile, relocatedPropertyFile)
-		if err != nil {
+		relocatedPropertyFile := path.Join(dir, path.Base(propertyFile))
+		if _, err := util.CopyFile(propertyFile, relocatedPropertyFile); err != nil {
 			return nil, err
 		}
 		relocatedPropertyFiles = append(relocatedPropertyFiles, relocatedPropertyFile)
@@ -365,9 +372,8 @@ func updateIntegrationProperties(properties []string, propertyFiles []string, ha
 	if !hasIntegrationDir {
 		// Output list of properties to property file if any CLI properties were given.
 		if len(properties) > 0 {
-			propertyFilePath := path.Join(util.GetLocalPropertiesDir(), "CLI.properties")
-			err = ioutil.WriteFile(propertyFilePath, []byte(strings.Join(properties, "\n")), 0o600)
-			if err != nil {
+			propertyFilePath := path.Join(dir, "CLI.properties")
+			if err := ioutil.WriteFile(propertyFilePath, []byte(strings.Join(properties, "\n")), 0o600); err != nil {
 				return nil, err
 			}
 			relocatedPropertyFiles = append(relocatedPropertyFiles, propertyFilePath)
@@ -380,22 +386,21 @@ func updateIntegrationProperties(properties []string, propertyFiles []string, ha
 func updateIntegrationDependencies(dependencies []string) error {
 	// Create dependencies directory under Maven working directory.
 	// This ensures that dependencies will be removed after they are not needed.
-	err := util.CreateLocalDependenciesDirectory()
-	if err != nil {
+	if err := createLocalDependenciesDirectory(); err != nil {
 		return err
 	}
 
 	// Relocate dependencies files to this integration's dependencies directory
+	dir := getLocalDependenciesDir()
 	for _, dependency := range dependencies {
 		var targetPath string
 		basePath := util.SubstringFrom(dependency, util.QuarkusDependenciesBaseDirectory)
 		if basePath != "" {
-			targetPath = path.Join(util.GetLocalDependenciesDir(), basePath)
+			targetPath = path.Join(dir, basePath)
 		} else {
-			targetPath = path.Join(util.GetLocalDependenciesDir(), path.Base(dependency))
+			targetPath = path.Join(dir, path.Base(dependency))
 		}
-		_, err = util.CopyFile(dependency, targetPath)
-		if err != nil {
+		if _, err := util.CopyFile(dependency, targetPath); err != nil {
 			return err
 		}
 	}
@@ -404,14 +409,13 @@ func updateIntegrationDependencies(dependencies []string) error {
 }
 
 func updateIntegrationRoutes(routes []string) error {
-	err := util.CreateLocalRoutesDirectory()
-	if err != nil {
+	if err := createLocalRoutesDirectory(); err != nil {
 		return err
 	}
 
+	dir := getLocalRoutesDir()
 	for _, route := range routes {
-		_, err = util.CopyFile(route, path.Join(util.GetLocalRoutesDir(), path.Base(route)))
-		if err != nil {
+		if _, err := util.CopyFile(route, path.Join(dir, path.Base(route))); err != nil {
 			return err
 		}
 	}
@@ -420,97 +424,121 @@ func updateIntegrationRoutes(routes []string) error {
 }
 
 func updateQuarkusDirectory() error {
-	err := util.CreateLocalQuarkusDirectory()
-	if err != nil {
+	if err := createLocalQuarkusDirectory(); err != nil {
 		return err
 	}
 
 	// ignore error if custom dir doesn't exist
-	_ = util.CopyQuarkusAppFiles(util.CustomQuarkusDirectoryName, util.GetLocalQuarkusDir())
+	_ = CopyQuarkusAppFiles(util.CustomQuarkusDirectoryName, getLocalQuarkusDir())
 
 	return nil
 }
 
 func updateAppDirectory() error {
-	err := util.CreateLocalAppDirectory()
-	if err != nil {
+	if err := createLocalAppDirectory(); err != nil {
 		return err
 	}
 
 	// ignore error if custom dir doesn't exist
-	_ = util.CopyAppFile(util.CustomAppDirectoryName, util.GetLocalAppDir())
+	_ = CopyAppFile(util.CustomAppDirectoryName, getLocalAppDir())
 
 	return nil
 }
 
 func updateLibDirectory() error {
-	err := util.CreateLocalLibDirectory()
-	if err != nil {
+	if err := createLocalLibDirectory(); err != nil {
 		return err
 	}
 
 	// ignore error if custom dir doesn't exist
-	_ = util.CopyLibFiles(util.CustomLibDirectoryName, util.GetLocalLibDir())
+	_ = CopyLibFiles(util.CustomLibDirectoryName, getLocalLibDir())
 
 	return nil
 }
 
-func createMavenWorkingDirectory() error {
-	// Create local Maven context
-	temporaryDirectory, err := ioutil.TempDir(os.TempDir(), "maven-")
-	if err != nil {
-		return err
+func CopyIntegrationFilesToDirectory(files []string, directory string) ([]string, error) {
+	// Create directory if one does not already exist
+	if err := util.CreateDirectory(directory); err != nil {
+		return nil, err
 	}
 
-	// Set the Maven directory to the default value
-	util.MavenWorkingDirectory = temporaryDirectory
+	// Copy files to new location. Also create the list with relocated files.
+	relocatedFilesList := []string{}
+	for _, filePath := range files {
+		newFilePath := path.Join(directory, path.Base(filePath))
+		if _, err := util.CopyFile(filePath, newFilePath); err != nil {
+			return relocatedFilesList, err
+		}
+		relocatedFilesList = append(relocatedFilesList, newFilePath)
+	}
 
-	return nil
+	return relocatedFilesList, nil
 }
 
-func deleteMavenWorkingDirectory() error {
-	// Remove directory used for computing the dependencies
-	return os.RemoveAll(util.MavenWorkingDirectory)
-}
+func CopyQuarkusAppFiles(dependenciesDir string, quarkusDir string) error {
+	// Create directory if one does not already exist
+	if err := util.CreateDirectory(quarkusDir); err != nil {
+		return err
+	}
 
-func getCustomDependenciesDir(integrationDirectory string) string {
-	return path.Join(integrationDirectory, "dependencies")
-}
+	// Transfer all files with a .dat extension and all files with a *-bytecode.jar suffix.
+	files, err := getRegularFilesInDir(dependenciesDir, false)
+	if err != nil {
+		return err
+	}
+	for _, file := range files {
+		if strings.HasSuffix(file, ".dat") || strings.HasSuffix(file, "-bytecode.jar") {
+			source := path.Join(dependenciesDir, file)
+			destination := path.Join(quarkusDir, file)
+			if _, err = util.CopyFile(source, destination); err != nil {
+				return err
+			}
+		}
+	}
 
-func getCustomPropertiesDir(integrationDirectory string) string {
-	return path.Join(integrationDirectory, "properties")
+	return nil
 }
 
-func getCustomRoutesDir(integrationDirectory string) string {
-	return path.Join(integrationDirectory, "routes")
-}
+func CopyLibFiles(dependenciesDir string, libDir string) error {
+	// Create directory if one does not already exist
+	if err := util.CreateDirectory(libDir); err != nil {
+		return err
+	}
 
-func getCustomQuarkusDir(integrationDirectory string) string {
-	parentDir := path.Dir(strings.TrimSuffix(integrationDirectory, "/"))
-	return path.Join(parentDir, "quarkus")
-}
+	fileNames, err := getRegularFilesInDir(dependenciesDir, false)
+	if err != nil {
+		return err
+	}
 
-func getCustomLibDir(integrationDirectory string) string {
-	parentDir := path.Dir(strings.TrimSuffix(integrationDirectory, "/"))
-	return path.Join(parentDir, "lib/main")
-}
+	for _, dependencyJar := range fileNames {
+		source := path.Join(dependenciesDir, dependencyJar)
+		destination := path.Join(libDir, dependencyJar)
+		if _, err = util.CopyFile(source, destination); err != nil {
+			return err
+		}
+	}
 
-func getCustomAppDir(integrationDirectory string) string {
-	parentDir := path.Dir(strings.TrimSuffix(integrationDirectory, "/"))
-	return path.Join(parentDir, "app")
+	return nil
 }
 
-func deleteLocalIntegrationDirs(integrationDirectory string) error {
-	dirs := []string{
-		getCustomQuarkusDir(integrationDirectory),
-		getCustomLibDir(integrationDirectory),
-		getCustomAppDir(integrationDirectory),
+func CopyAppFile(dependenciesDir string, appDir string) error {
+	// Create directory if one does not already exist
+	if err := util.CreateDirectory(appDir); err != nil {
+		return err
 	}
 
-	for _, dir := range dirs {
-		err := os.RemoveAll(dir)
-		if err != nil {
-			return err
+	fileNames, err := getRegularFilesInDir(dependenciesDir, false)
+	if err != nil {
+		return err
+	}
+
+	for _, dependencyJar := range fileNames {
+		if strings.HasPrefix(dependencyJar, "camel-k-integration-") {
+			source := path.Join(dependenciesDir, dependencyJar)
+			destination := path.Join(appDir, dependencyJar)
+			if _, err = util.CopyFile(source, destination); err != nil {
+				return err
+			}
 		}
 	}
 
diff --git a/pkg/cmd/local/workdir.go b/pkg/cmd/local/workdir.go
new file mode 100644
index 000000000..3ab67f5ba
--- /dev/null
+++ b/pkg/cmd/local/workdir.go
@@ -0,0 +1,244 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package local
+
+import (
+	"io/ioutil"
+	"os"
+	"path"
+	"strings"
+
+	"github.com/apache/camel-k/pkg/util"
+)
+
+// MavenWorkingDirectory is the directory used by Maven for an invocation of the kamel local command.
+// By default, a temporary folder will be used.
+var MavenWorkingDirectory = ""
+
+// CreateMavenWorkingDirectory creates local Maven working directory.
+func CreateMavenWorkingDirectory() error {
+	temporaryDirectory, err := ioutil.TempDir(os.TempDir(), "maven-")
+	if err != nil {
+		return err
+	}
+
+	// Set the Maven directory to the default value
+	MavenWorkingDirectory = temporaryDirectory
+
+	return nil
+}
+
+// DeleteMavenWorkingDirectory removes local Maven working directory.
+func DeleteMavenWorkingDirectory() error {
+	return os.RemoveAll(MavenWorkingDirectory)
+}
+
+// getLocalDependenciesDir returns <mavenWorkingDirectory>/dependencies.
+func getLocalDependenciesDir() string {
+	return path.Join(MavenWorkingDirectory, util.DefaultDependenciesDirectoryName)
+}
+
+func createLocalDependenciesDirectory() error {
+	// Do not create a directory unless the maven directory contains a valid value.
+	if MavenWorkingDirectory == "" {
+		return nil
+	}
+
+	directoryExists, err := util.DirectoryExists(getLocalDependenciesDir())
+	if err != nil {
+		return err
+	}
+
+	if !directoryExists {
+		if err := os.MkdirAll(getLocalDependenciesDir(), 0o700); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// GetLocalPropertiesDir returns <mavenWorkingDirectory>/properties.
+func GetLocalPropertiesDir() string {
+	return path.Join(MavenWorkingDirectory, util.DefaultPropertiesDirectoryName)
+}
+
+func CreateLocalPropertiesDirectory() error {
+	// Do not create a directory unless the maven directory contains a valid value.
+	if MavenWorkingDirectory == "" {
+		return nil
+	}
+
+	directoryExists, err := util.DirectoryExists(GetLocalPropertiesDir())
+	if err != nil {
+		return err
+	}
+
+	if !directoryExists {
+		err := os.MkdirAll(GetLocalPropertiesDir(), 0o700)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// getLocalRoutesDir returns <mavenWorkingDirectory>/routes.
+func getLocalRoutesDir() string {
+	return path.Join(MavenWorkingDirectory, util.DefaultRoutesDirectoryName)
+}
+
+func createLocalRoutesDirectory() error {
+	// Do not create a directory unless the maven directory contains a valid value.
+	if MavenWorkingDirectory == "" {
+		return nil
+	}
+
+	directoryExists, err := util.DirectoryExists(getLocalRoutesDir())
+	if err != nil {
+		return err
+	}
+
+	if !directoryExists {
+		if err := os.MkdirAll(getLocalRoutesDir(), 0o700); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// getLocalQuarkusDir returns <mavenWorkingDirectory>/quarkus.
+func getLocalQuarkusDir() string {
+	return path.Join(MavenWorkingDirectory, util.CustomQuarkusDirectoryName)
+}
+
+func createLocalQuarkusDirectory() error {
+	// Do not create a directory unless the maven directory contains a valid value.
+	if MavenWorkingDirectory == "" {
+		return nil
+	}
+
+	directoryExists, err := util.DirectoryExists(getLocalQuarkusDir())
+	if err != nil {
+		return err
+	}
+
+	if !directoryExists {
+		if err := os.MkdirAll(getLocalQuarkusDir(), 0o700); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// getLocalAppDir returns <mavenWorkingDirectory>/app.
+func getLocalAppDir() string {
+	return path.Join(MavenWorkingDirectory, util.CustomAppDirectoryName)
+}
+
+func createLocalAppDirectory() error {
+	// Do not create a directory unless the maven directory contains a valid value.
+	if MavenWorkingDirectory == "" {
+		return nil
+	}
+
+	directoryExists, err := util.DirectoryExists(getLocalAppDir())
+	if err != nil {
+		return err
+	}
+
+	if !directoryExists {
+		if err := os.MkdirAll(getLocalAppDir(), 0o700); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// getLocalLibDir returns <mavenWorkingDirectory>/lib/main.
+func getLocalLibDir() string {
+	return path.Join(MavenWorkingDirectory, util.CustomLibDirectoryName)
+}
+
+func createLocalLibDirectory() error {
+	// Do not create a directory unless the maven directory contains a valid value.
+	if MavenWorkingDirectory == "" {
+		return nil
+	}
+
+	directoryExists, err := util.DirectoryExists(getLocalLibDir())
+	if err != nil {
+		return err
+	}
+
+	if !directoryExists {
+		if err := os.MkdirAll(getLocalLibDir(), 0o700); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func GetCustomDependenciesDir(dir string) string {
+	return path.Join(dir, util.DefaultDependenciesDirectoryName)
+}
+
+func GetCustomPropertiesDir(dir string) string {
+	return path.Join(dir, util.DefaultPropertiesDirectoryName)
+}
+
+func GetCustomRoutesDir(dir string) string {
+	return path.Join(dir, util.DefaultRoutesDirectoryName)
+}
+
+func GetCustomQuarkusDir(dir string) string {
+	parentDir := path.Dir(strings.TrimSuffix(dir, "/"))
+	return path.Join(parentDir, util.CustomQuarkusDirectoryName)
+}
+
+func GetCustomLibDir(dir string) string {
+	parentDir := path.Dir(strings.TrimSuffix(dir, "/"))
+	return path.Join(parentDir, util.CustomLibDirectoryName)
+}
+
+func GetCustomAppDir(dir string) string {
+	parentDir := path.Dir(strings.TrimSuffix(dir, "/"))
+	return path.Join(parentDir, "app")
+}
+
+func DeleteLocalIntegrationDirs(dir string) error {
+	dirs := []string{
+		GetCustomQuarkusDir(dir),
+		GetCustomLibDir(dir),
+		GetCustomAppDir(dir),
+	}
+
+	for _, dir := range dirs {
+		err := os.RemoveAll(dir)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/pkg/cmd/local_build.go b/pkg/cmd/local_build.go
index 5c0d72cd4..6f8cbcfd0 100644
--- a/pkg/cmd/local_build.go
+++ b/pkg/cmd/local_build.go
@@ -23,6 +23,7 @@ import (
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
 
+	"github.com/apache/camel-k/pkg/cmd/local"
 	"github.com/apache/camel-k/pkg/util"
 )
 
@@ -37,7 +38,7 @@ func newCmdLocalBuild(localCmdOptions *LocalCmdOptions) (*cobra.Command, *localB
 		Long:    `Build integration images locally for containerized integrations.`,
 		PreRunE: decode(&options),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			if err := options.validate(args); err != nil {
+			if err := options.validate(cmd, args); err != nil {
 				return err
 			}
 			if err := options.init(args); err != nil {
@@ -57,14 +58,15 @@ func newCmdLocalBuild(localCmdOptions *LocalCmdOptions) (*cobra.Command, *localB
 		},
 	}
 
-	cmd.Flags().Bool("base-image", false, "Build base image used as a starting point for any integration.")
-	cmd.Flags().Bool("dependencies-only", false, "Only output the integration dependencies. The integration-directory flag must be set.")
-	cmd.Flags().String("container-registry", "", "Registry that holds intermediate images. This flag should only be used in conjunction with the base-image flag.")
-	cmd.Flags().String("image", "", "Full path to integration image including registry.")
-	cmd.Flags().String("integration-directory", "", "Directory to hold local integration files.")
-	cmd.Flags().StringArray("property-file", nil, "Add a property file to the integration.")
-	cmd.Flags().StringArrayP("property", "p", nil, "Add a Camel property to the integration.")
-	cmd.Flags().StringArray("maven-repository", nil, "Use a maven repository")
+	cmd.Flags().Bool("base-image", false, "Build base image used as a starting point for any integration")
+	cmd.Flags().Bool("dependencies-only", false,
+		"Only output the integration dependencies. The integration-directory flag must be set.")
+	cmd.Flags().String("container-registry", "",
+		"Registry that holds intermediate images. This flag should only be used in conjunction with the base-image flag.")
+	cmd.Flags().String("image", "", usageImage)
+	cmd.Flags().String("integration-directory", "", usageIntegrationDirectory)
+	cmd.Flags().StringArray("property-file", nil, usagePropertyFile)
+	cmd.Flags().StringArrayP("property", "p", nil, usageProperty)
 
 	return &cmd, &options
 }
@@ -78,99 +80,122 @@ type localBuildCmdOptions struct {
 	IntegrationDirectory string   `mapstructure:"integration-directory"`
 	Properties           []string `mapstructure:"properties"`
 	PropertyFiles        []string `mapstructure:"property-files"`
-	MavenRepositories    []string `mapstructure:"maven-repositories"`
 }
 
-func (command *localBuildCmdOptions) validate(args []string) error {
-	// Validate integration files.
+func (o *localBuildCmdOptions) validate(cmd *cobra.Command, args []string) error {
+	if o.BaseImage {
+		return o.validateBaseImageMode(cmd, args)
+	}
+
+	return o.validateIntegrationMode(args)
+}
+
+func (o *localBuildCmdOptions) validateBaseImageMode(cmd *cobra.Command, args []string) error {
+	// Cannot have both integration files and the base image construction enabled.
 	if len(args) > 0 {
-		if err := validateIntegrationFiles(args); err != nil {
-			return err
-		}
+		return errors.New("cannot use --base-image with integration files")
 	}
 
-	// Validate additional dependencies specified by the user.
-	if err := validateDependencies(command.Dependencies); err != nil {
-		return err
+	// Docker registry must be set.
+	if o.ContainerRegistry == "" {
+		return errors.New("--base-image requires --container-registry")
 	}
 
-	// Validate properties file.
-	if err := validateFiles(command.PropertyFiles); err != nil {
-		return err
+	// If an integration directory is provided then no base image containerization can be enabled.
+	if o.IntegrationDirectory != "" {
+		return errors.New("cannot use --integration-directory with --base-image")
 	}
 
-	if command.BaseImage {
-		// Cannot have both integration files and the base image construction enabled.
-		if len(args) > 0 {
-			return errors.New("integration files have been provided and the base image construction is enabled")
-		}
+	if o.DependenciesOnly {
+		return errors.New("cannot use --dependencies-only with --base-image")
+	}
 
-		// Docker registry must be set.
-		if command.ContainerRegistry == "" {
-			return errors.New("base image cannot be built because container registry has not been provided")
-		}
+	if len(o.Dependencies) > 0 || len(o.PropertyFiles) > 0 || len(o.Properties) > 0 {
+		fmt.Fprintln(cmd.OutOrStdout(),
+			"Warning: --dependency, --property, and --property-file are ignored in --base-image mode")
+	}
+
+	return nil
+}
 
-		// If an integration directory is provided then no base image containerization can be enabled.
-		if command.IntegrationDirectory != "" {
-			return errors.New("base image construction does not use integration files")
+func (o *localBuildCmdOptions) validateIntegrationMode(args []string) error {
+	if len(args) == 0 {
+		if o.IntegrationDirectory == "" {
+			return errors.New("either integration files, --integration-directory, or --base-image must be provided")
 		}
-	} else if command.ContainerRegistry != "" {
+	} else {
+		if o.IntegrationDirectory == "" && o.Image == "" {
+			return errors.New("either --integration-directory or --image must be provided with integration files")
+		}
+	}
+
+	if o.ContainerRegistry != "" {
 		// ContainerRegistry should only be specified when building the base image.
-		return errors.New("cannot specify container registry unless a base integration image is being built")
+		return errors.New("--container-registry must be used with --base-image")
 	}
 
 	// The integration directory must be set when only outputting dependencies.
-	if command.DependenciesOnly && command.IntegrationDirectory == "" {
-		return errors.New("to output dependencies the integration directory flag must be set")
+	if o.DependenciesOnly && o.IntegrationDirectory == "" {
+		return errors.New("--dependencies-only requires --integration-directory")
+	}
+
+	// Validate integration files.
+	if err := local.ValidateFiles(args); err != nil {
+		return err
+	}
+
+	// Validate additional dependencies specified by the user.
+	if err := local.ValidateDependencies(o.Dependencies); err != nil {
+		return err
+	}
+
+	// Validate properties file.
+	if err := local.ValidateFiles(o.PropertyFiles); err != nil {
+		return err
 	}
 
 	return nil
 }
 
-func (command *localBuildCmdOptions) init(args []string) error {
+func (o *localBuildCmdOptions) init(args []string) error {
 	// Create integration directory if one is provided.
-	err := util.CreateDirectory(command.IntegrationDirectory)
-	if err != nil {
-		return err
+	if o.IntegrationDirectory != "" {
+		if err := util.CreateDirectory(o.IntegrationDirectory); err != nil {
+			return err
+		}
 	}
 
-	if command.BaseImage || command.Image != "" {
+	if o.BaseImage || o.Image != "" {
 		// If base image construction is enabled create a directory for it.
-		err := createDockerBaseWorkingDirectory()
-		if err != nil {
+		if err := local.CreateDockerBaseWorkingDirectory(); err != nil {
 			return err
 		}
 
 		// If integration image construction is enabled, an integration image will be built.
-		if command.Image != "" {
-			err := createDockerWorkingDirectory()
-			if err != nil {
+		if o.Image != "" {
+			if err := local.CreateDockerWorkingDirectory(); err != nil {
 				return err
 			}
 		}
 	}
 
-	err = createMavenWorkingDirectory()
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return local.CreateMavenWorkingDirectory()
 }
 
-func (command *localBuildCmdOptions) run(cmd *cobra.Command, args []string) error {
+func (o *localBuildCmdOptions) run(cmd *cobra.Command, args []string) error {
 	var dependenciesList, propertyFilesList []string
 	routeFiles := args
-	if !command.BaseImage {
-		dependencies, err := GetDependencies(command.Context, args, command.Dependencies, command.MavenRepositories, true)
+
+	if !o.BaseImage {
+		dependencies, err := local.GetDependencies(o.Context, args, o.Dependencies, o.MavenRepositories, true)
 		if err != nil {
 			return err
 		}
 
 		var propertyFiles []string
-		if !command.DependenciesOnly {
+		if !o.DependenciesOnly {
 			// Manage integration properties which may come from files or CLI
-			propertyFiles, err = updateIntegrationProperties(command.Properties, command.PropertyFiles, false)
+			propertyFiles, err = local.UpdateIntegrationProperties(o.Properties, o.PropertyFiles, false)
 			if err != nil {
 				return err
 			}
@@ -178,77 +203,65 @@ func (command *localBuildCmdOptions) run(cmd *cobra.Command, args []string) erro
 
 		dependenciesList = dependencies
 		propertyFilesList = propertyFiles
-		hasIntegrationDir := command.IntegrationDirectory != ""
-		if hasIntegrationDir {
-			// Create dependencies subdirectory.
-			localDependenciesDirectory := getCustomDependenciesDir(command.IntegrationDirectory)
 
-			// Copy dependencies in persistent IntegrationDirectory/dependencies
-			dependenciesList, err = util.CopyIntegrationFilesToDirectory(dependencies, localDependenciesDirectory)
+		// Integration directory can only be used when building an integration image or when we just
+		// build the integration without also building the image. A local build of the integration is
+		// represented by all the files that define the integration: dependencies, properties, and routes.
+		if o.IntegrationDirectory != "" {
+			localDependenciesDir := local.GetCustomDependenciesDir(o.IntegrationDirectory)
+			dependenciesList, err = local.CopyIntegrationFilesToDirectory(dependencies, localDependenciesDir)
 			if err != nil {
 				return err
 			}
 
-			// Once dependencies have been copied to local folder, we can exit.
-			if command.DependenciesOnly {
+			if o.DependenciesOnly {
+				// Once dependencies have been copied to local folder, we can exit.
 				return nil
 			}
 
-			// Create dependencies subdirectory.
-			localPropertiesDirectory := getCustomPropertiesDir(command.IntegrationDirectory)
-
-			// Copy dependencies in persistent IntegrationDirectory/dependencies
-			propertyFilesList, err = util.CopyIntegrationFilesToDirectory(propertyFiles, localPropertiesDirectory)
+			localPropertiesDir := local.GetCustomPropertiesDir(o.IntegrationDirectory)
+			propertyFilesList, err = local.CopyIntegrationFilesToDirectory(propertyFiles, localPropertiesDir)
 			if err != nil {
 				return err
 			}
 
-			// Save routes.
-			localRoutesDirectory := getCustomRoutesDir(command.IntegrationDirectory)
-
-			// Copy routes in persistent IntegrationDirectory/dependencies
-			routeFiles, err = util.CopyIntegrationFilesToDirectory(args, localRoutesDirectory)
+			localRoutesDir := local.GetCustomRoutesDir(o.IntegrationDirectory)
+			routeFiles, err = local.CopyIntegrationFilesToDirectory(args, localRoutesDir)
 			if err != nil {
 				return err
 			}
-		}
-	}
 
-	// Integration directory can only be used when building an integration image or when we just
-	// build the integration without also building the image. A local build of the integration is
-	// represented by all the files that define the integration: dependencies, properties and routes.
-
-	// The only case in which we should not execute the integration image creation is when we want to
-	// just output the files that comprise the integration locally.
-	if command.IntegrationDirectory != "" && command.Image == "" {
-		return nil
+			// The only case in which we should not execute the integration image creation is when we want to
+			// just output the files that comprise the integration locally.
+			if o.Image == "" {
+				fmt.Fprintf(cmd.OutOrStdout(),
+					"Integration directory generated: %s\n", o.IntegrationDirectory)
+				return nil
+			}
+		}
 	}
 
-	// Create and build integration image.
-	err := createAndBuildIntegrationImage(command.Context, command.ContainerRegistry, command.BaseImage,
-		command.Image, propertyFilesList, dependenciesList, routeFiles, false, cmd.OutOrStdout(), cmd.ErrOrStderr())
-	if err != nil {
+	if err := local.CreateAndBuildIntegrationImage(o.Context, o.ContainerRegistry, o.BaseImage, o.Image,
+		propertyFilesList, dependenciesList, routeFiles, false,
+		cmd.OutOrStdout(), cmd.ErrOrStderr()); err != nil {
 		return err
 	}
 
 	return nil
 }
 
-func (command *localBuildCmdOptions) deinit() error {
+func (o *localBuildCmdOptions) deinit() error {
 	// If base image construction is enabled delete the directory for it.
-	err := deleteDockerBaseWorkingDirectory()
-	if err != nil {
+	if err := local.DeleteDockerBaseWorkingDirectory(); err != nil {
 		return err
 	}
 
 	// If integration files are provided delete the maven project folder.
-	if !command.BaseImage {
-		err = deleteDockerWorkingDirectory()
-		if err != nil {
+	if !o.BaseImage {
+		if err := local.DeleteDockerWorkingDirectory(); err != nil {
 			return err
 		}
-		err = deleteMavenWorkingDirectory()
-		if err != nil {
+		if err := local.DeleteMavenWorkingDirectory(); err != nil {
 			return err
 		}
 	}
diff --git a/pkg/cmd/local_inspect.go b/pkg/cmd/local_inspect.go
index 54fca9862..553f36fca 100644
--- a/pkg/cmd/local_inspect.go
+++ b/pkg/cmd/local_inspect.go
@@ -18,8 +18,10 @@ limitations under the License.
 package cmd
 
 import (
+	"errors"
 	"fmt"
 
+	"github.com/apache/camel-k/pkg/cmd/local"
 	"github.com/spf13/cobra"
 )
 
@@ -58,48 +60,50 @@ will be generated by calling Maven and then printed in the selected output forma
 
 	cmd.Flags().Bool("all-dependencies", false, "Enable computation of transitive dependencies.")
 	cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml")
-	cmd.Flags().StringArray("maven-repository", nil, "Use a maven repository")
 
 	return &cmd, &options
 }
 
 type localInspectCmdOptions struct {
 	*LocalCmdOptions
-	AllDependencies   bool     `mapstructure:"all-dependencies"`
-	OutputFormat      string   `mapstructure:"output"`
-	MavenRepositories []string `mapstructure:"maven-repositories"`
+	AllDependencies bool   `mapstructure:"all-dependencies"`
+	OutputFormat    string `mapstructure:"output"`
 }
 
-func (command *localInspectCmdOptions) validate(args []string) error {
-	if err := validateIntegrationFiles(args); err != nil {
+func (o *localInspectCmdOptions) validate(args []string) error {
+	// If no source files have been provided there is nothing to inspect.
+	if len(args) == 0 {
+		return errors.New("no integration files have been provided")
+	}
+
+	if err := local.ValidateFiles(args); err != nil {
 		return err
 	}
 
-	if err := validateDependencies(command.Dependencies); err != nil {
+	if err := local.ValidateDependencies(o.Dependencies); err != nil {
 		return err
 	}
 
 	return nil
 }
 
-func (command *localInspectCmdOptions) init() error {
-	return createMavenWorkingDirectory()
+func (o *localInspectCmdOptions) init() error {
+	return local.CreateMavenWorkingDirectory()
 }
 
-func (command *localInspectCmdOptions) run(cmd *cobra.Command, args []string) error {
-	dependencies, err := GetDependencies(command.Context, args, command.Dependencies, command.MavenRepositories, command.AllDependencies)
+func (o *localInspectCmdOptions) run(cmd *cobra.Command, args []string) error {
+	dependencies, err := local.GetDependencies(o.Context, args, o.Dependencies, o.MavenRepositories, o.AllDependencies)
 	if err != nil {
 		return err
 	}
 
-	err = outputDependencies(dependencies, command.OutputFormat, cmd)
-	if err != nil {
+	if err = local.OutputDependencies(dependencies, o.OutputFormat, cmd); err != nil {
 		return err
 	}
 
 	return nil
 }
 
-func (command *localInspectCmdOptions) deinit() error {
-	return deleteMavenWorkingDirectory()
+func (o *localInspectCmdOptions) deinit() error {
+	return local.DeleteMavenWorkingDirectory()
 }
diff --git a/pkg/cmd/local_run.go b/pkg/cmd/local_run.go
index ec021ed0d..2c3ce54a7 100644
--- a/pkg/cmd/local_run.go
+++ b/pkg/cmd/local_run.go
@@ -23,10 +23,10 @@ import (
 	"os/signal"
 	"syscall"
 
+	"github.com/apache/camel-k/pkg/cmd/local"
+	"github.com/apache/camel-k/pkg/util"
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
-
-	"github.com/apache/camel-k/pkg/util"
 )
 
 func newCmdLocalRun(localCmdOptions *LocalCmdOptions) (*cobra.Command, *localRunCmdOptions) {
@@ -74,13 +74,12 @@ func newCmdLocalRun(localCmdOptions *LocalCmdOptions) (*cobra.Command, *localRun
 	}
 
 	cmd.Flags().Bool("containerize", false, "Run integration in a local container.")
-	cmd.Flags().String("image", "", "Full path to integration image including registry.")
+	cmd.Flags().String("image", "", usageImage)
 	cmd.Flags().String("network", "", "Custom network name to be used by the underlying Docker command.")
-	cmd.Flags().String("integration-directory", "", "Directory which holds the locally built integration and is the result of a local build action.")
+	cmd.Flags().String("integration-directory", "", usageIntegrationDirectory)
 	cmd.Flags().StringArrayP("env", "e", nil, "Flag to specify an environment variable [--env VARIABLE=value].")
-	cmd.Flags().StringArray("property-file", nil, "Add a property file to the integration.")
-	cmd.Flags().StringArrayP("property", "p", nil, "Add a Camel property to the integration.")
-	cmd.Flags().StringArray("maven-repository", nil, "Use a maven repository")
+	cmd.Flags().StringArray("property-file", nil, usagePropertyFile)
+	cmd.Flags().StringArrayP("property", "p", nil, usageProperty)
 
 	return &cmd, &options
 }
@@ -94,184 +93,191 @@ type localRunCmdOptions struct {
 	EnvironmentVariables []string `mapstructure:"envs"`
 	PropertyFiles        []string `mapstructure:"property-files"`
 	Properties           []string `mapstructure:"properties"`
-	MavenRepositories    []string `mapstructure:"maven-repositories"`
 }
 
-func (command *localRunCmdOptions) validate(args []string) error {
-	// Validate integration files when no image is provided and we are
-	// not running an already locally-built integration.
-	if command.Image == "" && command.IntegrationDirectory == "" {
-		if err := validateIntegrationFiles(args); err != nil {
-			return err
-		}
+func (o *localRunCmdOptions) validate(args []string) error {
+	if len(args) == 0 && o.IntegrationDirectory == "" && o.Image == "" {
+		return errors.New("either integration files, --image, or --integration-directory must be provided")
+	}
+
+	// If containerize is set then docker image name must be set.
+	if o.Containerize && o.Image == "" {
+		return errors.New("--containerize requires --image")
+	}
+
+	// Validate integration files.
+	if err := local.ValidateFiles(args); err != nil {
+		return err
 	}
 
 	// Validate additional dependencies specified by the user.
-	if err := validateDependencies(command.Dependencies); err != nil {
+	if err := local.ValidateDependencies(o.Dependencies); err != nil {
 		return err
 	}
 
 	// Validate properties file.
-	if err := validatePropertyFiles(command.PropertyFiles); err != nil {
+	if err := local.ValidatePropertyFiles(o.PropertyFiles); err != nil {
 		return err
 	}
 
-	// If containerize is set then docker image name must be set.
-	if command.Containerize && command.Image == "" {
-		return errors.New("containerization is active but no image name has been provided")
+	if o.IntegrationDirectory != "" {
+		if ok, err := util.DirectoryExists(o.IntegrationDirectory); err != nil {
+			return err
+		} else if !ok {
+			return errors.Errorf("integration directory %q does not exist", o.IntegrationDirectory)
+		}
 	}
 
 	return nil
 }
 
-func (command *localRunCmdOptions) init() error {
-	if command.Containerize {
-		if err := createDockerBaseWorkingDirectory(); err != nil {
+func (o *localRunCmdOptions) init() error {
+	if o.Containerize {
+		if err := local.CreateDockerBaseWorkingDirectory(); err != nil {
 			return err
 		}
-
-		if err := createDockerWorkingDirectory(); err != nil {
+		if err := local.CreateDockerWorkingDirectory(); err != nil {
 			return err
 		}
 	}
+	local.SetDockerNetworkName(o.Network)
+	local.SetDockerEnvVars(o.EnvironmentVariables)
 
-	setDockerNetworkName(command.Network)
-
-	setDockerEnvVars(command.EnvironmentVariables)
-
-	return createMavenWorkingDirectory()
+	return local.CreateMavenWorkingDirectory()
 }
 
-func (command *localRunCmdOptions) run(cmd *cobra.Command, args []string) error {
+func (o *localRunCmdOptions) run(cmd *cobra.Command, args []string) error {
 	// If local run is provided with an image name, it will just run the image locally and exit.
-	if command.Image != "" && !command.Containerize {
+	if o.Image != "" && !o.Containerize {
 		// Run image locally.
-		if err := runIntegrationImage(command.Context, command.Image, cmd.OutOrStdout(), cmd.ErrOrStderr()); err != nil {
+		return local.RunIntegrationImage(o.Context, o.Image, cmd.OutOrStdout(), cmd.ErrOrStderr())
+	}
+
+	dependencies, err := o.processDependencies(args)
+	if err != nil {
+		return err
+	}
+	propertyFiles, err := o.processPropertyFiles()
+	if err != nil {
+		return err
+	}
+	routes, err := o.processRoutes(args)
+	if err != nil {
+		return err
+	}
+
+	if o.Containerize {
+		// Create, build, and run the container image.
+		if err := local.CreateAndBuildIntegrationImage(o.Context, "", false, o.Image,
+			propertyFiles, dependencies, routes, o.IntegrationDirectory != "",
+			cmd.OutOrStdout(), cmd.ErrOrStderr()); err != nil {
 			return err
 		}
 
-		return nil
+		return local.RunIntegrationImage(o.Context, o.Image, cmd.OutOrStdout(), cmd.ErrOrStderr())
 	}
 
-	hasIntegrationDir := command.IntegrationDirectory != ""
+	// Run integration locally.
+	return local.RunLocalIntegration(o.Context, propertyFiles, dependencies, routes, o.getPropertiesDir(),
+		cmd.OutOrStdout(), cmd.ErrOrStderr())
+}
 
-	var dependencies []string
-	if hasIntegrationDir {
-		// Fetch local dependencies
-		localBuildDependencies, err := getLocalBuildDependencies(command.IntegrationDirectory)
-		if err != nil {
-			return err
-		}
-		dependencies = localBuildDependencies
+func (o *localRunCmdOptions) processDependencies(args []string) ([]string, error) {
+	if o.IntegrationDirectory == "" {
+		return local.GetDependencies(o.Context, args, o.Dependencies, o.MavenRepositories, true)
+	}
 
-		// Local dependencies directory
-		localDependenciesDirectory := getCustomDependenciesDir(command.IntegrationDirectory)
+	// Set up on the integration directory
 
-		// The quarkus application files need to be at a specific location i.e.:
-		// <integration_directory>/../quarkus/quarkus-application.dat
-		// <integration_directory>/../quarkus/generated-bytecode.jar
-		localQuarkusDir := getCustomQuarkusDir(command.IntegrationDirectory)
-		err = util.CopyQuarkusAppFiles(localDependenciesDirectory, localQuarkusDir)
-		if err != nil {
-			return err
-		}
+	// Fetch local dependencies
+	dependencies, err := local.GetBuildDependencies(o.IntegrationDirectory)
+	if err != nil {
+		return nil, err
+	}
+	if err := o.setupDependenciesForQuarkusRun(); err != nil {
+		return dependencies, err
+	}
 
-		// The dependency jar files need to be at a specific location i.e.:
-		// <integration_directory>/../lib/main/*.jar
-		localLibDirectory := getCustomLibDir(command.IntegrationDirectory)
-		err = util.CopyLibFiles(localDependenciesDirectory, localLibDirectory)
-		if err != nil {
-			return err
-		}
+	return dependencies, nil
+}
 
-		// The Camel K jar file needs to be at a specific location i.e.:
-		// <integration_directory>/../app/camel-k-integration-X.X.X{-SNAPSHOT}.jar
-		localAppDirectory := getCustomAppDir(command.IntegrationDirectory)
-		err = util.CopyAppFile(localDependenciesDirectory, localAppDirectory)
-		if err != nil {
-			return err
-		}
-	} else {
-		computedDependencies, err := GetDependencies(command.Context, args, command.Dependencies, command.MavenRepositories, true)
-		if err != nil {
-			return err
-		}
-		dependencies = computedDependencies
+// setupDependenciesForQuarkusRun sets up resources under the integration directory for running Quarkus app.
+func (o *localRunCmdOptions) setupDependenciesForQuarkusRun() error {
+	// Local dependencies directory
+	localDependenciesDir := local.GetCustomDependenciesDir(o.IntegrationDirectory)
+
+	// The quarkus application files need to be at a specific location i.e.:
+	// <integration_directory>/../quarkus/quarkus-application.dat
+	// <integration_directory>/../quarkus/generated-bytecode.jar
+	localQuarkusDir := local.GetCustomQuarkusDir(o.IntegrationDirectory)
+	if err := local.CopyQuarkusAppFiles(localDependenciesDir, localQuarkusDir); err != nil {
+		return err
 	}
 
-	// Manage integration properties which may come from files or CLI.
-	propertyFiles := command.PropertyFiles
-	if hasIntegrationDir {
-		localBuildPropertyFiles, err := getLocalBuildProperties(command.IntegrationDirectory)
-		if err != nil {
-			return err
-		}
-		propertyFiles = localBuildPropertyFiles
+	// The dependency jar files need to be at a specific location i.e.:
+	// <integration_directory>/../lib/main/*.jar
+	localLibDir := local.GetCustomLibDir(o.IntegrationDirectory)
+	if err := local.CopyLibFiles(localDependenciesDir, localLibDir); err != nil {
+		return err
 	}
 
-	updatedPropertyFiles, err := updateIntegrationProperties(command.Properties, propertyFiles, hasIntegrationDir)
-	if err != nil {
+	// The Camel K jar file needs to be at a specific location i.e.:
+	// <integration_directory>/../app/camel-k-integration-X.X.X{-SNAPSHOT}.jar
+	localAppDir := local.GetCustomAppDir(o.IntegrationDirectory)
+	if err := local.CopyAppFile(localDependenciesDir, localAppDir); err != nil {
 		return err
 	}
-	propertyFiles = updatedPropertyFiles
 
-	routes := args
+	return nil
+}
+
+// processPropertyFiles processes integration properties which may come from files or CLI.
+func (o *localRunCmdOptions) processPropertyFiles() ([]string, error) {
+	propertyFiles := o.PropertyFiles
+	hasIntegrationDir := o.IntegrationDirectory != ""
 	if hasIntegrationDir {
-		localBuildRoutes, err := getLocalBuildRoutes(command.IntegrationDirectory)
+		localPropertyFiles, err := local.GetBuildProperties(o.IntegrationDirectory)
 		if err != nil {
-			return err
+			return nil, err
 		}
-		routes = localBuildRoutes
+		propertyFiles = localPropertyFiles
 	}
 
-	// If this is a containerized local run, create, build and run the container image.
-	if command.Containerize {
-		// Create and build integration image.
-		err := createAndBuildIntegrationImage(command.Context, "", false, command.Image, propertyFiles, dependencies, routes, hasIntegrationDir, cmd.OutOrStdout(), cmd.ErrOrStderr())
-		if err != nil {
-			return err
-		}
+	return local.UpdateIntegrationProperties(o.Properties, propertyFiles, hasIntegrationDir)
+}
 
-		// Run integration image.
-		err = runIntegrationImage(command.Context, command.Image, cmd.OutOrStdout(), cmd.ErrOrStderr())
-		if err != nil {
-			return err
-		}
-	} else {
-		propertiesDir := util.GetLocalPropertiesDir()
-		if hasIntegrationDir {
-			propertiesDir = getCustomPropertiesDir(command.IntegrationDirectory)
-		}
+func (o *localRunCmdOptions) processRoutes(args []string) ([]string, error) {
+	if o.IntegrationDirectory == "" {
+		return args, nil
+	}
 
-		// Run integration locally.
-		err := RunLocalIntegrationRunCommand(command.Context, propertyFiles, dependencies, routes, propertiesDir, cmd.OutOrStdout(), cmd.ErrOrStderr())
-		if err != nil {
-			return err
-		}
+	return local.GetBuildRoutes(o.IntegrationDirectory)
+}
+
+func (o *localRunCmdOptions) getPropertiesDir() string {
+	if o.IntegrationDirectory == "" {
+		return local.GetLocalPropertiesDir()
 	}
 
-	return nil
+	return local.GetCustomPropertiesDir(o.IntegrationDirectory)
 }
 
-func (command *localRunCmdOptions) deinit() error {
-	if command.Containerize {
-		err := deleteDockerBaseWorkingDirectory()
-		if err != nil {
+func (o *localRunCmdOptions) deinit() error {
+	if o.Containerize {
+		if err := local.DeleteDockerBaseWorkingDirectory(); err != nil {
 			return err
 		}
 
-		err = deleteDockerWorkingDirectory()
-		if err != nil {
+		if err := local.DeleteDockerWorkingDirectory(); err != nil {
 			return err
 		}
 	}
 
-	if command.IntegrationDirectory != "" {
-		err := deleteLocalIntegrationDirs(command.IntegrationDirectory)
-		if err != nil {
+	if o.IntegrationDirectory != "" {
+		if err := local.DeleteLocalIntegrationDirs(o.IntegrationDirectory); err != nil {
 			return err
 		}
 	}
 
-	return deleteMavenWorkingDirectory()
+	return local.DeleteMavenWorkingDirectory()
 }
diff --git a/pkg/cmd/modeline.go b/pkg/cmd/modeline.go
index 07e88155b..4ef02caa2 100644
--- a/pkg/cmd/modeline.go
+++ b/pkg/cmd/modeline.go
@@ -24,6 +24,7 @@ import (
 	"path/filepath"
 	"strings"
 
+	"github.com/apache/camel-k/pkg/cmd/source"
 	"github.com/apache/camel-k/pkg/util"
 	"github.com/apache/camel-k/pkg/util/modeline"
 	"github.com/pkg/errors"
@@ -194,7 +195,7 @@ func createKamelWithModelineCommand(ctx context.Context, args []string) (*cobra.
 func extractModelineOptions(ctx context.Context, sources []string, cmd *cobra.Command) ([]modeline.Option, error) {
 	opts := make([]modeline.Option, 0)
 
-	resolvedSources, err := ResolveSources(ctx, sources, false, cmd)
+	resolvedSources, err := source.Resolve(ctx, sources, false, cmd)
 	if err != nil {
 		return opts, errors.Wrap(err, "failed to resolve sources")
 	}
@@ -216,7 +217,7 @@ func extractModelineOptions(ctx context.Context, sources []string, cmd *cobra.Co
 	return opts, nil
 }
 
-func extractModelineOptionsFromSource(resolvedSource Source) ([]modeline.Option, error) {
+func extractModelineOptionsFromSource(resolvedSource source.Source) ([]modeline.Option, error) {
 	ops, err := modeline.Parse(resolvedSource.Name, resolvedSource.Content)
 	if err != nil {
 		return ops, errors.Wrapf(err, "cannot process file %s", resolvedSource.Location)
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index e84a628e6..a3282e1f7 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -61,6 +61,8 @@ import (
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/client"
+	"github.com/apache/camel-k/pkg/cmd/local"
+	"github.com/apache/camel-k/pkg/cmd/source"
 	platformutil "github.com/apache/camel-k/pkg/platform"
 	"github.com/apache/camel-k/pkg/trait"
 	"github.com/apache/camel-k/pkg/util"
@@ -238,7 +240,7 @@ func (o *runCmdOptions) validateArgs(cmd *cobra.Command, args []string) error {
 		return errors.New("run expects at least 1 argument, received 0")
 	}
 
-	if _, err := ResolveSources(context.Background(), args, false, cmd); err != nil {
+	if _, err := source.Resolve(context.Background(), args, false, cmd); err != nil {
 		return errors.Wrap(err, "One of the provided sources is not reachable")
 	}
 
@@ -259,7 +261,7 @@ func (o *runCmdOptions) validate() error {
 
 	propertyFiles := filterBuildPropertyFiles(o.Properties)
 	propertyFiles = append(propertyFiles, filterBuildPropertyFiles(o.BuildProperties)...)
-	err := validatePropertyFiles(propertyFiles)
+	err := local.ValidatePropertyFiles(propertyFiles)
 	if err != nil {
 		return err
 	}
@@ -441,7 +443,7 @@ func (o *runCmdOptions) syncIntegration(cmd *cobra.Command, c client.Client, sou
 	files = append(files, filterFileLocation(o.OpenAPIs)...)
 
 	for _, s := range files {
-		ok, err := isLocalAndFileExists(s)
+		ok, err := source.IsLocalAndFileExists(s)
 		if err != nil {
 			return err
 		}
@@ -578,7 +580,7 @@ func (o *runCmdOptions) createOrUpdateIntegration(cmd *cobra.Command, c client.C
 	srcs = append(srcs, sources...)
 	srcs = append(srcs, o.Sources...)
 
-	resolvedSources, err := ResolveSources(context.Background(), srcs, o.Compression, cmd)
+	resolvedSources, err := source.Resolve(context.Background(), srcs, o.Compression, cmd)
 	if err != nil {
 		return nil, err
 	}
@@ -801,7 +803,7 @@ func resolvePodTemplate(ctx context.Context, cmd *cobra.Command, templateSrc str
 
 	// check if value is a path to the file
 	if _, err := os.Stat(templateSrc); err == nil {
-		rsc, err := ResolveSources(ctx, []string{templateSrc}, false, cmd)
+		rsc, err := source.Resolve(ctx, []string{templateSrc}, false, cmd)
 		if err == nil && len(rsc) > 0 {
 			templateSrc = rsc[0].Content
 		}
diff --git a/pkg/cmd/run_help.go b/pkg/cmd/run_help.go
index 1f868292d..c885e4151 100644
--- a/pkg/cmd/run_help.go
+++ b/pkg/cmd/run_help.go
@@ -25,6 +25,7 @@ import (
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/client"
+	"github.com/apache/camel-k/pkg/cmd/source"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
 	"github.com/apache/camel-k/pkg/util/resource"
 	"github.com/magiconair/properties"
@@ -48,11 +49,11 @@ func parseConfigAndGenCm(ctx context.Context, cmd *cobra.Command, c client.Clien
 		}
 	case resource.StorageTypeFile:
 		// Don't allow a binary non compressed resource
-		rawData, contentType, err := loadRawContent(ctx, config.Name())
+		rawData, contentType, err := source.LoadRawContent(ctx, config.Name())
 		if err != nil {
 			return nil, err
 		}
-		if config.ContentType() != resource.ContentTypeData && !enableCompression && isBinary(contentType) {
+		if config.ContentType() != resource.ContentTypeData && !enableCompression && source.IsBinary(contentType) {
 			return nil, fmt.Errorf("you cannot provide a binary config, use a text file or check --resource flag instead")
 		}
 		resourceType := v1.ResourceTypeData
@@ -85,13 +86,13 @@ func binaryOrTextResource(fileName string, data []byte, contentType string, base
 		Type: resourceType,
 	}
 
-	if !base64Compression && isBinary(contentType) {
+	if !base64Compression && source.IsBinary(contentType) {
 		resourceSpec.RawContent = data
 		return resourceSpec, nil
 	}
 	// either is a text resource or base64 compression is enabled
 	if base64Compression {
-		content, err := compressToString(data)
+		content, err := source.CompressToString(data)
 		if err != nil {
 			return resourceSpec, err
 		}
diff --git a/pkg/cmd/util_content.go b/pkg/cmd/source/content.go
similarity index 85%
rename from pkg/cmd/util_content.go
rename to pkg/cmd/source/content.go
index 67e706aae..28e359fff 100644
--- a/pkg/cmd/util_content.go
+++ b/pkg/cmd/source/content.go
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package cmd
+package source
 
 import (
 	"context"
@@ -27,6 +27,7 @@ import (
 	"strings"
 
 	"github.com/apache/camel-k/pkg/util"
+	"github.com/apache/camel-k/pkg/util/gzip"
 )
 
 const (
@@ -36,11 +37,11 @@ const (
 	Kilobyte = 1 << 10
 )
 
-func loadRawContent(ctx context.Context, source string) ([]byte, string, error) {
+func LoadRawContent(ctx context.Context, source string) ([]byte, string, error) {
 	var content []byte
 	var err error
 
-	ok, err := isLocalAndFileExists(source)
+	ok, err := IsLocalAndFileExists(source)
 	if err != nil {
 		return nil, "", err
 	}
@@ -74,20 +75,29 @@ func loadRawContent(ctx context.Context, source string) ([]byte, string, error)
 	return content, contentType, nil
 }
 
-func isBinary(contentType string) bool {
+func IsBinary(contentType string) bool {
 	// According the http.DetectContentType method
 	// also json and other "text" application mime types would be reported as text
 	return !strings.HasPrefix(contentType, "text")
 }
 
-func loadTextContent(ctx context.Context, source string, base64Compression bool) (string, string, bool, error) {
-	content, contentType, err := loadRawContent(ctx, source)
+func CompressToString(content []byte) (string, error) {
+	bytes, err := gzip.CompressBase64(content)
+	if err != nil {
+		return "", err
+	}
+
+	return string(bytes), nil
+}
+
+func LoadTextContent(ctx context.Context, source string, base64Compression bool) (string, string, bool, error) {
+	content, contentType, err := LoadRawContent(ctx, source)
 	if err != nil {
 		return "", "", false, err
 	}
 
 	if base64Compression {
-		base64Compressed, err := compressToString(content)
+		base64Compressed, err := CompressToString(content)
 		return base64Compressed, contentType, true, err
 	}
 
diff --git a/pkg/cmd/util_sources.go b/pkg/cmd/source/source.go
similarity index 77%
rename from pkg/cmd/util_sources.go
rename to pkg/cmd/source/source.go
index b15e66912..7af7f3600 100644
--- a/pkg/cmd/util_sources.go
+++ b/pkg/cmd/source/source.go
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package cmd
+package source
 
 import (
 	"context"
@@ -45,9 +45,34 @@ type Source struct {
 	Local    bool
 }
 
+// newSource creates a source using the content provider function.
+func newSource(location string, compress bool, loadContent func() ([]byte, error)) (Source, error) {
+	// strip query part from location if any
+	locPath := util.SubstringBefore(location, "?")
+	if locPath == "" {
+		locPath = location
+	}
+	src := Source{
+		Name:     path.Base(locPath),
+		Origin:   location,
+		Location: location,
+		Compress: compress,
+	}
+
+	content, err := loadContent()
+	if err != nil {
+		return Source{}, err
+	}
+	if err := src.setContent(content); err != nil {
+		return Source{}, err
+	}
+
+	return src, nil
+}
+
 func (s *Source) setContent(content []byte) error {
 	if s.Compress {
-		result, err := compressToString(content)
+		result, err := CompressToString(content)
 		if err != nil {
 			return err
 		}
@@ -60,18 +85,18 @@ func (s *Source) setContent(content []byte) error {
 	return nil
 }
 
-// ResolveSources resolves sources from a variety of locations including local and remote.
-func ResolveSources(ctx context.Context, locations []string, compress bool, cmd *cobra.Command) ([]Source, error) {
+// Resolve resolves sources from a variety of locations including local and remote.
+func Resolve(ctx context.Context, locations []string, compress bool, cmd *cobra.Command) ([]Source, error) {
 	sources := make([]Source, 0, len(locations))
 
 	for _, location := range locations {
-		ok, err := isLocalAndFileExists(location)
+		ok, err := IsLocalAndFileExists(location)
 		if err != nil {
 			return sources, err
 		}
 
 		if ok {
-			answer, err := resolveLocalSource(location, compress)
+			answer, err := resolveLocal(location, compress)
 			if err != nil {
 				return sources, err
 			}
@@ -85,13 +110,13 @@ func ResolveSources(ctx context.Context, locations []string, compress bool, cmd
 
 			switch {
 			case u.Scheme == gistScheme || strings.HasPrefix(location, "https://gist.github.com/"):
-				answer, err := resolveGistSource(ctx, location, compress, cmd, u)
+				answer, err := resolveGist(ctx, location, compress, cmd, u)
 				if err != nil {
 					return sources, err
 				}
 				sources = append(sources, answer...)
 			case u.Scheme == githubScheme:
-				answer, err := resolveSource(location, compress, func() ([]byte, error) {
+				answer, err := newSource(location, compress, func() ([]byte, error) {
 					return loadContentGitHub(ctx, u)
 				})
 				if err != nil {
@@ -99,7 +124,7 @@ func ResolveSources(ctx context.Context, locations []string, compress bool, cmd
 				}
 				sources = append(sources, answer)
 			case u.Scheme == httpScheme || u.Scheme == httpsScheme:
-				answer, err := resolveSource(location, compress, func() ([]byte, error) {
+				answer, err := newSource(location, compress, func() ([]byte, error) {
 					return loadContentHTTP(ctx, u)
 				})
 				if err != nil {
@@ -115,8 +140,8 @@ func ResolveSources(ctx context.Context, locations []string, compress bool, cmd
 	return sources, nil
 }
 
-// resolveGistSource resolves sources from a Gist.
-func resolveGistSource(ctx context.Context, location string, compress bool, cmd *cobra.Command, u *url.URL) ([]Source, error) {
+// resolveGist resolves sources from a Gist.
+func resolveGist(ctx context.Context, location string, compress bool, cmd *cobra.Command, u *url.URL) ([]Source, error) {
 	var hc *http.Client
 
 	if token, ok := os.LookupEnv("GITHUB_TOKEN"); ok {
@@ -170,15 +195,15 @@ func resolveGistSource(ctx context.Context, location string, compress bool, cmd
 	return sources, nil
 }
 
-// resolveLocalSource resolves a source from the local file system.
-func resolveLocalSource(location string, compress bool) (Source, error) {
+// resolveLocal resolves a source from the local file system.
+func resolveLocal(location string, compress bool) (Source, error) {
 	if _, err := os.Stat(location); err != nil && os.IsNotExist(err) {
 		return Source{}, errors.Wrapf(err, "file %s does not exist", location)
 	} else if err != nil {
 		return Source{}, errors.Wrapf(err, "error while accessing file %s", location)
 	}
 
-	answer, err := resolveSource(location, compress, func() ([]byte, error) {
+	answer, err := newSource(location, compress, func() ([]byte, error) {
 		return util.ReadFile(location)
 	})
 	if err != nil {
@@ -188,28 +213,3 @@ func resolveLocalSource(location string, compress bool) (Source, error) {
 
 	return answer, nil
 }
-
-// resolveSource resolves a source using the content provider function.
-func resolveSource(location string, compress bool, loadContent func() ([]byte, error)) (Source, error) {
-	// strip query part from location if any
-	locPath := util.SubstringBefore(location, "?")
-	if locPath == "" {
-		locPath = location
-	}
-	answer := Source{
-		Name:     path.Base(locPath),
-		Origin:   location,
-		Location: location,
-		Compress: compress,
-	}
-
-	content, err := loadContent()
-	if err != nil {
-		return Source{}, err
-	}
-	if err := answer.setContent(content); err != nil {
-		return Source{}, err
-	}
-
-	return answer, nil
-}
diff --git a/pkg/cmd/source/util.go b/pkg/cmd/source/util.go
new file mode 100644
index 000000000..3e2b6a787
--- /dev/null
+++ b/pkg/cmd/source/util.go
@@ -0,0 +1,62 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package source
+
+import (
+	"fmt"
+	"os"
+	"strings"
+
+	"github.com/pkg/errors"
+)
+
+const (
+	// Supported source schemes.
+	gistScheme   = "gist"
+	githubScheme = "github"
+	httpScheme   = "http"
+	httpsScheme  = "https"
+)
+
+func IsLocalAndFileExists(uri string) (bool, error) {
+	if hasSupportedScheme(uri) {
+		// it's not a local file as it matches one of the supporting schemes
+		return false, nil
+	}
+	info, err := os.Stat(uri)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return false, nil
+		}
+		// If it is a different error (ie, permission denied) we should report it back
+		return false, errors.Wrap(err, fmt.Sprintf("file system error while looking for %s", uri))
+	}
+
+	return !info.IsDir(), nil
+}
+
+func hasSupportedScheme(uri string) bool {
+	if strings.HasPrefix(strings.ToLower(uri), gistScheme+":") ||
+		strings.HasPrefix(strings.ToLower(uri), githubScheme+":") ||
+		strings.HasPrefix(strings.ToLower(uri), httpScheme+":") ||
+		strings.HasPrefix(strings.ToLower(uri), httpsScheme+":") {
+		return true
+	}
+
+	return false
+}
diff --git a/pkg/cmd/util.go b/pkg/cmd/util.go
index 1dd67136a..1736037f2 100644
--- a/pkg/cmd/util.go
+++ b/pkg/cmd/util.go
@@ -24,17 +24,14 @@ import (
 	"fmt"
 	"io"
 	"log"
-	"os"
 	"reflect"
 	"strings"
 
 	"github.com/mitchellh/mapstructure"
-	"github.com/pkg/errors"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/client"
 	platformutil "github.com/apache/camel-k/pkg/platform"
-	"github.com/apache/camel-k/pkg/util/gzip"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 	"github.com/spf13/viper"
@@ -46,12 +43,6 @@ import (
 
 const (
 	offlineCommandLabel = "camel.apache.org/cmd.offline"
-
-	// Supported source schemes.
-	gistScheme   = "gist"
-	githubScheme = "github"
-	httpScheme   = "http"
-	httpsScheme  = "https"
 )
 
 // DeleteIntegration --.
@@ -250,42 +241,6 @@ func fieldByMapstructureTagName(target reflect.Value, tagName string) (reflect.S
 	return reflect.StructField{}, false
 }
 
-func compressToString(content []byte) (string, error) {
-	bytes, err := gzip.CompressBase64(content)
-	if err != nil {
-		return "", err
-	}
-
-	return string(bytes), nil
-}
-
-func isLocalAndFileExists(uri string) (bool, error) {
-	if hasSupportedScheme(uri) {
-		// it's not a local file as it matches one of the supporting schemes
-		return false, nil
-	}
-	info, err := os.Stat(uri)
-	if err != nil {
-		if os.IsNotExist(err) {
-			return false, nil
-		}
-		// If it is a different error (ie, permission denied) we should report it back
-		return false, errors.Wrap(err, fmt.Sprintf("file system error while looking for %s", uri))
-	}
-	return !info.IsDir(), nil
-}
-
-func hasSupportedScheme(uri string) bool {
-	if strings.HasPrefix(strings.ToLower(uri), gistScheme+":") ||
-		strings.HasPrefix(strings.ToLower(uri), githubScheme+":") ||
-		strings.HasPrefix(strings.ToLower(uri), httpScheme+":") ||
-		strings.HasPrefix(strings.ToLower(uri), httpsScheme+":") {
-		return true
-	}
-
-	return false
-}
-
 func verifyOperatorID(ctx context.Context, client client.Client, operatorID string, out io.Writer) error {
 	if pl, err := platformutil.LookupForPlatformName(ctx, client, operatorID); err != nil {
 		if k8serrors.IsForbidden(err) {
diff --git a/pkg/cmd/util_commands.go b/pkg/cmd/util_commands.go
deleted file mode 100644
index 7f0252a3f..000000000
--- a/pkg/cmd/util_commands.go
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one or more
-contributor license agreements.  See the NOTICE file distributed with
-this work for additional information regarding copyright ownership.
-The ASF licenses this file to You under the Apache License, Version 2.0
-(the "License"); you may not use this file except in compliance with
-the License.  You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package cmd
-
-import (
-	"context"
-	"fmt"
-	"io"
-	"os"
-	"os/exec"
-	"strings"
-
-	"github.com/apache/camel-k/pkg/util"
-	"github.com/apache/camel-k/pkg/util/docker"
-)
-
-func formatRoutes(files []string) []string {
-	routes := []string{}
-	for _, route := range files {
-		// Split route path.
-		a := strings.Split(route, ".")
-
-		// Extract extension.
-		extension := a[len(a)-1]
-
-		// Add file with extension.
-		routes = append(routes, "file:"+route+"?language="+extension)
-	}
-
-	return routes
-}
-
-func assembleClasspathArgValue(properties []string, dependencies []string, routes []string) string {
-	classpathContents := []string{}
-	classpathContents = append(classpathContents, properties...)
-	classpathContents = append(classpathContents, routes...)
-	classpathContents = append(classpathContents, dependencies...)
-	return strings.Join(classpathContents, string(os.PathListSeparator))
-}
-
-func assembleIntegrationRunCommand(ctx context.Context, properties []string, dependencies []string, routes []string, propertiesDir string, stdout, stderr io.Writer, local bool) (*exec.Cmd, error) {
-	// Create classpath value.
-	classpathValue := assembleClasspathArgValue(properties, dependencies, routes)
-
-	// Create java command that runs the integration.
-	javaCmd := "java"
-
-	// Create java command arguments.
-	args := make([]string, 0)
-	args = append(args, "-cp")
-	args = append(args, classpathValue)
-	args = append(args, "io.quarkus.bootstrap.runner.QuarkusEntryPoint")
-
-	cmd := exec.CommandContext(ctx, javaCmd, args...)
-
-	// Add directory where the properties files reside. The directory is the local properties directory
-	// or the properties directory inside the container.
-	cmd.Env = append(cmd.Env, "CAMEL_K_CONF_D="+propertiesDir)
-
-	// Add files to the command line under the CAMEL_K_ROUTES flag.
-	cmd.Env = append(cmd.Env, "CAMEL_K_ROUTES="+strings.Join(formatRoutes(routes), ","))
-
-	// Add any lazily evaluated environment variables.
-	if local {
-		// If we are running locally then this is as late as we can evaluate the
-		// lazy environment variables since we are going to run the command
-		// immediately after the generation of these arguments.
-		setEnvVars, err := util.EvaluateCLIAndLazyEnvVars()
-		if err != nil {
-			return nil, err
-		}
-		cmd.Env = append(cmd.Env, setEnvVars...)
-	} else {
-		// If we are running in containerized or just building an image, we should
-		// not evaluate the variables at this point since we are only generating the
-		// run command and not actually running it.
-		for _, lazyEnvVar := range util.ListOfLazyEvaluatedEnvVars {
-			cmd.Env = append(cmd.Env, lazyEnvVar+"={{env:"+lazyEnvVar+"}}")
-		}
-	}
-
-	// Set stdout and stderr.
-	cmd.Stderr = stderr
-	cmd.Stdout = stdout
-
-	return cmd, nil
-}
-
-// RunLocalIntegrationRunCommand --.
-func RunLocalIntegrationRunCommand(ctx context.Context, properties []string, dependencies []string, routes []string, propertiesDir string, stdout, stderr io.Writer) error {
-	cmd, err := assembleIntegrationRunCommand(ctx, properties, dependencies, routes, propertiesDir, stdout, stderr, true)
-	if err != nil {
-		return err
-	}
-
-	// Output command we are about to run.
-	fmt.Fprintln(cmd.Stdout, "Executing:", strings.Join(cmd.Args, " "))
-
-	// Run integration locally.
-	err = cmd.Run()
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// GetContainerIntegrationRunCommand --.
-func GetContainerIntegrationRunCommand(ctx context.Context, properties []string, dependencies []string, routes []string, stdout, stderr io.Writer) (*exec.Cmd, error) {
-	// This is the integration command which will be run inside the container. Therefore all paths need to
-	// be valid container paths.
-
-	// Update property file paths.
-	containerProperties := docker.ContainerizeFilePaths(properties, docker.GetContainerPropertiesDir())
-	containerDependencies := docker.ContainerizeDependencyPaths(dependencies, docker.GetContainerDependenciesDir())
-	containerRoutes := docker.ContainerizeFilePaths(routes, docker.GetContainerRoutesDir())
-
-	return assembleIntegrationRunCommand(ctx, containerProperties, containerDependencies, containerRoutes, docker.GetContainerPropertiesDir(), stdout, stderr, false)
-}
diff --git a/pkg/cmd/util_containerization.go b/pkg/cmd/util_containerization.go
deleted file mode 100644
index ec1c2e932..000000000
--- a/pkg/cmd/util_containerization.go
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one or more
-contributor license agreements.  See the NOTICE file distributed with
-this work for additional information regarding copyright ownership.
-The ASF licenses this file to You under the Apache License, Version 2.0
-(the "License"); you may not use this file except in compliance with
-the License.  You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package cmd
-
-import (
-	"context"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"os"
-	"os/exec"
-	"os/signal"
-	"strings"
-	"syscall"
-
-	"github.com/apache/camel-k/pkg/util"
-	"github.com/apache/camel-k/pkg/util/docker"
-	"github.com/pkg/errors"
-)
-
-// Local Docker file system management functions.
-
-func createDockerBaseWorkingDirectory() error {
-	// Create local docker base directory.
-	temporaryDirectory, err := ioutil.TempDir(os.TempDir(), "docker-base-")
-	if err != nil {
-		return err
-	}
-
-	// Set the Docker base directory to the default value.
-	docker.BaseWorkingDirectory = temporaryDirectory
-
-	return nil
-}
-
-func deleteDockerBaseWorkingDirectory() error {
-	// Remove directory used for computing the dependencies.
-	return os.RemoveAll(docker.BaseWorkingDirectory)
-}
-
-func createDockerWorkingDirectory() error {
-	// Create local docker base directory.
-	temporaryDirectory, err := ioutil.TempDir(os.TempDir(), "docker-")
-	if err != nil {
-		return err
-	}
-
-	// Set the Docker base directory to the default value.
-	docker.IntegrationWorkingDirectory = temporaryDirectory
-
-	return nil
-}
-
-func deleteDockerWorkingDirectory() error {
-	// Remove directory used for computing the dependencies.
-	return os.RemoveAll(docker.IntegrationWorkingDirectory)
-}
-
-func setDockerNetworkName(networkName string) {
-	if networkName != "" {
-		docker.NetworkName = networkName
-	}
-}
-
-func setDockerEnvVars(envVars []string) {
-	if len(envVars) > 0 {
-		util.CLIEnvVars = envVars
-	}
-}
-
-func createAndBuildBaseImage(ctx context.Context, stdout, stderr io.Writer) error {
-	// Create the base image Docker file.
-	err := docker.CreateBaseImageDockerFile()
-	if err != nil {
-		return err
-	}
-
-	// Get the Docker command arguments for building the base image and create the command.
-	args := docker.BuildBaseImageArgs()
-	cmd := exec.CommandContext(ctx, "docker", args...)
-
-	// Set stdout and stderr.
-	cmd.Stdout = stdout
-	cmd.Stderr = stderr
-
-	// Output executed command.
-	fmt.Fprintln(cmd.Stdout, "Executing:", strings.Join(cmd.Args, " "))
-
-	// Run the command.
-	if err := cmd.Run(); err != nil {
-		return errors.Errorf("base image containerization did not run successfully: %v", err)
-	}
-
-	return nil
-}
-
-func createAndBuildIntegrationImage(ctx context.Context, containerRegistry string, justBaseImage bool, image string,
-	propertyFiles []string, dependencies []string, routes []string, startsFromLocalFolder bool, stdout, stderr io.Writer) error {
-	// This ensures the Dockerfile for the base image will not end up in an undesired location.
-	if docker.BaseWorkingDirectory == "" {
-		return errors.New("base directory that holds the base image Dockerfile has not been set correctly")
-	}
-
-	docker.RegistryName = containerRegistry
-
-	// If we build a normal image, i.e. not the base image, we need to parse
-	// the location where images will be pushed.
-	if !justBaseImage {
-		registryName, err := docker.ExtractRegistryName(image)
-		if err != nil {
-			return err
-		}
-
-		docker.RegistryName = registryName
-	}
-
-	// Create the Dockerfile and build the base image.
-	err := createAndBuildBaseImage(ctx, stdout, stderr)
-	if err != nil {
-		return err
-	}
-
-	if justBaseImage {
-		return nil
-	}
-
-	if docker.IntegrationWorkingDirectory == "" {
-		return errors.New("integration directory that holds the image Dockerfile has not been set correctly")
-	}
-
-	// Create integration image if integration files were provided.
-	// Copy dependencies to a dependencies folder under a local directory.
-	err = updateIntegrationDependencies(dependencies)
-	if err != nil {
-		return err
-	}
-
-	// Copy routes to a routes folder under a local directory.
-	err = updateIntegrationRoutes(routes)
-	if err != nil {
-		return err
-	}
-
-	// Copy quarkus files in maven subdirectory
-	err = updateQuarkusDirectory()
-	if err != nil {
-		return err
-	}
-
-	// Copy app files in maven subdirectory
-	err = updateAppDirectory()
-	if err != nil {
-		return err
-	}
-
-	// Copy lib files in maven subdirectory
-	err = updateLibDirectory()
-	if err != nil {
-		return err
-	}
-
-	// Get integration run command to be run inside the container. This means the command
-	// has to be created with the paths which will be valid inside the container.
-	containerCmd, err := GetContainerIntegrationRunCommand(ctx, propertyFiles, dependencies, routes, stdout, stderr)
-	if err != nil {
-		return err
-	}
-
-	// Create the integration image Docker file.
-	err = docker.CreateIntegrationImageDockerFile(containerCmd, startsFromLocalFolder)
-	if err != nil {
-		return err
-	}
-
-	// Get the Docker command arguments for building the base image and create the command.
-	args := docker.BuildIntegrationImageArgs(image)
-	cmd := exec.CommandContext(ctx, "docker", args...)
-
-	// Set stdout and stderr.
-	cmd.Stderr = stderr
-	cmd.Stdout = stdout
-
-	// Output executed command.
-	fmt.Fprintln(cmd.Stdout, "Executing:", strings.Join(cmd.Args, " "))
-
-	// Run the command.
-	if err := cmd.Run(); err != nil {
-		return errors.Errorf("integration image containerization did not run successfully: %v", err)
-	}
-
-	return nil
-}
-
-func runIntegrationImage(ctx context.Context, image string, stdout, stderr io.Writer) error {
-	// Stop the child process before exiting
-	dockerCtx, cancel := context.WithCancel(ctx)
-	cs := make(chan os.Signal, 1)
-	signal.Notify(cs, os.Interrupt, syscall.SIGTERM)
-	go func() {
-		<-cs
-		cancel()
-	}()
-
-	// Get the docker command line argument for running an image.
-	args, err := docker.RunIntegrationImageArgs(image)
-	if err != nil {
-		return err
-	}
-
-	cmd := exec.CommandContext(dockerCtx, "docker", args...)
-
-	// Set stdout and stderr.
-	cmd.Stderr = stderr
-	cmd.Stdout = stdout
-
-	// Output executed command.
-	fmt.Fprintln(cmd.Stdout, "Executing:", strings.Join(cmd.Args, " "))
-
-	// Run the command.
-	if err := cmd.Run(); err != nil {
-		return errors.Errorf("integration image did not run successfully: %v", err)
-	}
-
-	return nil
-}
diff --git a/pkg/controller/integration/integration_controller.go b/pkg/controller/integration/integration_controller.go
index c17a1babf..bd9a19937 100644
--- a/pkg/controller/integration/integration_controller.go
+++ b/pkg/controller/integration/integration_controller.go
@@ -23,6 +23,8 @@ import (
 	"reflect"
 	"time"
 
+	"github.com/apache/camel-k/pkg/trait"
+
 	appsv1 "k8s.io/api/apps/v1"
 	batchv1 "k8s.io/api/batch/v1"
 	corev1 "k8s.io/api/core/v1"
@@ -75,6 +77,115 @@ func newReconciler(mgr manager.Manager, c client.Client) reconcile.Reconciler {
 	)
 }
 
+func integrationUpdateFunc(old *v1.Integration, it *v1.Integration) bool {
+	// Observe the time to first readiness metric
+	previous := old.Status.GetCondition(v1.IntegrationConditionReady)
+	if next := it.Status.GetCondition(v1.IntegrationConditionReady); (previous == nil || previous.Status != corev1.ConditionTrue && (previous.FirstTruthyTime == nil || previous.FirstTruthyTime.IsZero())) &&
+		next != nil && next.Status == corev1.ConditionTrue && next.FirstTruthyTime != nil && !next.FirstTruthyTime.IsZero() &&
+		it.Status.InitializationTimestamp != nil {
+		duration := next.FirstTruthyTime.Time.Sub(it.Status.InitializationTimestamp.Time)
+		Log.WithValues("request-namespace", it.Namespace, "request-name", it.Name, "ready-after", duration.Seconds()).
+			ForIntegration(it).Infof("First readiness after %s", duration)
+		timeToFirstReadiness.Observe(duration.Seconds())
+	}
+
+	// If traits have changed, the reconciliation loop must kick in as
+	// traits may have impact
+	sameTraits, err := trait.IntegrationsHaveSameTraits(old, it)
+	if err != nil {
+		Log.ForIntegration(it).Error(
+			err,
+			"unable to determine if old and new resource have the same traits")
+	}
+	if !sameTraits {
+		return true
+	}
+
+	// Ignore updates to the integration status in which case metadata.Generation does not change,
+	// or except when the integration phase changes as it's used to transition from one phase
+	// to another.
+	return old.Generation != it.Generation ||
+		old.Status.Phase != it.Status.Phase
+}
+
+func integrationKitEnqueueRequestsFromMapFunc(c client.Client, kit *v1.IntegrationKit) []reconcile.Request {
+	var requests []reconcile.Request
+	if kit.Status.Phase != v1.IntegrationKitPhaseReady && kit.Status.Phase != v1.IntegrationKitPhaseError {
+		return requests
+	}
+
+	list := &v1.IntegrationList{}
+	// Do global search in case of global operator (it may be using a global platform)
+	var opts []ctrl.ListOption
+	if !platform.IsCurrentOperatorGlobal() {
+		opts = append(opts, ctrl.InNamespace(kit.Namespace))
+	}
+	if err := c.List(context.Background(), list, opts...); err != nil {
+		log.Error(err, "Failed to retrieve integration list")
+		return requests
+	}
+
+	for i := range list.Items {
+		integration := &list.Items[i]
+		Log.Debug("Integration Controller: Assessing integration", "integration", integration.Name, "namespace", integration.Namespace)
+
+		match, err := sameOrMatch(kit, integration)
+		if err != nil {
+			Log.ForIntegration(integration).Errorf(err, "Error matching integration %q with kit %q", integration.Name, kit.Name)
+			continue
+		}
+		if !match {
+			continue
+		}
+
+		if integration.Status.Phase == v1.IntegrationPhaseBuildingKit ||
+			integration.Status.Phase == v1.IntegrationPhaseRunning {
+			log.Infof("Kit %s ready, notify integration: %s", kit.Name, integration.Name)
+			requests = append(requests, reconcile.Request{
+				NamespacedName: types.NamespacedName{
+					Namespace: integration.Namespace,
+					Name:      integration.Name,
+				},
+			})
+		}
+	}
+
+	return requests
+}
+
+func integrationPlatformEnqueueRequestsFromMapFunc(c client.Client, p *v1.IntegrationPlatform) []reconcile.Request {
+	var requests []reconcile.Request
+
+	if p.Status.Phase == v1.IntegrationPlatformPhaseReady {
+		list := &v1.IntegrationList{}
+
+		// Do global search in case of global operator (it may be using a global platform)
+		var opts []ctrl.ListOption
+		if !platform.IsCurrentOperatorGlobal() {
+			opts = append(opts, ctrl.InNamespace(p.Namespace))
+		}
+
+		if err := c.List(context.Background(), list, opts...); err != nil {
+			log.Error(err, "Failed to list integrations")
+			return requests
+		}
+
+		for _, integration := range list.Items {
+			if integration.Status.Phase == v1.IntegrationPhaseWaitingForPlatform {
+				log.Infof("Platform %s ready, wake-up integration: %s", p.Name, integration.Name)
+				requests = append(requests, reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Namespace: integration.Namespace,
+						Name:      integration.Name,
+					},
+				})
+			}
+		}
+	}
+
+	return requests
+}
+
 func add(mgr manager.Manager, c client.Client, r reconcile.Reconciler) error {
 	b := builder.ControllerManagedBy(mgr).
 		Named("integration-controller").
@@ -90,21 +201,8 @@ func add(mgr manager.Manager, c client.Client, r reconcile.Reconciler) error {
 					if !ok {
 						return false
 					}
-					// Observe the time to first readiness metric
-					previous := old.Status.GetCondition(v1.IntegrationConditionReady)
-					if next := it.Status.GetCondition(v1.IntegrationConditionReady); (previous == nil || previous.Status != corev1.ConditionTrue && (previous.FirstTruthyTime == nil || previous.FirstTruthyTime.IsZero())) &&
-						next != nil && next.Status == corev1.ConditionTrue && next.FirstTruthyTime != nil && !next.FirstTruthyTime.IsZero() &&
-						it.Status.InitializationTimestamp != nil {
-						duration := next.FirstTruthyTime.Time.Sub(it.Status.InitializationTimestamp.Time)
-						Log.WithValues("request-namespace", it.Namespace, "request-name", it.Name, "ready-after", duration.Seconds()).
-							ForIntegration(it).Infof("First readiness after %s", duration)
-						timeToFirstReadiness.Observe(duration.Seconds())
-					}
-					// Ignore updates to the integration status in which case metadata.Generation does not change,
-					// or except when the integration phase changes as it's used to transition from one phase
-					// to another.
-					return old.Generation != it.Generation ||
-						old.Status.Phase != it.Status.Phase
+
+					return integrationUpdateFunc(old, it)
 				},
 				DeleteFunc: func(e event.DeleteEvent) bool {
 					// Evaluates to false if the object has been confirmed deleted
@@ -116,92 +214,25 @@ func add(mgr manager.Manager, c client.Client, r reconcile.Reconciler) error {
 		// or running phase.
 		Watches(&source.Kind{Type: &v1.IntegrationKit{}},
 			handler.EnqueueRequestsFromMapFunc(func(a ctrl.Object) []reconcile.Request {
-				var requests []reconcile.Request
 				kit, ok := a.(*v1.IntegrationKit)
 				if !ok {
 					log.Error(fmt.Errorf("type assertion failed: %v", a), "Failed to retrieve integration list")
-					return requests
-				}
-
-				if kit.Status.Phase != v1.IntegrationKitPhaseReady && kit.Status.Phase != v1.IntegrationKitPhaseError {
-					return requests
-				}
-
-				list := &v1.IntegrationList{}
-				// Do global search in case of global operator (it may be using a global platform)
-				var opts []ctrl.ListOption
-				if !platform.IsCurrentOperatorGlobal() {
-					opts = append(opts, ctrl.InNamespace(kit.Namespace))
-				}
-				if err := c.List(context.Background(), list, opts...); err != nil {
-					log.Error(err, "Failed to retrieve integration list")
-					return requests
-				}
-
-				for i := range list.Items {
-					integration := &list.Items[i]
-					log.Debug("Integration Controller: Assessing integration", "integration", integration.Name, "namespace", integration.Namespace)
-
-					if match, err := integrationMatches(integration, kit); err != nil {
-						log.Errorf(err, "Error matching integration %q with kit %q", integration.Name, kit.Name)
-
-						continue
-					} else if !match {
-						continue
-					}
-					if integration.Status.Phase == v1.IntegrationPhaseBuildingKit ||
-						integration.Status.Phase == v1.IntegrationPhaseRunning {
-						log.Infof("Kit %s ready, notify integration: %s", kit.Name, integration.Name)
-						requests = append(requests, reconcile.Request{
-							NamespacedName: types.NamespacedName{
-								Namespace: integration.Namespace,
-								Name:      integration.Name,
-							},
-						})
-					}
+					return []reconcile.Request{}
 				}
 
-				return requests
+				return integrationKitEnqueueRequestsFromMapFunc(c, kit)
 			})).
 		// Watch for IntegrationPlatform phase transitioning to ready and enqueue
 		// requests for any integrations that are in phase waiting for platform
 		Watches(&source.Kind{Type: &v1.IntegrationPlatform{}},
 			handler.EnqueueRequestsFromMapFunc(func(a ctrl.Object) []reconcile.Request {
-				var requests []reconcile.Request
 				p, ok := a.(*v1.IntegrationPlatform)
 				if !ok {
 					log.Error(fmt.Errorf("type assertion failed: %v", a), "Failed to list integrations")
-					return requests
-				}
-
-				if p.Status.Phase == v1.IntegrationPlatformPhaseReady {
-					list := &v1.IntegrationList{}
-
-					// Do global search in case of global operator (it may be using a global platform)
-					var opts []ctrl.ListOption
-					if !platform.IsCurrentOperatorGlobal() {
-						opts = append(opts, ctrl.InNamespace(p.Namespace))
-					}
-
-					if err := c.List(context.Background(), list, opts...); err != nil {
-						log.Error(err, "Failed to list integrations")
-						return requests
-					}
-
-					for _, integration := range list.Items {
-						if integration.Status.Phase == v1.IntegrationPhaseWaitingForPlatform {
-							log.Infof("Platform %s ready, wake-up integration: %s", p.Name, integration.Name)
-							requests = append(requests, reconcile.Request{
-								NamespacedName: types.NamespacedName{
-									Namespace: integration.Namespace,
-									Name:      integration.Name,
-								},
-							})
-						}
-					}
+					return []reconcile.Request{}
 				}
 
-				return requests
+				return integrationPlatformEnqueueRequestsFromMapFunc(c, p)
 			})).
 		// Watch for the owned Deployments
 		Owns(&appsv1.Deployment{}, builder.WithPredicates(StatusChangedPredicate{})).
diff --git a/pkg/controller/integration/kits.go b/pkg/controller/integration/kits.go
index 4861d8972..433d96180 100644
--- a/pkg/controller/integration/kits.go
+++ b/pkg/controller/integration/kits.go
@@ -26,9 +26,9 @@ import (
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/selection"
 
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
 
-	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/platform"
 	"github.com/apache/camel-k/pkg/trait"
 	"github.com/apache/camel-k/pkg/util"
@@ -82,6 +82,18 @@ func lookupKitsForIntegration(ctx context.Context, c ctrl.Reader, integration *v
 	return kits, nil
 }
 
+// sameOrMatch returns whether the v1.IntegrationKit is the one used by the v1.Integration or if it meets the
+// requirements of the v1.Integration.
+func sameOrMatch(kit *v1.IntegrationKit, integration *v1.Integration) (bool, error) {
+	if integration.Status.IntegrationKit != nil {
+		if integration.Status.IntegrationKit.Namespace == kit.Namespace && integration.Status.IntegrationKit.Name == kit.Name {
+			return true, nil
+		}
+	}
+
+	return integrationMatches(integration, kit)
+}
+
 // integrationMatches returns whether the v1.IntegrationKit meets the requirements of the v1.Integration.
 func integrationMatches(integration *v1.Integration, kit *v1.IntegrationKit) (bool, error) {
 	ilog := log.ForIntegration(integration)
@@ -101,7 +113,17 @@ func integrationMatches(integration *v1.Integration, kit *v1.IntegrationKit) (bo
 	//
 	// A kit can be used only if it contains a subset of the traits and related configurations
 	// declared on integration.
-	if match, err := hasMatchingTraits(integration.Spec.Traits, kit.Spec.Traits); !match || err != nil {
+
+	itc, err := trait.NewTraitsOptionsForIntegration(integration)
+	if err != nil {
+		return false, err
+	}
+	ikc, err := trait.NewTraitsOptionsForIntegrationKit(kit)
+	if err != nil {
+		return false, err
+	}
+
+	if match, err := hasMatchingTraits(itc, ikc); !match || err != nil {
 		ilog.Debug("Integration and integration-kit traits do not match", "integration", integration.Name, "integration-kit", kit.Name, "namespace", integration.Namespace)
 		return false, err
 	}
@@ -147,7 +169,17 @@ func kitMatches(kit1 *v1.IntegrationKit, kit2 *v1.IntegrationKit) (bool, error)
 	if len(kit1.Spec.Dependencies) != len(kit2.Spec.Dependencies) {
 		return false, nil
 	}
-	if match, err := hasMatchingTraits(kit1.Spec.Traits, kit2.Spec.Traits); !match || err != nil {
+
+	c1, err := trait.NewTraitsOptionsForIntegrationKit(kit1)
+	if err != nil {
+		return false, err
+	}
+	c2, err := trait.NewTraitsOptionsForIntegrationKit(kit2)
+	if err != nil {
+		return false, err
+	}
+
+	if match, err := hasMatchingTraits(c1, c2); !match || err != nil {
 		return false, err
 	}
 	if !util.StringSliceContains(kit1.Spec.Dependencies, kit2.Spec.Dependencies) {
@@ -157,15 +189,7 @@ func kitMatches(kit1 *v1.IntegrationKit, kit2 *v1.IntegrationKit) (bool, error)
 	return true, nil
 }
 
-func hasMatchingTraits(traits interface{}, kitTraits interface{}) (bool, error) {
-	traitMap, err := trait.ToTraitMap(traits)
-	if err != nil {
-		return false, err
-	}
-	kitTraitMap, err := trait.ToTraitMap(kitTraits)
-	if err != nil {
-		return false, err
-	}
+func hasMatchingTraits(traitMap trait.Options, kitTraitMap trait.Options) (bool, error) {
 	catalog := trait.NewCatalog(nil)
 
 	for _, t := range catalog.AllTraits() {
@@ -173,9 +197,10 @@ func hasMatchingTraits(traits interface{}, kitTraits interface{}) (bool, error)
 			// We don't store the trait configuration if the trait cannot influence the kit behavior
 			continue
 		}
+
 		id := string(t.ID())
-		it, ok1 := findTrait(traitMap, id)
-		kt, ok2 := findTrait(kitTraitMap, id)
+		it, ok1 := traitMap.Get(id)
+		kt, ok2 := kitTraitMap.Get(id)
 
 		if !ok1 && !ok2 {
 			continue
@@ -198,22 +223,6 @@ func hasMatchingTraits(traits interface{}, kitTraits interface{}) (bool, error)
 	return true, nil
 }
 
-func findTrait(traitsMap map[string]map[string]interface{}, id string) (map[string]interface{}, bool) {
-	if trait, ok := traitsMap[id]; ok {
-		return trait, true
-	}
-
-	if addons, ok := traitsMap["addons"]; ok {
-		if addon, ok := addons[id]; ok {
-			if trait, ok := addon.(map[string]interface{}); ok {
-				return trait, true
-			}
-		}
-	}
-
-	return nil, false
-}
-
 func matchesComparableTrait(ct trait.ComparableTrait, it map[string]interface{}, kt map[string]interface{}) (bool, error) {
 	t1 := reflect.New(reflect.TypeOf(ct).Elem()).Interface()
 	if err := trait.ToTrait(it, &t1); err != nil {
diff --git a/pkg/controller/kameletbinding/kamelet_binding_controller.go b/pkg/controller/kameletbinding/kamelet_binding_controller.go
index 8cd8bebb1..77278f3c1 100644
--- a/pkg/controller/kameletbinding/kamelet_binding_controller.go
+++ b/pkg/controller/kameletbinding/kamelet_binding_controller.go
@@ -37,6 +37,8 @@ import (
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	"github.com/apache/camel-k/pkg/client"
+	"github.com/apache/camel-k/pkg/trait"
+
 	camelevent "github.com/apache/camel-k/pkg/event"
 	"github.com/apache/camel-k/pkg/platform"
 	"github.com/apache/camel-k/pkg/util/monitoring"
@@ -87,6 +89,19 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 				if !ok {
 					return false
 				}
+
+				// If traits have changed, the reconciliation loop must kick in as
+				// traits may have impact
+				sameTraits, err := trait.KameletBindingsHaveSameTraits(oldKameletBinding, newKameletBinding)
+				if err != nil {
+					Log.ForKameletBinding(newKameletBinding).Error(
+						err,
+						"unable to determine if old and new resource have the same traits")
+				}
+				if !sameTraits {
+					return true
+				}
+
 				// Ignore updates to the kameletBinding status in which case metadata.Generation
 				// does not change, or except when the kameletBinding phase changes as it's used
 				// to transition from one phase to another
diff --git a/pkg/controller/kameletbinding/monitor.go b/pkg/controller/kameletbinding/monitor.go
index 841a313fc..04a4e546c 100644
--- a/pkg/controller/kameletbinding/monitor.go
+++ b/pkg/controller/kameletbinding/monitor.go
@@ -31,6 +31,7 @@ import (
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+	"github.com/apache/camel-k/pkg/trait"
 )
 
 // NewMonitorAction returns an action that monitors the KameletBinding after it's fully initialized.
@@ -76,14 +77,25 @@ func (action *monitorAction) Handle(ctx context.Context, kameletbinding *v1alpha
 	operatorIDChanged := v1.GetOperatorIDAnnotation(kameletbinding) != "" &&
 		(v1.GetOperatorIDAnnotation(kameletbinding) != v1.GetOperatorIDAnnotation(&it))
 
+	sameTraits, err := trait.IntegrationAndBindingSameTraits(&it, kameletbinding)
+	if err != nil {
+		return nil, err
+	}
+
 	// Check if the integration needs to be changed
 	expected, err := CreateIntegrationFor(ctx, action.client, kameletbinding)
 	if err != nil {
 		return nil, err
 	}
 
-	if !equality.Semantic.DeepDerivative(expected.Spec, it.Spec) || operatorIDChanged {
-		action.L.Info("Monitor: KameletBinding needs a rebuild")
+	semanticEquality := equality.Semantic.DeepDerivative(expected.Spec, it.Spec)
+
+	if !semanticEquality || operatorIDChanged || !sameTraits {
+		action.L.Info(
+			"KameletBinding needs a rebuild",
+			"semantic-equality", !semanticEquality,
+			"operatorid-changed", operatorIDChanged,
+			"traits-changed", !sameTraits)
 
 		// KameletBinding has changed and needs rebuild
 		target := kameletbinding.DeepCopy()
diff --git a/pkg/trait/camel.go b/pkg/trait/camel.go
index 671cee6ba..17f847560 100644
--- a/pkg/trait/camel.go
+++ b/pkg/trait/camel.go
@@ -79,10 +79,6 @@ func (t *camelTrait) Apply(e *Environment) error {
 	if e.IntegrationKitInPhase(v1.IntegrationKitPhaseReady) && e.IntegrationInRunningPhases() {
 		// Get all resources
 		maps := t.computeConfigMaps(e)
-		if t.Properties != nil {
-			// Only user.properties
-			maps = append(maps, t.computeUserProperties(e)...)
-		}
 		e.Resources.AddAll(maps)
 	}
 
@@ -179,6 +175,14 @@ func (t *camelTrait) computeConfigMaps(e *Environment) []ctrl.Object {
 		userProperties += fmt.Sprintf("%s=%s\n", prop.Name, prop.Value)
 	}
 
+	if t.Properties != nil {
+		// Merge with properties set in the trait
+		for _, prop := range t.Properties {
+			k, v := property.SplitPropertyFileEntry(prop)
+			userProperties += fmt.Sprintf("%s=%s\n", k, v)
+		}
+	}
+
 	if userProperties != "" {
 		maps = append(
 			maps,
@@ -283,41 +287,3 @@ func (t *camelTrait) computeConfigMaps(e *Environment) []ctrl.Object {
 
 	return maps
 }
-
-func (t *camelTrait) computeUserProperties(e *Environment) []ctrl.Object {
-	maps := make([]ctrl.Object, 0)
-
-	// combine properties of integration with kit, integration
-	// properties have the priority
-	userProperties := ""
-
-	for _, prop := range t.Properties {
-		k, v := property.SplitPropertyFileEntry(prop)
-		userProperties += fmt.Sprintf("%s=%s\n", k, v)
-	}
-
-	if userProperties != "" {
-		maps = append(
-			maps,
-			&corev1.ConfigMap{
-				TypeMeta: metav1.TypeMeta{
-					Kind:       "ConfigMap",
-					APIVersion: "v1",
-				},
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      e.Integration.Name + "-user-properties",
-					Namespace: e.Integration.Namespace,
-					Labels: map[string]string{
-						v1.IntegrationLabel:                e.Integration.Name,
-						"camel.apache.org/properties.type": "user",
-					},
-				},
-				Data: map[string]string{
-					"application.properties": userProperties,
-				},
-			},
-		)
-	}
-
-	return maps
-}
diff --git a/pkg/trait/deployment.go b/pkg/trait/deployment.go
index d7a966d93..35a3eeada 100644
--- a/pkg/trait/deployment.go
+++ b/pkg/trait/deployment.go
@@ -20,10 +20,12 @@ package trait
 import (
 	"fmt"
 
+	"k8s.io/apimachinery/pkg/util/intstr"
+	"k8s.io/utils/pointer"
+
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/utils/pointer"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	traitv1 "github.com/apache/camel-k/pkg/apis/camel/v1/trait"
@@ -165,6 +167,36 @@ func (t *deploymentTrait) getDeploymentFor(e *Environment) *appsv1.Deployment {
 		},
 	}
 
+	switch t.Strategy {
+	case appsv1.RecreateDeploymentStrategyType:
+		deployment.Spec.Strategy = appsv1.DeploymentStrategy{
+			Type: t.Strategy,
+		}
+	case appsv1.RollingUpdateDeploymentStrategyType:
+		deployment.Spec.Strategy = appsv1.DeploymentStrategy{
+			Type: t.Strategy,
+		}
+
+		if t.RollingUpdateMaxSurge != nil || t.RollingUpdateMaxUnavailable != nil {
+			var maxSurge *intstr.IntOrString
+			var maxUnavailable *intstr.IntOrString
+
+			if t.RollingUpdateMaxSurge != nil {
+				v := intstr.FromInt(*t.RollingUpdateMaxSurge)
+				maxSurge = &v
+			}
+			if t.RollingUpdateMaxUnavailable != nil {
+				v := intstr.FromInt(*t.RollingUpdateMaxUnavailable)
+				maxUnavailable = &v
+			}
+
+			deployment.Spec.Strategy.RollingUpdate = &appsv1.RollingUpdateDeployment{
+				MaxSurge:       maxSurge,
+				MaxUnavailable: maxUnavailable,
+			}
+		}
+	}
+
 	// Reconcile the deployment replicas
 	replicas := e.Integration.Spec.Replicas
 	// Deployment replicas defaults to 1, so we avoid forcing
diff --git a/pkg/trait/util.go b/pkg/trait/util.go
index c1310ec6e..880098c71 100644
--- a/pkg/trait/util.go
+++ b/pkg/trait/util.go
@@ -29,10 +29,11 @@ import (
 	user "github.com/mitchellh/go-homedir"
 	"github.com/pkg/errors"
 	"github.com/scylladb/go-set/strset"
-
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	"github.com/apache/camel-k/pkg/client"
 	"github.com/apache/camel-k/pkg/metadata"
 	"github.com/apache/camel-k/pkg/util"
@@ -40,6 +41,24 @@ import (
 	"github.com/apache/camel-k/pkg/util/property"
 )
 
+type Options map[string]map[string]interface{}
+
+func (u Options) Get(id string) (map[string]interface{}, bool) {
+	if t, ok := u[id]; ok {
+		return t, true
+	}
+
+	if addons, ok := u["addons"]; ok {
+		if addon, ok := addons[id]; ok {
+			if t, ok := addon.(map[string]interface{}); ok {
+				return t, true
+			}
+		}
+	}
+
+	return nil, false
+}
+
 var exactVersionRegexp = regexp.MustCompile(`^(\d+)\.(\d+)\.([\w-.]+)$`)
 
 // getIntegrationKit retrieves the kit set on the integration.
@@ -237,7 +256,7 @@ func AssertTraitsType(traits interface{}) error {
 }
 
 // ToTraitMap accepts either v1.Traits or v1.IntegrationKitTraits and converts it to a map of traits.
-func ToTraitMap(traits interface{}) (map[string]map[string]interface{}, error) {
+func ToTraitMap(traits interface{}) (Options, error) {
 	if err := AssertTraitsType(traits); err != nil {
 		return nil, err
 	}
@@ -246,7 +265,7 @@ func ToTraitMap(traits interface{}) (map[string]map[string]interface{}, error) {
 	if err != nil {
 		return nil, err
 	}
-	traitMap := make(map[string]map[string]interface{})
+	traitMap := make(Options)
 	if err = json.Unmarshal(data, &traitMap); err != nil {
 		return nil, err
 	}
@@ -320,3 +339,206 @@ func getBuilderTask(tasks []v1.Task) *v1.BuilderTask {
 	}
 	return nil
 }
+
+// Equals return if traits are the same.
+func Equals(i1 Options, i2 Options) bool {
+	return reflect.DeepEqual(i1, i2)
+}
+
+// IntegrationsHaveSameTraits return if traits are the same.
+func IntegrationsHaveSameTraits(i1 *v1.Integration, i2 *v1.Integration) (bool, error) {
+	c1, err := NewTraitsOptionsForIntegration(i1)
+	if err != nil {
+		return false, err
+	}
+	c2, err := NewTraitsOptionsForIntegration(i2)
+	if err != nil {
+		return false, err
+	}
+
+	return Equals(c1, c2), nil
+}
+
+// IntegrationKitsHaveSameTraits return if traits are the same.
+func IntegrationKitsHaveSameTraits(i1 *v1.IntegrationKit, i2 *v1.IntegrationKit) (bool, error) {
+	c1, err := NewTraitsOptionsForIntegrationKit(i1)
+	if err != nil {
+		return false, err
+	}
+	c2, err := NewTraitsOptionsForIntegrationKit(i2)
+	if err != nil {
+		return false, err
+	}
+
+	return Equals(c1, c2), nil
+}
+
+// KameletBindingsHaveSameTraits return if traits are the same.
+func KameletBindingsHaveSameTraits(i1 *v1alpha1.KameletBinding, i2 *v1alpha1.KameletBinding) (bool, error) {
+	c1, err := NewTraitsOptionsForKameletBinding(i1)
+	if err != nil {
+		return false, err
+	}
+	c2, err := NewTraitsOptionsForKameletBinding(i2)
+	if err != nil {
+		return false, err
+	}
+
+	return Equals(c1, c2), nil
+}
+
+// IntegrationAndBindingSameTraits return if traits are the same.
+// The comparison is done for the subset of traits defines on the binding as during the trait processing,
+// some traits may be added to the Integration i.e. knative configuration in case of sink binding.
+func IntegrationAndBindingSameTraits(i1 *v1.Integration, i2 *v1alpha1.KameletBinding) (bool, error) {
+	itOpts, err := NewTraitsOptionsForIntegration(i1)
+	if err != nil {
+		return false, err
+	}
+	klbOpts, err := NewTraitsOptionsForKameletBinding(i2)
+	if err != nil {
+		return false, err
+	}
+
+	toCompare := make(Options)
+	for k := range klbOpts {
+		if v, ok := itOpts[k]; ok {
+			toCompare[k] = v
+		}
+	}
+
+	return Equals(klbOpts, toCompare), nil
+}
+
+// IntegrationAndKitHaveSameTraits return if traits are the same.
+func IntegrationAndKitHaveSameTraits(i1 *v1.Integration, i2 *v1.IntegrationKit) (bool, error) {
+	itOpts, err := NewTraitsOptionsForIntegration(i1)
+	if err != nil {
+		return false, err
+	}
+	ikOpts, err := NewTraitsOptionsForIntegrationKit(i2)
+	if err != nil {
+		return false, err
+	}
+
+	return Equals(ikOpts, itOpts), nil
+}
+
+func NewTraitsOptionsForIntegration(i *v1.Integration) (Options, error) {
+	m1, err := ToTraitMap(i.Spec.Traits)
+	if err != nil {
+		return nil, err
+	}
+
+	m2, err := FromAnnotations(&i.ObjectMeta)
+	if err != nil {
+		return nil, err
+	}
+
+	for k, v := range m2 {
+		m1[k] = v
+	}
+
+	return m1, nil
+}
+
+func NewTraitsOptionsForIntegrationKit(i *v1.IntegrationKit) (Options, error) {
+	m1, err := ToTraitMap(i.Spec.Traits)
+	if err != nil {
+		return nil, err
+	}
+
+	m2, err := FromAnnotations(&i.ObjectMeta)
+	if err != nil {
+		return nil, err
+	}
+
+	for k, v := range m2 {
+		m1[k] = v
+	}
+
+	return m1, nil
+}
+
+func NewTraitsOptionsForIntegrationPlatform(i *v1.IntegrationPlatform) (Options, error) {
+	m1, err := ToTraitMap(i.Spec.Traits)
+	if err != nil {
+		return nil, err
+	}
+
+	m2, err := FromAnnotations(&i.ObjectMeta)
+	if err != nil {
+		return nil, err
+	}
+
+	for k, v := range m2 {
+		m1[k] = v
+	}
+
+	return m1, nil
+}
+
+func NewTraitsOptionsForKameletBinding(i *v1alpha1.KameletBinding) (Options, error) {
+	if i.Spec.Integration != nil {
+		m1, err := ToTraitMap(i.Spec.Integration.Traits)
+		if err != nil {
+			return nil, err
+		}
+
+		m2, err := FromAnnotations(&i.ObjectMeta)
+		if err != nil {
+			return nil, err
+		}
+
+		for k, v := range m2 {
+			m1[k] = v
+		}
+
+		return m1, nil
+	}
+
+	m1, err := FromAnnotations(&i.ObjectMeta)
+	if err != nil {
+		return nil, err
+	}
+
+	return m1, nil
+}
+
+func FromAnnotations(meta *metav1.ObjectMeta) (Options, error) {
+	options := make(Options)
+
+	for k, v := range meta.Annotations {
+		if strings.HasPrefix(k, v1.TraitAnnotationPrefix) {
+			configKey := strings.TrimPrefix(k, v1.TraitAnnotationPrefix)
+			if strings.Contains(configKey, ".") {
+				parts := strings.SplitN(configKey, ".", 2)
+				id := parts[0]
+				prop := parts[1]
+				if _, ok := options[id]; !ok {
+					options[id] = make(map[string]interface{})
+				}
+
+				propParts := util.ConfigTreePropertySplit(prop)
+				var current = options[id]
+				if len(propParts) > 1 {
+					c, err := util.NavigateConfigTree(current, propParts[0:len(propParts)-1])
+					if err != nil {
+						return options, err
+					}
+					if cc, ok := c.(map[string]interface{}); ok {
+						current = cc
+					} else {
+						return options, errors.New(`invalid array specification: to set an array value use the ["v1", "v2"] format`)
+					}
+				}
+				current[prop] = v
+
+			} else {
+				return options, fmt.Errorf("wrong format for trait annotation %q: missing trait ID", k)
+			}
+		}
+	}
+
+	return options, nil
+}
diff --git a/pkg/util/digest/digest.go b/pkg/util/digest/digest.go
index 02a060411..2c1ea63b0 100644
--- a/pkg/util/digest/digest.go
+++ b/pkg/util/digest/digest.go
@@ -202,6 +202,10 @@ func ComputeForIntegrationKit(kit *v1.IntegrationKit) (string, error) {
 		return "", err
 	}
 
+	if _, err := hash.Write([]byte(kit.Spec.Image)); err != nil {
+		return "", err
+	}
+
 	for _, item := range kit.Spec.Dependencies {
 		if _, err := hash.Write([]byte(item)); err != nil {
 			return "", err
diff --git a/pkg/util/docker/docker.go b/pkg/util/docker/docker.go
index 38bf8a0e9..eca77307f 100644
--- a/pkg/util/docker/docker.go
+++ b/pkg/util/docker/docker.go
@@ -39,8 +39,7 @@ func CreateBaseImageDockerFile() error {
 
 	// Write <BaseWorkingDirectory>/Dockerfile
 	baseDockerFilePath := path.Join(BaseWorkingDirectory, "Dockerfile")
-	err := util.WriteToFile(baseDockerFilePath, strings.Join(dockerFile, "\n"))
-	if err != nil {
+	if err := util.WriteToFile(baseDockerFilePath, strings.Join(dockerFile, "\n")); err != nil {
 		return err
 	}
 
@@ -92,31 +91,27 @@ func CreateIntegrationImageDockerFile(integrationRunCmd *exec.Cmd, startsFromLoc
 	return nil
 }
 
-// BuildBaseImageArgs --.
+// BuildBaseImageArgs constructs the docker command:
+//
+// docker build -f <BaseWorkingDirectory>/Dockerfile -t <dockerRegistry>/<BaseImageName> <BaseWorkingDirectory>.
+//
 func BuildBaseImageArgs() []string {
-	// Construct the docker command:
-	//
-	// docker build -f <BaseWorkingDirectory>/Dockerfile -t <dockerRegistry>/<BaseImageName> <BaseWorkingDirectory>
-	//
-	// Add register
 	return BuildImageArgs(BaseWorkingDirectory, GetBaseImagePath(), BaseWorkingDirectory)
 }
 
-// BuildIntegrationImageArgs --.
-func BuildIntegrationImageArgs(imagePath string) []string {
-	// Construct the docker command:
-	//
-	// docker build -f <BaseWorkingDirectory>/Dockerfile -t <imagePath> <MavenWorkingDirectory>
-	//
-	return BuildImageArgs(IntegrationWorkingDirectory, imagePath, util.MavenWorkingDirectory)
+// BuildIntegrationImageArgs constructs the docker command:
+//
+// docker build -f <BaseWorkingDirectory>/Dockerfile -t <imagePath> <mavenWorkingDirectory>.
+//
+func BuildIntegrationImageArgs(imagePath string, mavenWorkingDirectory string) []string {
+	return BuildImageArgs(IntegrationWorkingDirectory, imagePath, mavenWorkingDirectory)
 }
 
-// RunIntegrationImageArgs --.
+// RunIntegrationImageArgs constructs the docker command:
+//
+// docker run --network=<network-name> --env LAZY_ENV_VAR=value <dockerRegistry>/<ImageName>.
+//
 func RunIntegrationImageArgs(imagePath string) ([]string, error) {
-	// Construct the docker command:
-	//
-	// docker run --network=<network-name> --env LAZY_ENV_VAR=value <dockerRegistry>/<ImageName>
-	//
 	return RunImageArgs(imagePath, latestTag)
 }
 
diff --git a/pkg/util/docker/docker_base.go b/pkg/util/docker/docker_base.go
index b1e032fbe..5fba82e95 100644
--- a/pkg/util/docker/docker_base.go
+++ b/pkg/util/docker/docker_base.go
@@ -40,7 +40,7 @@ var IntegrationWorkingDirectory = ""
 var NetworkName = "host"
 
 // Internal variables.
-var (
+const (
 	dockerEndpointSeparator = "/"
 	containerFileSeparator  = "/"
 	latestTag               = "latest"
@@ -174,7 +174,7 @@ func GetFullDockerImage(dockerImageName string, tag string) string {
 	return strings.Join(fullImagePath, dockerEndpointSeparator)
 }
 
-// GetBaseImagePath --.
+// GetBaseImagePath returns Docker base image path.
 func GetBaseImagePath() string {
 	return RegistryName + dockerEndpointSeparator + BaseImageName
 }
diff --git a/pkg/util/kubernetes/util.go b/pkg/util/kubernetes/util.go
index 3d029c202..2816ea15d 100644
--- a/pkg/util/kubernetes/util.go
+++ b/pkg/util/kubernetes/util.go
@@ -18,10 +18,9 @@ limitations under the License.
 package kubernetes
 
 import (
+	"github.com/apache/camel-k/pkg/util"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/util/json"
-
-	"github.com/apache/camel-k/pkg/util"
 )
 
 // ToJSON marshal to json format.
diff --git a/pkg/util/util.go b/pkg/util/util.go
index c68f27717..25ca86517 100644
--- a/pkg/util/util.go
+++ b/pkg/util/util.go
@@ -44,10 +44,6 @@ import (
 
 // Directories and file names:
 
-// MavenWorkingDirectory is the directory used by Maven for an invocation of the kamel local command.
-// By default, a temporary folder will be used.
-var MavenWorkingDirectory = ""
-
 // DefaultDependenciesDirectoryName --.
 const DefaultDependenciesDirectoryName = "dependencies"
 
@@ -368,20 +364,21 @@ func DirectoryEmpty(directory string) (bool, error) {
 	return ok, err
 }
 
+// CreateDirectory creates a directory if it does not exist.
 func CreateDirectory(directory string) error {
-	if directory != "" {
-		// If directory does not exist, create it
-		directoryExists, err := DirectoryExists(directory)
-		if err != nil {
-			return err
-		}
+	if directory == "" {
+		return errors.New("directory name must not be empty")
+	}
 
-		if !directoryExists {
-			// #nosec G301
-			err := os.MkdirAll(directory, 0o755)
-			if err != nil {
-				return err
-			}
+	directoryExists, err := DirectoryExists(directory)
+	if err != nil {
+		return err
+	}
+
+	if !directoryExists {
+		// #nosec G301
+		if err := os.MkdirAll(directory, 0o755); err != nil {
+			return err
 		}
 	}
 
@@ -426,21 +423,6 @@ func CopyMap(source map[string]string) map[string]string {
 	return dest
 }
 
-func DependenciesToJSON(list []string) ([]byte, error) {
-	jsondata := map[string]interface{}{}
-	jsondata["dependencies"] = list
-	return json.Marshal(jsondata)
-}
-
-func DependenciesToYAML(list []string) ([]byte, error) {
-	data, err := DependenciesToJSON(list)
-	if err != nil {
-		return nil, err
-	}
-
-	return JSONToYAML(data)
-}
-
 func JSONToYAML(src []byte) ([]byte, error) {
 	mapdata, err := JSONToMap(src)
 	if err != nil {
@@ -478,158 +460,6 @@ func WriteToFile(filePath string, fileContents string) error {
 	return nil
 }
 
-// Local directories:
-
-// GetLocalPropertiesDir -- <mavenWorkingDirectory>/properties.
-func GetLocalPropertiesDir() string {
-	return path.Join(MavenWorkingDirectory, DefaultPropertiesDirectoryName)
-}
-
-// GetLocalDependenciesDir --<mavenWorkingDirectory>/dependencies.
-func GetLocalDependenciesDir() string {
-	return path.Join(MavenWorkingDirectory, DefaultDependenciesDirectoryName)
-}
-
-// GetLocalRoutesDir -- <mavenWorkingDirectory>/routes.
-func GetLocalRoutesDir() string {
-	return path.Join(MavenWorkingDirectory, DefaultRoutesDirectoryName)
-}
-
-// GetLocalQuarkusDir -- <mavenWorkingDirectory>/quarkus.
-func GetLocalQuarkusDir() string {
-	return path.Join(MavenWorkingDirectory, CustomQuarkusDirectoryName)
-}
-
-// GetLocalAppDir -- <mavenWorkingDirectory>/app.
-func GetLocalAppDir() string {
-	return path.Join(MavenWorkingDirectory, CustomAppDirectoryName)
-}
-
-// GetLocalLibDir -- <mavenWorkingDirectory>/lib/main.
-func GetLocalLibDir() string {
-	return path.Join(MavenWorkingDirectory, CustomLibDirectoryName)
-}
-
-func CreateLocalPropertiesDirectory() error {
-	// Do not create a directory unless the maven directory contains a valid value.
-	if MavenWorkingDirectory == "" {
-		return nil
-	}
-
-	directoryExists, err := DirectoryExists(GetLocalPropertiesDir())
-	if err != nil {
-		return err
-	}
-
-	if !directoryExists {
-		err := os.MkdirAll(GetLocalPropertiesDir(), 0o700)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func CreateLocalDependenciesDirectory() error {
-	// Do not create a directory unless the maven directory contains a valid value.
-	if MavenWorkingDirectory == "" {
-		return nil
-	}
-
-	directoryExists, err := DirectoryExists(GetLocalDependenciesDir())
-	if err != nil {
-		return err
-	}
-
-	if !directoryExists {
-		err := os.MkdirAll(GetLocalDependenciesDir(), 0o700)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func CreateLocalRoutesDirectory() error {
-	// Do not create a directory unless the maven directory contains a valid value.
-	if MavenWorkingDirectory == "" {
-		return nil
-	}
-
-	directoryExists, err := DirectoryExists(GetLocalRoutesDir())
-	if err != nil {
-		return err
-	}
-
-	if !directoryExists {
-		err := os.MkdirAll(GetLocalRoutesDir(), 0o700)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func CreateLocalQuarkusDirectory() error {
-	// Do not create a directory unless the maven directory contains a valid value.
-	if MavenWorkingDirectory == "" {
-		return nil
-	}
-
-	directoryExists, err := DirectoryExists(GetLocalQuarkusDir())
-	if err != nil {
-		return err
-	}
-
-	if !directoryExists {
-		err := os.MkdirAll(GetLocalQuarkusDir(), 0o700)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func CreateLocalAppDirectory() error {
-	// Do not create a directory unless the maven directory contains a valid value.
-	if MavenWorkingDirectory == "" {
-		return nil
-	}
-
-	directoryExists, err := DirectoryExists(GetLocalAppDir())
-	if err != nil {
-		return err
-	}
-
-	if !directoryExists {
-		err := os.MkdirAll(GetLocalAppDir(), 0o700)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func CreateLocalLibDirectory() error {
-	// Do not create a directory unless the maven directory contains a valid value.
-	if MavenWorkingDirectory == "" {
-		return nil
-	}
-
-	directoryExists, err := DirectoryExists(GetLocalLibDir())
-	if err != nil {
-		return err
-	}
-
-	if !directoryExists {
-		err := os.MkdirAll(GetLocalLibDir(), 0o700)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
 func GetEnvironmentVariable(variable string) (string, error) {
 	value, isPresent := os.LookupEnv(variable)
 	if !isPresent {
@@ -682,122 +512,6 @@ func EvaluateCLIAndLazyEnvVars() ([]string, error) {
 	return evaluatedEnvVars, nil
 }
 
-func CopyIntegrationFilesToDirectory(files []string, directory string) ([]string, error) {
-	// Create directory if one does not already exist
-	err := CreateDirectory(directory)
-	if err != nil {
-		return nil, err
-	}
-
-	// Copy files to new location. Also create the list with relocated files.
-	relocatedFilesList := []string{}
-	for _, filePath := range files {
-		newFilePath := path.Join(directory, path.Base(filePath))
-		_, err := CopyFile(filePath, newFilePath)
-		if err != nil {
-			return relocatedFilesList, err
-		}
-		relocatedFilesList = append(relocatedFilesList, newFilePath)
-	}
-
-	return relocatedFilesList, nil
-}
-
-func CopyQuarkusAppFiles(localDependenciesDirectory string, localQuarkusDir string) error {
-	// Create directory if one does not already exist
-	err := CreateDirectory(localQuarkusDir)
-	if err != nil {
-		return err
-	}
-
-	// Transfer all files with a .dat extension and all files with a *-bytecode.jar suffix.
-	files, err := getRegularFileNamesInDir(localDependenciesDirectory)
-	if err != nil {
-		return err
-	}
-	for _, file := range files {
-		if strings.HasSuffix(file, ".dat") || strings.HasSuffix(file, "-bytecode.jar") {
-			source := path.Join(localDependenciesDirectory, file)
-			destination := path.Join(localQuarkusDir, file)
-			_, err = CopyFile(source, destination)
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	return nil
-}
-
-func getRegularFileNamesInDir(directory string) ([]string, error) {
-	var dirFiles []string
-	files, err := ioutil.ReadDir(directory)
-	for _, file := range files {
-		fileName := file.Name()
-
-		// Do not include hidden files or sub-directories.
-		if !file.IsDir() && !strings.HasPrefix(fileName, ".") {
-			dirFiles = append(dirFiles, fileName)
-		}
-	}
-
-	if err != nil {
-		return nil, err
-	}
-
-	return dirFiles, nil
-}
-
-func CopyLibFiles(localDependenciesDirectory string, localLibDirectory string) error {
-	// Create directory if one does not already exist
-	err := CreateDirectory(localLibDirectory)
-	if err != nil {
-		return err
-	}
-
-	fileNames, err := getRegularFileNamesInDir(localDependenciesDirectory)
-	if err != nil {
-		return err
-	}
-
-	for _, dependencyJar := range fileNames {
-		source := path.Join(localDependenciesDirectory, dependencyJar)
-		destination := path.Join(localLibDirectory, dependencyJar)
-		_, err = CopyFile(source, destination)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-func CopyAppFile(localDependenciesDirectory string, localAppDirectory string) error {
-	// Create directory if one does not already exist
-	err := CreateDirectory(localAppDirectory)
-	if err != nil {
-		return err
-	}
-
-	fileNames, err := getRegularFileNamesInDir(localDependenciesDirectory)
-	if err != nil {
-		return err
-	}
-
-	for _, dependencyJar := range fileNames {
-		if strings.HasPrefix(dependencyJar, "camel-k-integration-") {
-			source := path.Join(localDependenciesDirectory, dependencyJar)
-			destination := path.Join(localAppDirectory, dependencyJar)
-			_, err = CopyFile(source, destination)
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	return nil
-}
-
 // Open a safe wrapper of os.Open.
 func Open(name string) (*os.File, error) {
 	return os.Open(filepath.Clean(name))
diff --git a/resources/traits.yaml b/resources/traits.yaml
index 81686464b..5d9aa7c9d 100755
--- a/resources/traits.yaml
+++ b/resources/traits.yaml
@@ -333,6 +333,22 @@ traits:
     type: int32
     description: The maximum time in seconds for the deployment to make progress before
       it is considered to be failed. It defaults to 60s.
+  - name: strategy
+    type: DeploymentStrategyType
+    description: The deployment strategy to use to replace existing pods with new
+      ones.
+  - name: rolling-update-max-unavailable
+    type: int
+    description: 'The maximum number of pods that can be unavailable during the update.
+      Value can be an absolute number (ex: 5) or a percentage of desired pods (ex:
+      10%). Absolute number is calculated from percentage by rounding down. This can
+      not be 0 if MaxSurge is 0. Defaults to 25%.'
+  - name: rolling-update-max-surge
+    type: int
+    description: 'The maximum number of pods that can be scheduled above the desired
+      number of pods. Value can be an absolute number (ex: 5) or a percentage of desired
+      pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is
+      calculated from percentage by rounding up. Defaults to 25%.'
 - name: environment
   platform: true
   profiles:
diff --git a/script/Makefile b/script/Makefile
index 2efae5375..6d159f2e6 100644
--- a/script/Makefile
+++ b/script/Makefile
@@ -388,10 +388,12 @@ kamel-overlay:
 
 images: build kamel-overlay maven-overlay bundle-kamelets
 	@echo "####### Building Camel K operator container image..."
+	mkdir -p build/_maven_output
 	docker build -t $(CUSTOM_IMAGE):$(CUSTOM_VERSION) -f build/Dockerfile .
 
 images-arch: build kamel-overlay maven-overlay bundle-kamelets
 	@echo "####### Building Camel K operator container image for multi architectures..."
+	mkdir -p build/_maven_output
 	docker buildx rm --all-inactive --force
 	docker buildx create --append --name builder
 ifeq ($(shell uname -m), x86_x64)
@@ -518,6 +520,7 @@ ifeq (, $(shell command -v operator-sdk 2> /dev/null))
 		-L https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$(OS_LOWER)_amd64 \
 		-o operator-sdk ; \
 	chmod +x operator-sdk ;\
+	mkdir -p $(GOBIN) ;\
 	mv operator-sdk $(GOBIN)/ ;\
 	}
 OPERATOR_SDK=$(GOBIN)/operator-sdk


Test patch:
diff --git a/e2e/global/common/integration_fail_test.go b/e2e/global/common/integration_fail_test.go
index ec41bdd6a..c95b691b1 100644
--- a/e2e/global/common/integration_fail_test.go
+++ b/e2e/global/common/integration_fail_test.go
@@ -55,17 +55,10 @@ func TestBadRouteIntegration(t *testing.T) {
 				Should(gstruct.PointTo(BeNumerically("==", 2)))
 			// Check the Integration stays in error phase
 			Eventually(IntegrationPhase(ns, name), TestTimeoutShort).Should(Equal(v1.IntegrationPhaseError))
-		})
 
-		// Clean up
-		Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
-	})
-}
-
-func TestMissingDependencyIntegration(t *testing.T) {
-	WithNewTestNamespace(t, func(ns string) {
-		operatorID := "camel-k-missing-dependencies"
-		Expect(KamelInstallWithID(operatorID, ns).Execute()).To(Succeed())
+			// Clean up
+			Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
+		})
 
 		t.Run("run missing dependency java route", func(t *testing.T) {
 			RegisterTestingT(t)
@@ -80,9 +73,9 @@ func TestMissingDependencyIntegration(t *testing.T) {
 			build := Build(ns, kitName)()
 			Eventually(build.Status.Phase, TestTimeoutShort).Should(Equal(v1.BuildPhaseError))
 			Eventually(build.Status.Failure.Recovery.Attempt, TestTimeoutShort).Should(Equal(5))
-		})
 
-		// Clean up
-		Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
+			// Clean up
+			Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
+		})
 	})
 }
diff --git a/e2e/global/common/kamelet_binding_test.go b/e2e/global/common/kamelet_binding_test.go
index 2c88d4e2c..a98854254 100644
--- a/e2e/global/common/kamelet_binding_test.go
+++ b/e2e/global/common/kamelet_binding_test.go
@@ -33,62 +33,91 @@ import (
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 )
 
-func TestErrorHandler(t *testing.T) {
+func TestKameletBinding(t *testing.T) {
 	WithNewTestNamespace(t, func(ns string) {
-		operatorID := "camel-k-kamelet-errorhandler"
+		operatorID := "camel-k-kameletbinding"
 		Expect(KamelInstallWithID(operatorID, ns).Execute()).To(Succeed())
 
-		Expect(createErrorProducerKamelet(ns, "my-own-error-producer-source")()).To(Succeed())
-		Expect(createLogKamelet(ns, "my-own-log-sink")()).To(Succeed())
-		from := corev1.ObjectReference{
-			Kind:       "Kamelet",
-			Name:       "my-own-error-producer-source",
-			APIVersion: v1alpha1.SchemeGroupVersion.String(),
-		}
-
-		to := corev1.ObjectReference{
-			Kind:       "Kamelet",
-			Name:       "my-own-log-sink",
-			APIVersion: v1alpha1.SchemeGroupVersion.String(),
-		}
-
-		errorHandler := map[string]interface{}{
-			"sink": map[string]interface{}{
-				"endpoint": map[string]interface{}{
-					"ref": map[string]string{
-						"kind":       "Kamelet",
-						"apiVersion": v1alpha1.SchemeGroupVersion.String(),
-						"name":       "my-own-log-sink",
-					},
-					"properties": map[string]string{
-						"loggerName": "kameletErrorHandler",
-					},
-				}}}
-
-		t.Run("throw error test", func(t *testing.T) {
+		// Error Handler testing
+		t.Run("test error handler", func(t *testing.T) {
 			RegisterTestingT(t)
-
-			Expect(BindKameletToWithErrorHandler(ns, "throw-error-binding", map[string]string{}, from, to, map[string]string{"message": "throw Error"}, map[string]string{"loggerName": "integrationLogger"}, errorHandler)()).To(Succeed())
-
-			Eventually(IntegrationPodPhase(ns, "throw-error-binding"), TestTimeoutLong).Should(Equal(corev1.PodRunning))
-			Eventually(IntegrationLogs(ns, "throw-error-binding"), TestTimeoutShort).Should(ContainSubstring("kameletErrorHandler"))
-			Eventually(IntegrationLogs(ns, "throw-error-binding"), TestTimeoutShort).ShouldNot(ContainSubstring("integrationLogger"))
-
+			Expect(createErrorProducerKamelet(ns, "my-own-error-producer-source")()).To(Succeed())
+			Expect(createLogKamelet(ns, "my-own-log-sink")()).To(Succeed())
+			from := corev1.ObjectReference{
+				Kind:       "Kamelet",
+				Name:       "my-own-error-producer-source",
+				APIVersion: v1alpha1.SchemeGroupVersion.String(),
+			}
+
+			to := corev1.ObjectReference{
+				Kind:       "Kamelet",
+				Name:       "my-own-log-sink",
+				APIVersion: v1alpha1.SchemeGroupVersion.String(),
+			}
+
+			errorHandler := map[string]interface{}{
+				"sink": map[string]interface{}{
+					"endpoint": map[string]interface{}{
+						"ref": map[string]string{
+							"kind":       "Kamelet",
+							"apiVersion": v1alpha1.SchemeGroupVersion.String(),
+							"name":       "my-own-log-sink",
+						},
+						"properties": map[string]string{
+							"loggerName": "kameletErrorHandler",
+						},
+					}}}
+
+			t.Run("throw error test", func(t *testing.T) {
+				RegisterTestingT(t)
+
+				Expect(BindKameletToWithErrorHandler(ns, "throw-error-binding", map[string]string{}, from, to, map[string]string{"message": "throw Error"}, map[string]string{"loggerName": "integrationLogger"}, errorHandler)()).To(Succeed())
+
+				Eventually(IntegrationPodPhase(ns, "throw-error-binding"), TestTimeoutLong).Should(Equal(corev1.PodRunning))
+				Eventually(IntegrationLogs(ns, "throw-error-binding"), TestTimeoutShort).Should(ContainSubstring("kameletErrorHandler"))
+				Eventually(IntegrationLogs(ns, "throw-error-binding"), TestTimeoutShort).ShouldNot(ContainSubstring("integrationLogger"))
+
+			})
+
+			t.Run("don't throw error test", func(t *testing.T) {
+				RegisterTestingT(t)
+
+				Expect(BindKameletToWithErrorHandler(ns, "no-error-binding", map[string]string{}, from, to, map[string]string{"message": "true"}, map[string]string{"loggerName": "integrationLogger"}, errorHandler)()).To(Succeed())
+
+				Eventually(IntegrationPodPhase(ns, "no-error-binding"), TestTimeoutLong).Should(Equal(corev1.PodRunning))
+				Eventually(IntegrationLogs(ns, "no-error-binding"), TestTimeoutShort).ShouldNot(ContainSubstring("kameletErrorHandler"))
+				Eventually(IntegrationLogs(ns, "no-error-binding"), TestTimeoutShort).Should(ContainSubstring("integrationLogger"))
+
+			})
+
+			// Cleanup
+			Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil())
 		})
 
-		t.Run("don't throw error test", func(t *testing.T) {
+		// Kamelet binding with traits testing
+		t.Run("test kamelet binding with trait", func(t *testing.T) {
 			RegisterTestingT(t)
-
-			Expect(BindKameletToWithErrorHandler(ns, "no-error-binding", map[string]string{}, from, to, map[string]string{"message": "true"}, map[string]string{"loggerName": "integrationLogger"}, errorHandler)()).To(Succeed())
-
-			Eventually(IntegrationPodPhase(ns, "no-error-binding"), TestTimeoutLong).Should(Equal(corev1.PodRunning))
-			Eventually(IntegrationLogs(ns, "no-error-binding"), TestTimeoutShort).ShouldNot(ContainSubstring("kameletErrorHandler"))
-			Eventually(IntegrationLogs(ns, "no-error-binding"), TestTimeoutShort).Should(ContainSubstring("integrationLogger"))
-
+			Expect(CreateTimerKamelet(ns, "my-own-timer-source")()).To(Succeed())
+			// Log sink kamelet exists from previous test
+
+			from := corev1.ObjectReference{
+				Kind:       "Kamelet",
+				Name:       "my-own-timer-source",
+				APIVersion: v1alpha1.SchemeGroupVersion.String(),
+			}
+
+			to := corev1.ObjectReference{
+				Kind:       "Kamelet",
+				Name:       "my-own-log-sink",
+				APIVersion: v1alpha1.SchemeGroupVersion.String(),
+			}
+
+			Expect(BindKameletTo(ns, "kb-with-traits", map[string]string{"trait.camel.apache.org/camel.properties": "[\"camel.prop1=a\",\"camel.prop2=b\"]"}, from, to, map[string]string{"message": "hello from test"}, map[string]string{"loggerName": "integrationLogger"})()).To(Succeed())
+
+			Eventually(IntegrationPodPhase(ns, "kb-with-traits"), TestTimeoutLong).Should(Equal(corev1.PodRunning))
+			Eventually(IntegrationLogs(ns, "kb-with-traits"), TestTimeoutShort).Should(ContainSubstring("hello from test"))
+			Eventually(IntegrationLogs(ns, "kb-with-traits"), TestTimeoutShort).Should(ContainSubstring("integrationLogger"))
 		})
-
-		// Cleanup
-		Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil())
 	})
 }
 
diff --git a/e2e/global/common/kamelet_binding_with_image_test.go b/e2e/global/common/kamelet_binding_with_image_test.go
new file mode 100644
index 000000000..e28132c59
--- /dev/null
+++ b/e2e/global/common/kamelet_binding_with_image_test.go
@@ -0,0 +1,116 @@
+//go:build integration
+// +build integration
+
+// To enable compilation of this file in Goland, go to "Settings -> Go -> Vendoring & Build Tags -> Custom Tags" and add "integration"
+
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package common
+
+import (
+	"github.com/onsi/gomega/gstruct"
+	"testing"
+
+	. "github.com/apache/camel-k/e2e/support"
+	. "github.com/onsi/gomega"
+
+	corev1 "k8s.io/api/core/v1"
+
+	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+)
+
+func TestBindingWithImage(t *testing.T) {
+	WithNewTestNamespace(t, func(ns string) {
+		operatorID := "camel-k-binding-image"
+		bindingID := "with-image-binding"
+
+		Expect(KamelInstallWithID(operatorID, ns).Execute()).To(Succeed())
+
+		from := corev1.ObjectReference{
+			Kind:       "Kamelet",
+			Name:       "my-own-timer-source",
+			APIVersion: v1alpha1.SchemeGroupVersion.String(),
+		}
+
+		to := corev1.ObjectReference{
+			Kind:       "Kamelet",
+			Name:       "my-own-log-sink",
+			APIVersion: v1alpha1.SchemeGroupVersion.String(),
+		}
+
+		emptyMap := map[string]string{}
+
+		annotations1 := map[string]string{
+			"trait.camel.apache.org/container.image":      "docker.io/jmalloc/echo-server:0.3.2",
+			"trait.camel.apache.org/jvm.enabled":          "false",
+			"trait.camel.apache.org/kamelets.enabled":     "false",
+			"trait.camel.apache.org/dependencies.enabled": "false",
+			"test": "1",
+		}
+		annotations2 := map[string]string{
+			"trait.camel.apache.org/container.image":      "docker.io/jmalloc/echo-server:0.3.3",
+			"trait.camel.apache.org/jvm.enabled":          "false",
+			"trait.camel.apache.org/kamelets.enabled":     "false",
+			"trait.camel.apache.org/dependencies.enabled": "false",
+			"test": "2",
+		}
+
+		t.Run("run with initial image", func(t *testing.T) {
+			expectedImage := annotations1["trait.camel.apache.org/container.image"]
+
+			RegisterTestingT(t)
+
+			Expect(BindKameletTo(ns, bindingID, annotations1, from, to, emptyMap, emptyMap)()).
+				To(Succeed())
+			Eventually(IntegrationGeneration(ns, bindingID)).
+				Should(gstruct.PointTo(BeNumerically("==", 1)))
+			Eventually(Integration(ns, bindingID)).Should(WithTransform(Annotations, And(
+				HaveKeyWithValue("test", "1"))),
+				HaveKeyWithValue("trait.camel.apache.org/container.image", expectedImage))
+			Eventually(IntegrationStatusImage(ns, bindingID)).
+				Should(Equal(expectedImage))
+			Eventually(IntegrationPodPhase(ns, bindingID), TestTimeoutLong).
+				Should(Equal(corev1.PodRunning))
+			Eventually(IntegrationPodImage(ns, bindingID)).
+				Should(Equal(expectedImage))
+		})
+
+		t.Run("run with new image", func(t *testing.T) {
+			expectedImage := annotations2["trait.camel.apache.org/container.image"]
+
+			RegisterTestingT(t)
+
+			Expect(BindKameletTo(ns, bindingID, annotations2, from, to, emptyMap, emptyMap)()).
+				To(Succeed())
+			Eventually(IntegrationGeneration(ns, bindingID)).
+				Should(gstruct.PointTo(BeNumerically("==", 1)))
+			Eventually(Integration(ns, bindingID)).Should(WithTransform(Annotations, And(
+				HaveKeyWithValue("test", "2"))),
+				HaveKeyWithValue("trait.camel.apache.org/container.image", expectedImage))
+			Eventually(IntegrationStatusImage(ns, bindingID)).
+				Should(Equal(expectedImage))
+			Eventually(IntegrationPodPhase(ns, bindingID), TestTimeoutLong).
+				Should(Equal(corev1.PodRunning))
+			Eventually(IntegrationPodImage(ns, bindingID)).
+				Should(Equal(expectedImage))
+		})
+
+		// Cleanup
+		Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil())
+	})
+}
diff --git a/e2e/global/common/traits/deployment_test.go b/e2e/global/common/traits/deployment_test.go
new file mode 100644
index 000000000..9d9a23c22
--- /dev/null
+++ b/e2e/global/common/traits/deployment_test.go
@@ -0,0 +1,98 @@
+//go:build integration
+// +build integration
+
+// To enable compilation of this file in Goland, go to "Settings -> Go -> Vendoring & Build Tags -> Custom Tags" and add "integration"
+
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package traits
+
+import (
+	appsv1 "k8s.io/api/apps/v1"
+	"testing"
+
+	. "github.com/onsi/gomega"
+	. "github.com/onsi/gomega/gstruct"
+
+	corev1 "k8s.io/api/core/v1"
+
+	. "github.com/apache/camel-k/e2e/support"
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+)
+
+func TestRecreateDeploymentStrategyTrait(t *testing.T) {
+	WithNewTestNamespace(t, func(ns string) {
+		operatorID := "camel-k-trait-jolokia"
+		Expect(KamelInstallWithID(operatorID, ns).Execute()).To(Succeed())
+
+		t.Run("Run with Recreate Deployment Strategy", func(t *testing.T) {
+			Expect(KamelRunWithID(operatorID, ns, "files/Java.java",
+				"-t", "deployment.strategy="+string(appsv1.RecreateDeploymentStrategyType)).
+				Execute()).To(Succeed())
+
+			Eventually(IntegrationPodPhase(ns, "java"), TestTimeoutLong).Should(Equal(corev1.PodRunning))
+			Eventually(IntegrationConditionStatus(ns, "java", v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
+			Eventually(IntegrationLogs(ns, "java"), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
+
+			Eventually(Deployment(ns, "java"), TestTimeoutMedium).Should(PointTo(MatchFields(IgnoreExtras,
+				Fields{
+					"Spec": MatchFields(IgnoreExtras,
+						Fields{
+							"Strategy": MatchFields(IgnoreExtras,
+								Fields{
+									"Type": Equal(appsv1.RecreateDeploymentStrategyType),
+								}),
+						}),
+				}),
+			))
+
+			Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
+		})
+	})
+}
+
+func TestRollingUpdateDeploymentStrategyTrait(t *testing.T) {
+	WithNewTestNamespace(t, func(ns string) {
+		operatorID := "camel-k-trait-jolokia"
+		Expect(KamelInstallWithID(operatorID, ns).Execute()).To(Succeed())
+
+		t.Run("Run with RollingUpdate Deployment Strategy", func(t *testing.T) {
+			Expect(KamelRunWithID(operatorID, ns, "files/Java.java",
+				"-t", "deployment.strategy="+string(appsv1.RollingUpdateDeploymentStrategyType)).
+				Execute()).To(Succeed())
+
+			Eventually(IntegrationPodPhase(ns, "java"), TestTimeoutLong).Should(Equal(corev1.PodRunning))
+			Eventually(IntegrationConditionStatus(ns, "java", v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
+			Eventually(IntegrationLogs(ns, "java"), TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
+
+			Eventually(Deployment(ns, "java"), TestTimeoutMedium).Should(PointTo(MatchFields(IgnoreExtras,
+				Fields{
+					"Spec": MatchFields(IgnoreExtras,
+						Fields{
+							"Strategy": MatchFields(IgnoreExtras,
+								Fields{
+									"Type": Equal(appsv1.RollingUpdateDeploymentStrategyType),
+								}),
+						}),
+				}),
+			))
+
+			Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
+		})
+	})
+}
diff --git a/e2e/local/local_build_test.go b/e2e/local/local_build_test.go
index 2ea34f91b..2c0f40c6d 100644
--- a/e2e/local/local_build_test.go
+++ b/e2e/local/local_build_test.go
@@ -65,6 +65,7 @@ func TestLocalBuild(t *testing.T) {
 
 	kamelBuild := KamelWithContext(ctx, "local", "build", file, "--image", image)
 	kamelBuild.SetOut(pipew)
+	kamelBuild.SetErr(pipew)
 
 	msgTagged := "Successfully tagged"
 	logScanner := testutil.NewLogScanner(ctx, piper, msgTagged, image)
@@ -94,6 +95,7 @@ func TestLocalBuildWithTrait(t *testing.T) {
 
 	kamelBuild := KamelWithContext(ctx, "local", "build", file, "--image", image)
 	kamelBuild.SetOut(pipew)
+	kamelBuild.SetErr(pipew)
 
 	msgWarning := "Warning: traits are specified but don't take effect for local run: [jolokia.enabled=true]"
 	msgTagged := "Successfully tagged"
diff --git a/e2e/local/local_run_test.go b/e2e/local/local_run_test.go
index c703e3825..cef5112b1 100644
--- a/e2e/local/local_run_test.go
+++ b/e2e/local/local_run_test.go
@@ -23,7 +23,6 @@ package local
 import (
 	"context"
 	"io"
-	"os"
 	"strings"
 	"testing"
 
@@ -48,6 +47,7 @@ func TestLocalRun(t *testing.T) {
 
 	kamelRun := KamelWithContext(ctx, "local", "run", file)
 	kamelRun.SetOut(pipew)
+	kamelRun.SetErr(pipew)
 
 	logScanner := testutil.NewLogScanner(ctx, piper, "Magicstring!")
 
@@ -73,6 +73,7 @@ func TestLocalRunWithDependencies(t *testing.T) {
 
 	kamelRun := KamelWithContext(ctx, "local", "run", file, "-d", "camel-amqp")
 	kamelRun.SetOut(pipew)
+	kamelRun.SetErr(pipew)
 
 	logScanner := testutil.NewLogScanner(ctx, piper, "Magicstring!")
 
@@ -99,6 +100,7 @@ func TestLocalRunContainerize(t *testing.T) {
 
 	kamelRun := KamelWithContext(ctx, "local", "run", file, "--image", image, "--containerize")
 	kamelRun.SetOut(pipew)
+	kamelRun.SetErr(pipew)
 
 	logScanner := testutil.NewLogScanner(ctx, piper, "Magicstring!")
 
@@ -116,10 +118,6 @@ func TestLocalRunContainerize(t *testing.T) {
 func TestLocalRunIntegrationDirectory(t *testing.T) {
 	RegisterTestingT(t)
 
-	if os.Getenv("CI") == "true" {
-		t.Skip("TODO: Temporarily disabled as this test is flaky and hangs the test process")
-	}
-
 	ctx1, cancel1 := context.WithCancel(TestContext)
 	defer cancel1()
 
@@ -146,6 +144,7 @@ func TestLocalRunIntegrationDirectory(t *testing.T) {
 
 	kamelRun := KamelWithContext(ctx2, "local", "run", "--integration-directory", dir)
 	kamelRun.SetOut(pipew)
+	kamelRun.SetErr(pipew)
 
 	logScanner := testutil.NewLogScanner(ctx2, piper, "Magicstring!")
 
diff --git a/e2e/namespace/install/cli/dev_mode_test.go b/e2e/namespace/install/cli/dev_mode_test.go
index dd808e2c5..289af3697 100644
--- a/e2e/namespace/install/cli/dev_mode_test.go
+++ b/e2e/namespace/install/cli/dev_mode_test.go
@@ -28,9 +28,9 @@ import (
 	"io"
 	"io/ioutil"
 	"os"
+	"path/filepath"
 	"testing"
 	"time"
-	"path/filepath"
 
 	. "github.com/onsi/gomega"
 	"github.com/stretchr/testify/assert"
diff --git a/e2e/support/test_support.go b/e2e/support/test_support.go
index 1d690cfb1..509c3b3ff 100644
--- a/e2e/support/test_support.go
+++ b/e2e/support/test_support.go
@@ -545,6 +545,26 @@ func IntegrationSpecReplicas(ns string, name string) func() *int32 {
 	}
 }
 
+func IntegrationGeneration(ns string, name string) func() *int64 {
+	return func() *int64 {
+		it := Integration(ns, name)()
+		if it == nil {
+			return nil
+		}
+		return &it.Generation
+	}
+}
+
+func IntegrationObservedGeneration(ns string, name string) func() *int64 {
+	return func() *int64 {
+		it := Integration(ns, name)()
+		if it == nil {
+			return nil
+		}
+		return &it.Status.ObservedGeneration
+	}
+}
+
 func IntegrationStatusReplicas(ns string, name string) func() *int32 {
 	return func() *int32 {
 		it := Integration(ns, name)()
@@ -555,6 +575,26 @@ func IntegrationStatusReplicas(ns string, name string) func() *int32 {
 	}
 }
 
+func IntegrationStatusImage(ns string, name string) func() string {
+	return func() string {
+		it := Integration(ns, name)()
+		if it == nil {
+			return ""
+		}
+		return it.Status.Image
+	}
+}
+
+func IntegrationAnnotations(ns string, name string) func() map[string]string {
+	return func() map[string]string {
+		it := Integration(ns, name)()
+		if it == nil {
+			return map[string]string{}
+		}
+		return it.Annotations
+	}
+}
+
 func IntegrationCondition(ns string, name string, conditionType v1.IntegrationConditionType) func() *v1.IntegrationCondition {
 	return func() *v1.IntegrationCondition {
 		it := Integration(ns, name)()
@@ -603,6 +643,10 @@ func AssignIntegrationToOperator(ns, name, operator string) error {
 	return TestClient().Update(TestContext, it)
 }
 
+func Annotations(object metav1.Object) map[string]string {
+	return object.GetAnnotations()
+}
+
 func Lease(ns string, name string) func() *coordination.Lease {
 	return func() *coordination.Lease {
 		lease := coordination.Lease{}
diff --git a/pkg/cmd/local_util_test.go b/pkg/cmd/local/local_test.go
similarity index 93%
rename from pkg/cmd/local_util_test.go
rename to pkg/cmd/local/local_test.go
index 2f44490aa..207947241 100644
--- a/pkg/cmd/local_util_test.go
+++ b/pkg/cmd/local/local_test.go
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package cmd
+package local
 
 import (
 	"io/ioutil"
@@ -37,7 +37,7 @@ func TestValidatePropertyFiles_ShouldSucceed(t *testing.T) {
 	assert.Nil(t, ioutil.WriteFile(tmpFile1.Name(), []byte("key=value"), 0o400))
 
 	inputValues := []string{tmpFile1.Name()}
-	err = validatePropertyFiles(inputValues)
+	err = ValidatePropertyFiles(inputValues)
 
 	assert.Nil(t, err)
 }
@@ -53,7 +53,7 @@ func TestValidatePropertyFiles_ShouldFailNotAPropertiesFile(t *testing.T) {
 	assert.Nil(t, ioutil.WriteFile(tmpFile1.Name(), []byte("key=value"), 0o400))
 
 	inputValues := []string{tmpFile1.Name()}
-	err = validatePropertyFiles(inputValues)
+	err = ValidatePropertyFiles(inputValues)
 
 	assert.NotNil(t, err)
 	assert.True(t, strings.HasPrefix(err.Error(), "supported property files must have a .properties extension"))
@@ -61,7 +61,7 @@ func TestValidatePropertyFiles_ShouldFailNotAPropertiesFile(t *testing.T) {
 
 func TestValidatePropertyFiles_ShouldFailNotFound(t *testing.T) {
 	inputValues := []string{"/tmp/not-found.properties"}
-	err := validatePropertyFiles(inputValues)
+	err := ValidatePropertyFiles(inputValues)
 
 	assert.NotNil(t, err)
 	assert.True(t, strings.HasPrefix(err.Error(), "unable to access property file"))
diff --git a/pkg/cmd/run_test.go b/pkg/cmd/run_test.go
index d53516400..b02f37698 100644
--- a/pkg/cmd/run_test.go
+++ b/pkg/cmd/run_test.go
@@ -27,6 +27,7 @@ import (
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	traitv1 "github.com/apache/camel-k/pkg/apis/camel/v1/trait"
+	"github.com/apache/camel-k/pkg/cmd/source"
 	"github.com/apache/camel-k/pkg/platform"
 	"github.com/apache/camel-k/pkg/trait"
 	"github.com/apache/camel-k/pkg/util/test"
@@ -463,7 +464,7 @@ func TestRunBinaryResource(t *testing.T) {
 
 func TestRunBinaryCompressedResource(t *testing.T) {
 	data := []byte{1, 2, 3, 4}
-	base64Compressed, _ := compressToString(data)
+	base64Compressed, _ := source.CompressToString(data)
 	binaryResourceSpec, err := binaryOrTextResource("file.ext", data, "application/octet-stream", true, v1.ResourceTypeData, "")
 	assert.Nil(t, err)
 	assert.Equal(t, base64Compressed, binaryResourceSpec.Content)
@@ -485,7 +486,7 @@ func TestRunTextResource(t *testing.T) {
 
 func TestRunTextCompressedResource(t *testing.T) {
 	data := []byte("hello horld")
-	base64Compressed, _ := compressToString(data)
+	base64Compressed, _ := source.CompressToString(data)
 	textResourceSpec, err := binaryOrTextResource("file.ext", []byte("hello horld"), "text/plain", true, v1.ResourceTypeData, "")
 	assert.Nil(t, err)
 	assert.Equal(t, base64Compressed, textResourceSpec.Content)
diff --git a/pkg/cmd/util_content_test.go b/pkg/cmd/source/content_test.go
similarity index 83%
rename from pkg/cmd/util_content_test.go
rename to pkg/cmd/source/content_test.go
index a0548c0ed..1a3a9a7b6 100644
--- a/pkg/cmd/util_content_test.go
+++ b/pkg/cmd/source/content_test.go
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package cmd
+package source
 
 import (
 	"context"
@@ -31,7 +31,7 @@ import (
 )
 
 func TestRawContentFileMissing(t *testing.T) {
-	_, _, err := loadRawContent(context.Background(), "dsadas")
+	_, _, err := LoadRawContent(context.Background(), "dsadas")
 	assert.NotNil(t, err)
 }
 
@@ -44,10 +44,10 @@ func TestRawBinaryContentType(t *testing.T) {
 	assert.Nil(t, tmpFile.Close())
 	assert.Nil(t, ioutil.WriteFile(tmpFile.Name(), []byte{1, 2, 3, 4, 5, 6}, 0o400))
 
-	data, contentType, err := loadRawContent(context.Background(), tmpFile.Name())
+	data, contentType, err := LoadRawContent(context.Background(), tmpFile.Name())
 	assert.Nil(t, err)
 	assert.Equal(t, []byte{1, 2, 3, 4, 5, 6}, data)
-	assert.True(t, isBinary(contentType))
+	assert.True(t, IsBinary(contentType))
 }
 
 func TestRawApplicationContentType(t *testing.T) {
@@ -59,10 +59,10 @@ func TestRawApplicationContentType(t *testing.T) {
 	assert.Nil(t, tmpFile.Close())
 	assert.Nil(t, ioutil.WriteFile(tmpFile.Name(), []byte(`{"hello":"world"}`), 0o400))
 
-	data, contentType, err := loadRawContent(context.Background(), tmpFile.Name())
+	data, contentType, err := LoadRawContent(context.Background(), tmpFile.Name())
 	assert.Nil(t, err)
 	assert.Equal(t, `{"hello":"world"}`, string(data))
-	assert.False(t, isBinary(contentType))
+	assert.False(t, IsBinary(contentType))
 }
 
 func TestTextContentType(t *testing.T) {
@@ -74,10 +74,10 @@ func TestTextContentType(t *testing.T) {
 	assert.Nil(t, tmpFile.Close())
 	assert.Nil(t, ioutil.WriteFile(tmpFile.Name(), []byte(`{"hello":"world"}`), 0o400))
 
-	data, contentType, compressed, err := loadTextContent(context.Background(), tmpFile.Name(), false)
+	data, contentType, compressed, err := LoadTextContent(context.Background(), tmpFile.Name(), false)
 	assert.Nil(t, err)
 	assert.Equal(t, `{"hello":"world"}`, data)
-	assert.False(t, isBinary(contentType))
+	assert.False(t, IsBinary(contentType))
 	assert.False(t, compressed)
 }
 
@@ -90,17 +90,17 @@ func TestTextCompressed(t *testing.T) {
 	assert.Nil(t, tmpFile.Close())
 	assert.Nil(t, ioutil.WriteFile(tmpFile.Name(), []byte(`{"hello":"world"}`), 0o400))
 
-	data, contentType, compressed, err := loadTextContent(context.Background(), tmpFile.Name(), true)
+	data, contentType, compressed, err := LoadTextContent(context.Background(), tmpFile.Name(), true)
 	assert.Nil(t, err)
 	assert.NotEqual(t, `{"hello":"world"}`, data)
-	assert.False(t, isBinary(contentType))
+	assert.False(t, IsBinary(contentType))
 	assert.True(t, compressed)
 }
 
 func TestIsBinary(t *testing.T) {
-	assert.True(t, isBinary("image/jpeg"))
-	assert.True(t, isBinary("application/zip"))
-	assert.False(t, isBinary("text/plain"))
+	assert.True(t, IsBinary("image/jpeg"))
+	assert.True(t, IsBinary("application/zip"))
+	assert.False(t, IsBinary("text/plain"))
 }
 
 func TestContentHttp(t *testing.T) {
diff --git a/pkg/cmd/util_test.go b/pkg/cmd/source/util_test.go
similarity index 76%
rename from pkg/cmd/util_test.go
rename to pkg/cmd/source/util_test.go
index 2d8734b35..53de90c9b 100644
--- a/pkg/cmd/util_test.go
+++ b/pkg/cmd/source/util_test.go
@@ -18,7 +18,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package cmd
+package source
 
 import (
 	"testing"
@@ -27,8 +27,8 @@ import (
 )
 
 func TestCorrectFileValuesButNotFound(t *testing.T) {
-	value1, err1 := isLocalAndFileExists("c:\\test")
-	value2, err2 := isLocalAndFileExists("path/to/file")
+	value1, err1 := IsLocalAndFileExists("c:\\test")
+	value2, err2 := IsLocalAndFileExists("path/to/file")
 
 	// they are all not found, but it must not panic
 	assert.Nil(t, err1)
@@ -38,17 +38,17 @@ func TestCorrectFileValuesButNotFound(t *testing.T) {
 }
 
 func TestPermissionDenied(t *testing.T) {
-	value, err := isLocalAndFileExists("/root/test")
+	value, err := IsLocalAndFileExists("/root/test")
 	// must not panic because a permission error
 	assert.NotNil(t, err)
 	assert.False(t, value)
 }
 
 func TestSupportedScheme(t *testing.T) {
-	gistValue, err1 := isLocalAndFileExists("gist:some/gist/resource")
-	githubValue, err2 := isLocalAndFileExists("github:some/github/resource")
-	httpValue, err3 := isLocalAndFileExists("http://some/http/resource")
-	httpsValue, err4 := isLocalAndFileExists("https://some/https/resource")
+	gistValue, err1 := IsLocalAndFileExists("gist:some/gist/resource")
+	githubValue, err2 := IsLocalAndFileExists("github:some/github/resource")
+	httpValue, err3 := IsLocalAndFileExists("http://some/http/resource")
+	httpsValue, err4 := IsLocalAndFileExists("https://some/https/resource")
 
 	assert.Nil(t, err1)
 	assert.False(t, gistValue)
@@ -61,7 +61,7 @@ func TestSupportedScheme(t *testing.T) {
 }
 
 func TestUnSupportedScheme(t *testing.T) {
-	value, err := isLocalAndFileExists("bad_scheme:some/bad/resource")
+	value, err := IsLocalAndFileExists("bad_scheme:some/bad/resource")
 	// must not report an error
 	assert.Nil(t, err)
 	assert.False(t, value)
diff --git a/pkg/controller/integration/kits_test.go b/pkg/controller/integration/kits_test.go
index 506d26f96..694be1521 100644
--- a/pkg/controller/integration/kits_test.go
+++ b/pkg/controller/integration/kits_test.go
@@ -21,15 +21,17 @@ import (
 	"context"
 	"testing"
 
-	"github.com/stretchr/testify/assert"
-
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/utils/pointer"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	traitv1 "github.com/apache/camel-k/pkg/apis/camel/v1/trait"
+
+	"github.com/apache/camel-k/pkg/trait"
 	"github.com/apache/camel-k/pkg/util/log"
 	"github.com/apache/camel-k/pkg/util/test"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func TestLookupKitForIntegration_DiscardKitsInError(t *testing.T) {
@@ -277,7 +279,7 @@ func TestHasMatchingTraits_KitNoTraitShouldNotBePicked(t *testing.T) {
 	a := buildKitAction{}
 	a.InjectLogger(log.Log)
 
-	ok, err := hasMatchingTraits(integration.Spec.Traits, kit.Spec.Traits)
+	ok, err := trait.IntegrationAndKitHaveSameTraits(integration, kit)
 	assert.Nil(t, err)
 	assert.False(t, ok)
 }
@@ -332,7 +334,7 @@ func TestHasMatchingTraits_KitSameTraitShouldBePicked(t *testing.T) {
 	a := buildKitAction{}
 	a.InjectLogger(log.Log)
 
-	ok, err := hasMatchingTraits(integration.Spec.Traits, kit.Spec.Traits)
+	ok, err := trait.IntegrationAndKitHaveSameTraits(integration, kit)
 	assert.Nil(t, err)
 	assert.True(t, ok)
 }
diff --git a/pkg/trait/deployment_test.go b/pkg/trait/deployment_test.go
index 015cbf7de..d0235e5af 100644
--- a/pkg/trait/deployment_test.go
+++ b/pkg/trait/deployment_test.go
@@ -147,6 +147,94 @@ func TestApplyDeploymentTraitWithProgressDeadline(t *testing.T) {
 	assert.Equal(t, int32(120), *deployment.Spec.ProgressDeadlineSeconds)
 }
 
+func TestApplyDeploymentTraitWitRecresteStrategy(t *testing.T) {
+	deploymentTrait, environment := createNominalDeploymentTest()
+	maxSurge := 10
+
+	deploymentTrait.Strategy = appsv1.RecreateDeploymentStrategyType
+	deploymentTrait.RollingUpdateMaxSurge = &maxSurge
+
+	environment.Integration.Status.Phase = v1.IntegrationPhaseRunning
+
+	err := deploymentTrait.Apply(environment)
+
+	assert.Nil(t, err)
+
+	deployment := environment.Resources.GetDeployment(func(deployment *appsv1.Deployment) bool { return true })
+	assert.NotNil(t, deployment)
+	assert.Equal(t, "integration-name", deployment.Name)
+	assert.Equal(t, appsv1.RecreateDeploymentStrategyType, deployment.Spec.Strategy.Type)
+	assert.Nil(t, deployment.Spec.Strategy.RollingUpdate)
+}
+
+func TestApplyDeploymentTraitWitRollingUpdateStrategy(t *testing.T) {
+
+	t.Run("with defaults", func(t *testing.T) {
+		deploymentTrait, environment := createNominalDeploymentTest()
+
+		deploymentTrait.Strategy = appsv1.RollingUpdateDeploymentStrategyType
+		environment.Integration.Status.Phase = v1.IntegrationPhaseRunning
+
+		err := deploymentTrait.Apply(environment)
+
+		assert.Nil(t, err)
+
+		deployment := environment.Resources.GetDeployment(func(deployment *appsv1.Deployment) bool { return true })
+		assert.NotNil(t, deployment)
+		assert.Equal(t, "integration-name", deployment.Name)
+		assert.Equal(t, appsv1.RollingUpdateDeploymentStrategyType, deployment.Spec.Strategy.Type)
+		assert.Nil(t, deployment.Spec.Strategy.RollingUpdate)
+	})
+
+	t.Run("with surge", func(t *testing.T) {
+		deploymentTrait, environment := createNominalDeploymentTest()
+
+		maxSurge := 10
+
+		deploymentTrait.Strategy = appsv1.RollingUpdateDeploymentStrategyType
+		deploymentTrait.RollingUpdateMaxSurge = &maxSurge
+
+		environment.Integration.Status.Phase = v1.IntegrationPhaseRunning
+
+		err := deploymentTrait.Apply(environment)
+
+		assert.Nil(t, err)
+
+		deployment := environment.Resources.GetDeployment(func(deployment *appsv1.Deployment) bool { return true })
+		assert.NotNil(t, deployment)
+		assert.Equal(t, "integration-name", deployment.Name)
+		assert.Equal(t, appsv1.RollingUpdateDeploymentStrategyType, deployment.Spec.Strategy.Type)
+		assert.NotNil(t, deployment.Spec.Strategy.RollingUpdate)
+		assert.Nil(t, deployment.Spec.Strategy.RollingUpdate.MaxUnavailable)
+		assert.Equal(t, maxSurge, deployment.Spec.Strategy.RollingUpdate.MaxSurge.IntValue())
+	})
+
+	t.Run("with surge and unavailable", func(t *testing.T) {
+		deploymentTrait, environment := createNominalDeploymentTest()
+
+		maxSurge := 10
+		maxUnavailable := 11
+
+		deploymentTrait.Strategy = appsv1.RollingUpdateDeploymentStrategyType
+		deploymentTrait.RollingUpdateMaxSurge = &maxSurge
+		deploymentTrait.RollingUpdateMaxUnavailable = &maxUnavailable
+
+		environment.Integration.Status.Phase = v1.IntegrationPhaseRunning
+
+		err := deploymentTrait.Apply(environment)
+
+		assert.Nil(t, err)
+
+		deployment := environment.Resources.GetDeployment(func(deployment *appsv1.Deployment) bool { return true })
+		assert.NotNil(t, deployment)
+		assert.Equal(t, "integration-name", deployment.Name)
+		assert.Equal(t, appsv1.RollingUpdateDeploymentStrategyType, deployment.Spec.Strategy.Type)
+		assert.NotNil(t, deployment.Spec.Strategy.RollingUpdate)
+		assert.Equal(t, maxUnavailable, deployment.Spec.Strategy.RollingUpdate.MaxUnavailable.IntValue())
+		assert.Equal(t, maxSurge, deployment.Spec.Strategy.RollingUpdate.MaxSurge.IntValue())
+	})
+}
+
 func createNominalDeploymentTest() (*deploymentTrait, *Environment) {
 	trait, _ := newDeploymentTrait().(*deploymentTrait)
 	trait.Enabled = pointer.Bool(true)
diff --git a/pkg/trait/util_test.go b/pkg/trait/util_test.go
index c44a3e26b..7c19d9332 100644
--- a/pkg/trait/util_test.go
+++ b/pkg/trait/util_test.go
@@ -20,10 +20,12 @@ package trait
 import (
 	"testing"
 
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/utils/pointer"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	traitv1 "github.com/apache/camel-k/pkg/apis/camel/v1/trait"
+	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -56,7 +58,7 @@ func TestToTraitMap(t *testing.T) {
 			}),
 		},
 	}
-	expected := map[string]map[string]interface{}{
+	expected := Options{
 		"container": {
 			"enabled":         true,
 			"auto":            false,
@@ -201,3 +203,176 @@ func TestToTrait(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, expected, trait)
 }
+
+func TestSameTraits(t *testing.T) {
+	t.Run("empty traits", func(t *testing.T) {
+		oldKlb := &v1alpha1.KameletBinding{
+			Spec: v1alpha1.KameletBindingSpec{
+				Integration: &v1.IntegrationSpec{
+					Traits: v1.Traits{},
+				},
+			},
+		}
+		newKlb := &v1alpha1.KameletBinding{
+			Spec: v1alpha1.KameletBindingSpec{
+				Integration: &v1.IntegrationSpec{
+					Traits: v1.Traits{},
+				},
+			},
+		}
+
+		ok, err := KameletBindingsHaveSameTraits(oldKlb, newKlb)
+		assert.NoError(t, err)
+		assert.True(t, ok)
+	})
+
+	t.Run("same traits", func(t *testing.T) {
+		oldKlb := &v1alpha1.KameletBinding{
+			Spec: v1alpha1.KameletBindingSpec{
+				Integration: &v1.IntegrationSpec{
+					Traits: v1.Traits{
+						Container: &traitv1.ContainerTrait{
+							Image: "foo/bar:1",
+						},
+					},
+				},
+			},
+		}
+		newKlb := &v1alpha1.KameletBinding{
+			Spec: v1alpha1.KameletBindingSpec{
+				Integration: &v1.IntegrationSpec{
+					Traits: v1.Traits{
+						Container: &traitv1.ContainerTrait{
+							Image: "foo/bar:1",
+						},
+					},
+				},
+			},
+		}
+
+		ok, err := KameletBindingsHaveSameTraits(oldKlb, newKlb)
+		assert.NoError(t, err)
+		assert.True(t, ok)
+	})
+
+	t.Run("not same traits", func(t *testing.T) {
+		oldKlb := &v1alpha1.KameletBinding{
+			Spec: v1alpha1.KameletBindingSpec{
+				Integration: &v1.IntegrationSpec{
+					Traits: v1.Traits{
+						Container: &traitv1.ContainerTrait{
+							Image: "foo/bar:1",
+						},
+					},
+				},
+			},
+		}
+		newKlb := &v1alpha1.KameletBinding{
+			Spec: v1alpha1.KameletBindingSpec{
+				Integration: &v1.IntegrationSpec{
+					Traits: v1.Traits{
+						Owner: &traitv1.OwnerTrait{
+							TargetAnnotations: []string{"foo"},
+						},
+					},
+				},
+			},
+		}
+
+		ok, err := KameletBindingsHaveSameTraits(oldKlb, newKlb)
+		assert.NoError(t, err)
+		assert.False(t, ok)
+	})
+
+	t.Run("same traits with annotations", func(t *testing.T) {
+		oldKlb := &v1alpha1.KameletBinding{
+			Spec: v1alpha1.KameletBindingSpec{
+				Integration: &v1.IntegrationSpec{
+					Traits: v1.Traits{
+						Container: &traitv1.ContainerTrait{
+							Image: "foo/bar:1",
+						},
+					},
+				},
+			},
+		}
+		newKlb := &v1alpha1.KameletBinding{
+			ObjectMeta: metav1.ObjectMeta{
+				Annotations: map[string]string{
+					v1.TraitAnnotationPrefix + "container.image": "foo/bar:1",
+				},
+			},
+		}
+
+		ok, err := KameletBindingsHaveSameTraits(oldKlb, newKlb)
+		assert.NoError(t, err)
+		assert.True(t, ok)
+	})
+
+	t.Run("same traits with annotations only", func(t *testing.T) {
+		oldKlb := &v1alpha1.KameletBinding{
+			ObjectMeta: metav1.ObjectMeta{
+				Annotations: map[string]string{
+					v1.TraitAnnotationPrefix + "container.image": "foo/bar:1",
+				},
+			},
+		}
+		newKlb := &v1alpha1.KameletBinding{
+			ObjectMeta: metav1.ObjectMeta{
+				Annotations: map[string]string{
+					v1.TraitAnnotationPrefix + "container.image": "foo/bar:1",
+				},
+			},
+		}
+
+		ok, err := KameletBindingsHaveSameTraits(oldKlb, newKlb)
+		assert.NoError(t, err)
+		assert.True(t, ok)
+	})
+
+	t.Run("not same traits with annotations", func(t *testing.T) {
+		oldKlb := &v1alpha1.KameletBinding{
+			Spec: v1alpha1.KameletBindingSpec{
+				Integration: &v1.IntegrationSpec{
+					Traits: v1.Traits{
+						Container: &traitv1.ContainerTrait{
+							Image: "foo/bar:1",
+						},
+					},
+				},
+			},
+		}
+		newKlb := &v1alpha1.KameletBinding{
+			ObjectMeta: metav1.ObjectMeta{
+				Annotations: map[string]string{
+					v1.TraitAnnotationPrefix + "container.image": "foo/bar:2",
+				},
+			},
+		}
+
+		ok, err := KameletBindingsHaveSameTraits(oldKlb, newKlb)
+		assert.NoError(t, err)
+		assert.False(t, ok)
+	})
+
+	t.Run("not same traits with annotations only", func(t *testing.T) {
+		oldKlb := &v1alpha1.KameletBinding{
+			ObjectMeta: metav1.ObjectMeta{
+				Annotations: map[string]string{
+					v1.TraitAnnotationPrefix + "container.image": "foo/bar:1",
+				},
+			},
+		}
+		newKlb := &v1alpha1.KameletBinding{
+			ObjectMeta: metav1.ObjectMeta{
+				Annotations: map[string]string{
+					v1.TraitAnnotationPrefix + "container.image": "foo/bar:2",
+				},
+			},
+		}
+
+		ok, err := KameletBindingsHaveSameTraits(oldKlb, newKlb)
+		assert.NoError(t, err)
+		assert.False(t, ok)
+	})
+}