Repository: apache/camel-k
Pull request #3499 (2022-07-29 06:20:44)

fix(cli): more user-friendly error messages for kamel local subcommands
<!-- Description -->

Large overhaul of `kamel local` commands along with fix to #3028, which should make it easier for future enhancements and bug fixes for the commands.

<!--
Enter your extended release note in the below block. If the PR requires
additional action from users switching to the new release, include the string
"action required". If no release note is required, write "NONE". 

You can (optionally) mark this PR with labels "kind/bug" or "kind/feature" to make sure
the text is added to the right section of the release notes. 
-->

**Release Note**
```release-note
fix(cli): more user-friendly error messages for `kamel local` subcommands
```



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 639c013ac..42c2cd83f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,8 +6,10 @@
 
 **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)
 - 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)
@@ -54,9 +56,11 @@
 - 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)
 - OLM scorecard tests missing [\#3022](https://github.com/apache/camel-k/issues/3022)
@@ -76,6 +80,7 @@
 - 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)
@@ -102,6 +107,7 @@
 - Restful sample does not work [\#589](https://github.com/apache/camel-k/issues/589)
 - Improve IDE compatibility [\#564](https://github.com/apache/camel-k/issues/564)
 - create test suite [\#558](https://github.com/apache/camel-k/issues/558)
+- Refactor roles to simplify them [\#516](https://github.com/apache/camel-k/issues/516)
 - CRD's without cluster admin access / No direct network access from cluster [\#512](https://github.com/apache/camel-k/issues/512)
 - Improving kamel install [\#481](https://github.com/apache/camel-k/issues/481)
 - Allow to remap headers to cloudevents headers [\#361](https://github.com/apache/camel-k/issues/361)
@@ -1351,7 +1357,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/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/pages/running/dev-mode.adoc b/docs/modules/ROOT/pages/running/dev-mode.adoc
index 7f266f730..fc6f32af5 100644
--- a/docs/modules/ROOT/pages/running/dev-mode.adoc
+++ b/docs/modules/ROOT/pages/running/dev-mode.adoc
@@ -10,7 +10,7 @@ Dev mode is just a helper to let you be quicker during development.
 To enable dev mode, just add the `--dev` flag when running the integration:
 
 ```
-kamel run examples/Sample.java --dev
+kamel run examples/languages/Sample.java --dev
 ```
 
 The `--dev` flag deploys immediately the integration and shows the integration logs in the console. You can then change the code and see
@@ -21,7 +21,7 @@ The console follows automatically all redeploys of the integration.
 Here's an example of the output:
 
 ```
-$ kamel run examples/Sample.java --dev
+$ kamel run examples/languages/Sample.java --dev
 integration "sample" created
 integration "sample" in phase Initialization
 integration "sample" in phase Building Kit
diff --git a/docs/modules/ROOT/pages/running/local.adoc b/docs/modules/ROOT/pages/running/local.adoc
index 0cc1ea34a..899fc835e 100644
--- a/docs/modules/ROOT/pages/running/local.adoc
+++ b/docs/modules/ROOT/pages/running/local.adoc
@@ -67,7 +67,7 @@ To package an integration, use `kamel kit create` to create an `IntegrationKit`
 [[running-locally-build-integration]]
 === Building an integration
 
-To build an integration image locally, you at lease need to specify an image name with the `--image` option in addition to the integration files you want to build.
+To build an integration image locally, you at least need to specify an image name with the `--image` option in addition to the integration files you want to build.
 
 [source,console]
 ----
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/README.md b/helm/camel-k/README.md
index 17753d5e2..b834f2119 100644
--- a/helm/camel-k/README.md
+++ b/helm/camel-k/README.md
@@ -79,6 +79,8 @@ Camel K chart and their default values. The chart allows configuration of an `In
 | `platform.build.registry.insecure`     | Indicates if the registry is not secured                                  | true                           |
 | `platform.cluster`                     | The kind of Kubernetes cluster (Kubernetes or OpenShift)                  | `Kubernetes`                   |
 | `platform.profile`                     | The trait profile to use (Knative, Kubernetes or OpenShift)               | auto                           |
+| `operator.resources`                   | the resource requests and limits to use for the operator                  |                                |
+| `operator.securityContext`             | The (container-related) securityContext to use for the operato            |                                |
 
 ## Contributing
 
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/helm/camel-k/templates/operator.yaml b/helm/camel-k/templates/operator.yaml
index 9f606bc03..aac26c1c0 100644
--- a/helm/camel-k/templates/operator.yaml
+++ b/helm/camel-k/templates/operator.yaml
@@ -68,5 +68,6 @@ spec:
           ports:
             - containerPort: 8080
               name: metrics
-          resources: {}
+          resources: {{ .Values.operator.resources }}
+          securityContext: {{ .Values.operator.securityContext }}
       serviceAccountName: camel-k-operator
diff --git a/helm/camel-k/values.yaml b/helm/camel-k/values.yaml
index 002712829..2e8d7854f 100644
--- a/helm/camel-k/values.yaml
+++ b/helm/camel-k/values.yaml
@@ -24,6 +24,8 @@ fullnameOverride: ""
 
 operator:
   image: docker.io/apache/camel-k:1.10.0-SNAPSHOT
+  resources: {}
+  securityContext: {}
 
 platform:
   build:
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/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 878e90025..6d159f2e6 100644
--- a/script/Makefile
+++ b/script/Makefile
@@ -28,7 +28,7 @@ KUSTOMIZE_VERSION := v4.1.2
 OPM_VERSION := v1.21.0
 BASE_IMAGE := docker.io/adoptopenjdk/openjdk11:slim
 LOCAL_REPOSITORY := /tmp/artifacts/m2
-IMAGE_NAME := docker.io/apache/camel-k
+IMAGE_NAME ?= docker.io/apache/camel-k
 
 #
 # Situations when user wants to override
@@ -298,6 +298,9 @@ ifeq ($(shell uname -s 2>/dev/null || echo Unknown),Linux)
 else
 	go build $(GOFLAGS) -o kamel ./cmd/kamel/*.go
 endif
+ifeq ($(shell uname -m), arm64)
+	GOOS=linux GOARCH=arm64 go build $(GOFLAGS) -o kamel.linux.arm ./cmd/kamel/*.go
+endif
 
 build-resources:
 	./script/get_catalog.sh $(RUNTIME_VERSION) $(STAGING_RUNTIME_REPO)
@@ -385,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)
@@ -397,6 +402,9 @@ endif
 ifeq ($(shell uname -m), aarch64)
 	docker buildx build --platform=linux/arm64 -t $(CUSTOM_IMAGE):$(CUSTOM_VERSION) -f build/Dockerfile.arch .
 endif
+ifeq ($(shell uname -m), arm64)
+	docker buildx build --platform=linux/amd64 -t $(CUSTOM_IMAGE):$(CUSTOM_VERSION) -f build/Dockerfile.arch .
+endif
 
 images-dev: build kamel-overlay maven-overlay bundle-kamelets package-artifacts
 	@echo "####### Building Camel K operator container development image..."
@@ -412,6 +420,10 @@ endif
 ifeq ($(shell uname -m), aarch64)
 	docker buildx build --platform=linux/arm64 -t $(CUSTOM_IMAGE):$(CUSTOM_VERSION) -f build/Dockerfile.arch .
 endif
+ifeq ($(shell uname -m), arm64)
+	mv kamel.linux.arm build/_output/bin/kamel
+	docker buildx build --platform=linux/amd64 -t $(CUSTOM_IMAGE):$(CUSTOM_VERSION) -f build/Dockerfile.arch .
+endif
 
 images-push:
 	docker push $(CUSTOM_IMAGE):$(CUSTOM_VERSION)
@@ -508,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/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)
+	})
+}