Repository: apache/camel-k
Pull request #1259 (2020-02-10 16:36:28)

Buildah support
**Release Note**
```release-note
feat(build): Buildah support
```



Patch:
diff --git a/pkg/apis/camel/v1/build_types.go b/pkg/apis/camel/v1/build_types.go
index d27177fb6..204544232 100644
--- a/pkg/apis/camel/v1/build_types.go
+++ b/pkg/apis/camel/v1/build_types.go
@@ -35,7 +35,7 @@ type BuildSpec struct {
 // Task --
 type Task struct {
 	Builder *BuilderTask `json:"builder,omitempty"`
-	Kaniko  *KanikoTask  `json:"kaniko,omitempty"`
+	Image   *ImageTask   `json:"image,omitempty"`
 }
 
 // BaseTask --
@@ -46,18 +46,20 @@ type BaseTask struct {
 	VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"`
 }
 
-// ImageTask --
-type ImageTask struct {
-	BaseTask `json:",inline"`
-	Image    string          `json:"image,omitempty"`
-	Args     []string        `json:"args,omitempty"`
-	Env      []corev1.EnvVar `json:"env,omitempty"`
+// ContainerTask --
+type ContainerTask struct {
+	BaseTask   `json:",inline"`
+	Image      string          `json:"image,omitempty"`
+	Command    []string        `json:"command,omitempty"`
+	Args       []string        `json:"args,omitempty"`
+	Env        []corev1.EnvVar `json:"env,omitempty"`
+	WorkingDir string          `json:"workingDir,omitempty"`
 }
 
-// KanikoTask --
-type KanikoTask struct {
-	ImageTask  `json:",inline"`
-	BuiltImage string `json:"builtImage,omitempty"`
+// ImageTask --
+type ImageTask struct {
+	ContainerTask `json:",inline"`
+	BuiltImage    string `json:"builtImage,omitempty"`
 }
 
 // BuilderTask --
@@ -81,6 +83,7 @@ type BuilderTask struct {
 type BuildStatus struct {
 	Phase      BuildPhase       `json:"phase,omitempty"`
 	Image      string           `json:"image,omitempty"`
+	Digest     string           `json:"digest,omitempty"`
 	BaseImage  string           `json:"baseImage,omitempty"`
 	Artifacts  []Artifact       `json:"artifacts,omitempty"`
 	Error      string           `json:"error,omitempty"`
diff --git a/pkg/apis/camel/v1/build_types_support.go b/pkg/apis/camel/v1/build_types_support.go
index 85bc40594..19ee88037 100644
--- a/pkg/apis/camel/v1/build_types_support.go
+++ b/pkg/apis/camel/v1/build_types_support.go
@@ -26,8 +26,8 @@ import (
 func (t *Task) GetName() string {
 	if t.Builder != nil {
 		return t.Builder.Name
-	} else if t.Kaniko != nil {
-		return t.Kaniko.Name
+	} else if t.Image != nil {
+		return t.Image.Name
 	}
 	return ""
 }
diff --git a/pkg/apis/camel/v1/integrationplatform_types.go b/pkg/apis/camel/v1/integrationplatform_types.go
index dcda3fa41..ef721933e 100644
--- a/pkg/apis/camel/v1/integrationplatform_types.go
+++ b/pkg/apis/camel/v1/integrationplatform_types.go
@@ -130,7 +130,6 @@ type IntegrationPlatformBuildStrategy string
 const (
 	// IntegrationPlatformBuildStrategyRoutine performs the build in a routine
 	IntegrationPlatformBuildStrategyRoutine = "routine"
-
 	// IntegrationPlatformBuildStrategyPod performs the build in a pod
 	IntegrationPlatformBuildStrategyPod = "pod"
 )
@@ -139,11 +138,12 @@ const (
 type IntegrationPlatformBuildPublishStrategy string
 
 const (
-	// IntegrationPlatformBuildPublishStrategyS2I performs a OpenShift binary S2I build
-	IntegrationPlatformBuildPublishStrategyS2I = "S2I"
-
-	// IntegrationPlatformBuildPublishStrategyKaniko performs
+	// IntegrationPlatformBuildPublishStrategyBuildah
+	IntegrationPlatformBuildPublishStrategyBuildah = "Buildah"
+	// IntegrationPlatformBuildPublishStrategyKaniko
 	IntegrationPlatformBuildPublishStrategyKaniko = "Kaniko"
+	// IntegrationPlatformBuildPublishStrategyS2I
+	IntegrationPlatformBuildPublishStrategyS2I = "S2I"
 )
 
 // IntegrationPlatformPhase --
diff --git a/pkg/apis/camel/v1/zz_generated.deepcopy.go b/pkg/apis/camel/v1/zz_generated.deepcopy.go
index 59f7a18ff..dbe985841 100644
--- a/pkg/apis/camel/v1/zz_generated.deepcopy.go
+++ b/pkg/apis/camel/v1/zz_generated.deepcopy.go
@@ -494,6 +494,40 @@ func (in *ConfigurationSpec) DeepCopy() *ConfigurationSpec {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ContainerTask) DeepCopyInto(out *ContainerTask) {
+	*out = *in
+	in.BaseTask.DeepCopyInto(&out.BaseTask)
+	if in.Command != nil {
+		in, out := &in.Command, &out.Command
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.Args != nil {
+		in, out := &in.Args, &out.Args
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.Env != nil {
+		in, out := &in.Env, &out.Env
+		*out = make([]corev1.EnvVar, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerTask.
+func (in *ContainerTask) DeepCopy() *ContainerTask {
+	if in == nil {
+		return nil
+	}
+	out := new(ContainerTask)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *DataSpec) DeepCopyInto(out *DataSpec) {
 	*out = *in
@@ -548,19 +582,7 @@ func (in *FailureRecovery) DeepCopy() *FailureRecovery {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ImageTask) DeepCopyInto(out *ImageTask) {
 	*out = *in
-	in.BaseTask.DeepCopyInto(&out.BaseTask)
-	if in.Args != nil {
-		in, out := &in.Args, &out.Args
-		*out = make([]string, len(*in))
-		copy(*out, *in)
-	}
-	if in.Env != nil {
-		in, out := &in.Env, &out.Env
-		*out = make([]corev1.EnvVar, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
-		}
-	}
+	in.ContainerTask.DeepCopyInto(&out.ContainerTask)
 	return
 }
 
@@ -1114,23 +1136,6 @@ func (in *IntegrationStatus) DeepCopy() *IntegrationStatus {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *KanikoTask) DeepCopyInto(out *KanikoTask) {
-	*out = *in
-	in.ImageTask.DeepCopyInto(&out.ImageTask)
-	return
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KanikoTask.
-func (in *KanikoTask) DeepCopy() *KanikoTask {
-	if in == nil {
-		return nil
-	}
-	out := new(KanikoTask)
-	in.DeepCopyInto(out)
-	return out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *MavenArtifact) DeepCopyInto(out *MavenArtifact) {
 	*out = *in
@@ -1239,9 +1244,9 @@ func (in *Task) DeepCopyInto(out *Task) {
 		*out = new(BuilderTask)
 		(*in).DeepCopyInto(*out)
 	}
-	if in.Kaniko != nil {
-		in, out := &in.Kaniko, &out.Kaniko
-		*out = new(KanikoTask)
+	if in.Image != nil {
+		in, out := &in.Image, &out.Image
+		*out = new(ImageTask)
 		(*in).DeepCopyInto(*out)
 	}
 	return
diff --git a/pkg/builder/builder.go b/pkg/builder/builder.go
index 3aa6aa90d..8a036dda7 100644
--- a/pkg/builder/builder.go
+++ b/pkg/builder/builder.go
@@ -158,6 +158,7 @@ func (b *defaultBuilder) Run(build v1.BuilderTask) v1.BuildStatus {
 	if result.Phase != v1.BuildPhaseInterrupted {
 		result.BaseImage = c.BaseImage
 		result.Image = c.Image
+		result.Digest = c.Digest
 
 		if c.Error != nil {
 			result.Error = c.Error.Error()
diff --git a/pkg/builder/builder_steps.go b/pkg/builder/builder_steps.go
index c5cf3fd9a..0ecd89a4b 100644
--- a/pkg/builder/builder_steps.go
+++ b/pkg/builder/builder_steps.go
@@ -19,6 +19,7 @@ package builder
 
 import (
 	"fmt"
+	"io/ioutil"
 	"os"
 	"path"
 	"reflect"
@@ -30,9 +31,9 @@ import (
 	k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	"github.com/apache/camel-k/pkg/util"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
 	"github.com/apache/camel-k/pkg/util/maven"
-	"github.com/apache/camel-k/pkg/util/tar"
 )
 
 var stepsByID = make(map[string]Step)
@@ -46,8 +47,8 @@ type steps struct {
 	GenerateProjectSettings Step
 	InjectDependencies      Step
 	SanitizeDependencies    Step
-	StandardPackager        Step
-	IncrementalPackager     Step
+	StandardImageContext    Step
+	IncrementalImageContext Step
 }
 
 // Steps --
@@ -68,13 +69,13 @@ var Steps = steps{
 		ProjectGenerationPhase+3,
 		sanitizeDependencies,
 	),
-	StandardPackager: NewStep(
+	StandardImageContext: NewStep(
 		ApplicationPackagePhase,
-		standardPackager,
+		standardImageContext,
 	),
-	IncrementalPackager: NewStep(
+	IncrementalImageContext: NewStep(
 		ApplicationPackagePhase,
-		incrementalPackager,
+		incrementalImageContext,
 	),
 }
 
@@ -84,7 +85,7 @@ var DefaultSteps = []Step{
 	Steps.GenerateProjectSettings,
 	Steps.InjectDependencies,
 	Steps.SanitizeDependencies,
-	Steps.IncrementalPackager,
+	Steps.IncrementalImageContext,
 }
 
 // RegisterSteps --
@@ -261,21 +262,21 @@ func sanitizeDependencies(ctx *Context) error {
 
 type artifactsSelector func(ctx *Context) error
 
-func standardPackager(ctx *Context) error {
-	return packager(ctx, func(ctx *Context) error {
+func standardImageContext(ctx *Context) error {
+	return imageContext(ctx, func(ctx *Context) error {
 		ctx.SelectedArtifacts = ctx.Artifacts
 
 		return nil
 	})
 }
 
-func incrementalPackager(ctx *Context) error {
+func incrementalImageContext(ctx *Context) error {
 	if ctx.HasRequiredImage() {
 		//
 		// If the build requires a specific image, don't try to determine the
 		// base image using artifact so just use the standard packages
 		//
-		return standardPackager(ctx)
+		return standardImageContext(ctx)
 	}
 
 	images, err := listPublishedImages(ctx)
@@ -283,7 +284,7 @@ func incrementalPackager(ctx *Context) error {
 		return err
 	}
 
-	return packager(ctx, func(ctx *Context) error {
+	return imageContext(ctx, func(ctx *Context) error {
 		ctx.SelectedArtifacts = ctx.Artifacts
 
 		bestImage, commonLibs := findBestImage(images, ctx.Artifacts)
@@ -303,43 +304,44 @@ func incrementalPackager(ctx *Context) error {
 	})
 }
 
-func packager(ctx *Context, selector artifactsSelector) error {
+func imageContext(ctx *Context, selector artifactsSelector) error {
 	err := selector(ctx)
 	if err != nil {
 		return err
 	}
 
-	tarFileName := path.Join(ctx.Path, "package", "occi.tar")
-	tarFileDir := path.Dir(tarFileName)
+	contextDir := path.Join(ctx.Path, "context")
 
-	err = os.MkdirAll(tarFileDir, 0777)
+	err = os.MkdirAll(contextDir, 0777)
 	if err != nil {
 		return err
 	}
 
-	tarAppender, err := tar.NewAppender(tarFileName)
-	if err != nil {
-		return err
-	}
-	defer tarAppender.Close()
-
 	for _, entry := range ctx.SelectedArtifacts {
-		_, tarFileName := path.Split(entry.Target)
-		tarFilePath := path.Dir(entry.Target)
-
-		_, err = tarAppender.AddFileWithName(tarFileName, entry.Location, tarFilePath)
+		_, err := util.CopyFile(entry.Location, path.Join(contextDir, entry.Target))
 		if err != nil {
 			return err
 		}
 	}
 
 	for _, entry := range ctx.Resources {
-		if err := tarAppender.AddData(entry.Content, entry.Target); err != nil {
-			return err
+		filePath, fileName := path.Split(entry.Target)
+		if err := util.WriteFileWithContent(path.Join(contextDir, filePath), fileName, entry.Content); err != nil {
+			return nil
 		}
 	}
 
-	ctx.Archive = tarFileName
+	// #nosec G202
+	dockerfile := []byte(`
+		FROM ` + ctx.BaseImage + `
+		ADD . /deployments
+		USER 1000
+	`)
+
+	err = ioutil.WriteFile(path.Join(contextDir, "Dockerfile"), dockerfile, 0777)
+	if err != nil {
+		return err
+	}
 
 	return nil
 }
@@ -409,8 +411,7 @@ func findBestImage(images []publishedImage, artifacts []v1.Artifact) (publishedI
 		surplus := len(image.Artifacts) - numCommonLibs
 
 		if numCommonLibs != len(image.Artifacts) && surplus >= numCommonLibs/3 {
-			// Heuristic approach: if there are too many unrelated libraries, just use
-			// the base image
+			// Heuristic approach: if there are too many unrelated libraries, just use the base image
 			continue
 		}
 
diff --git a/pkg/builder/builder_types.go b/pkg/builder/builder_types.go
index d9bb40ae1..d7bdda90d 100644
--- a/pkg/builder/builder_types.go
+++ b/pkg/builder/builder_types.go
@@ -104,12 +104,12 @@ type Context struct {
 	Build             v1.BuilderTask
 	BaseImage         string
 	Image             string
+	Digest            string
 	Error             error
 	Namespace         string
 	Path              string
 	Artifacts         []v1.Artifact
 	SelectedArtifacts []v1.Artifact
-	Archive           string
 	Resources         []Resource
 
 	Maven struct {
diff --git a/pkg/builder/kaniko/kaniko.go b/pkg/builder/kaniko/kaniko.go
index b61638d03..4cbb532a1 100644
--- a/pkg/builder/kaniko/kaniko.go
+++ b/pkg/builder/kaniko/kaniko.go
@@ -17,30 +17,5 @@ limitations under the License.
 
 package kaniko
 
-import (
-	"github.com/apache/camel-k/pkg/builder"
-)
-
-func init() {
-	builder.RegisterSteps(Steps)
-}
-
-type steps struct {
-	Publisher builder.Step
-}
-
-// Steps --
-var Steps = steps{
-	Publisher: builder.NewStep(
-		builder.ApplicationPublishPhase,
-		publisher,
-	),
-}
-
-// KanikoSteps --
-var KanikoSteps = []builder.Step{
-	Steps.Publisher,
-}
-
 // CacheDir is the cache directory for Kaniko builds (mounted into the Kaniko pod)
 const CacheDir = "/kaniko/cache"
diff --git a/pkg/builder/kaniko/publisher.go b/pkg/builder/kaniko/publisher.go
deleted file mode 100644
index f34f2fb2c..000000000
--- a/pkg/builder/kaniko/publisher.go
+++ /dev/null
@@ -1,54 +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 kaniko
-
-import (
-	"io/ioutil"
-	"os"
-	"path"
-
-	"github.com/apache/camel-k/pkg/builder"
-	"github.com/apache/camel-k/pkg/util/tar"
-)
-
-func publisher(ctx *builder.Context) error {
-	baseDir, _ := path.Split(ctx.Archive)
-	contextDir := path.Join(baseDir, "context")
-
-	err := os.MkdirAll(contextDir, 0777)
-	if err != nil {
-		return err
-	}
-
-	if err := tar.Extract(ctx.Archive, contextDir); err != nil {
-		return err
-	}
-
-	// #nosec G202
-	dockerFileContent := []byte(`
-		FROM ` + ctx.BaseImage + `
-		ADD . /deployments
-	`)
-
-	err = ioutil.WriteFile(path.Join(contextDir, "Dockerfile"), dockerFileContent, 0777)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
diff --git a/pkg/builder/s2i/publisher.go b/pkg/builder/s2i/publisher.go
index 149321d07..ae86de844 100644
--- a/pkg/builder/s2i/publisher.go
+++ b/pkg/builder/s2i/publisher.go
@@ -18,7 +18,6 @@ limitations under the License.
 package s2i
 
 import (
-	"fmt"
 	"io/ioutil"
 	"os"
 	"path"
@@ -38,17 +37,10 @@ import (
 	"github.com/apache/camel-k/pkg/builder"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
 	"github.com/apache/camel-k/pkg/util/kubernetes/customclient"
+	"github.com/apache/camel-k/pkg/util/zip"
 )
 
 func publisher(ctx *builder.Context) error {
-	// We may want to unify the Dockerfile between build strategies, i.e., between Kaniko and S2I
-	// #nosec G202
-	dockerfile := `
-		FROM ` + ctx.BaseImage + `
-		ADD . /deployments
-		USER 1000
-	`
-
 	bc := buildv1.BuildConfig{
 		TypeMeta: metav1.TypeMeta{
 			APIVersion: buildv1.GroupVersion.String(),
@@ -64,8 +56,7 @@ func publisher(ctx *builder.Context) error {
 		Spec: buildv1.BuildConfigSpec{
 			CommonSpec: buildv1.CommonSpec{
 				Source: buildv1.BuildSource{
-					Type:       buildv1.BuildSourceBinary,
-					Dockerfile: &dockerfile,
+					Type: buildv1.BuildSourceBinary,
 				},
 				Strategy: buildv1.BuildStrategy{
 					DockerStrategy: &buildv1.DockerBuildStrategy{},
@@ -119,13 +110,18 @@ func publisher(ctx *builder.Context) error {
 		return errors.Wrap(err, "cannot create image stream")
 	}
 
-	resource, err := ioutil.ReadFile(ctx.Archive)
+	archive := path.Join(ctx.Path, "archive.zip")
+	err = zip.Directory(path.Join(ctx.Path, "context"), archive)
 	if err != nil {
-		return errors.Wrap(err, "cannot fully read tar file "+ctx.Archive)
+		return errors.Wrap(err, "cannot zip context directory")
 	}
 
-	baseDir, _ := path.Split(ctx.Archive)
-	defer os.RemoveAll(baseDir)
+	resource, err := ioutil.ReadFile(archive)
+	if err != nil {
+		return errors.Wrap(err, "cannot fully read zip file "+archive)
+	}
+
+	defer os.RemoveAll(ctx.Path)
 
 	restClient, err := customclient.GetClientFor(ctx.Client, "build.openshift.io", "v1")
 	if err != nil {
@@ -155,12 +151,11 @@ func publisher(ctx *builder.Context) error {
 		return errors.Wrap(err, "cannot unmarshal instantiated binary response")
 	}
 
-	var sha string
 	err = kubernetes.WaitCondition(ctx.C, ctx.Client, &ocbuild, func(obj interface{}) (bool, error) {
 		if val, ok := obj.(*buildv1.Build); ok {
 			if val.Status.Phase == buildv1.BuildPhaseComplete {
 				if val.Status.Output.To != nil {
-					sha = val.Status.Output.To.ImageDigest
+					ctx.Digest = val.Status.Output.To.ImageDigest
 				}
 				return true, nil
 			} else if val.Status.Phase == buildv1.BuildPhaseCancelled ||
@@ -189,14 +184,7 @@ func publisher(ctx *builder.Context) error {
 		return errors.New("dockerImageRepository not available in ImageStream")
 	}
 
-	var image string
-	if sha != "" {
-		image = fmt.Sprintf("%s@%s", is.Status.DockerImageRepository, sha)
-	} else {
-		// fallback to using tag
-		image = fmt.Sprintf("%s:%s", is.Status.DockerImageRepository, ctx.Build.Meta.ResourceVersion)
-	}
-	ctx.Image = image
+	ctx.Image = is.Status.DockerImageRepository + ":" + ctx.Build.Meta.ResourceVersion
 
 	return nil
 }
diff --git a/pkg/controller/build/monitor_pod.go b/pkg/controller/build/monitor_pod.go
index 8d7a11b5b..44c13c235 100644
--- a/pkg/controller/build/monitor_pod.go
+++ b/pkg/controller/build/monitor_pod.go
@@ -68,8 +68,15 @@ func (action *monitorPodAction) Handle(ctx context.Context, build *v1.Build) (*v
 		build.Status.Phase = v1.BuildPhaseSucceeded
 		build.Status.Duration = metav1.Now().Sub(build.Status.StartedAt.Time).String()
 		for _, task := range build.Spec.Tasks {
-			if task.Kaniko != nil {
-				build.Status.Image = task.Kaniko.BuiltImage
+			if task.Image != nil {
+				build.Status.Image = task.Image.BuiltImage
+				break
+			}
+		}
+		// Reconcile image digest from build container status if available
+		for _, container := range pod.Status.ContainerStatuses {
+			if container.Name == "buildah" {
+				build.Status.Digest = container.State.Terminated.Message
 				break
 			}
 		}
diff --git a/pkg/controller/build/schedule_pod.go b/pkg/controller/build/schedule_pod.go
index 85871ceef..08b773a5a 100644
--- a/pkg/controller/build/schedule_pod.go
+++ b/pkg/controller/build/schedule_pod.go
@@ -142,8 +142,8 @@ func (action *schedulePodAction) newBuildPod(ctx context.Context, build *v1.Buil
 				action.operatorImage = operatorImage
 			}
 			action.addBuilderTaskToPod(build, task.Builder, pod)
-		} else if task.Kaniko != nil {
-			action.addKanikoTaskToPod(task.Kaniko, pod)
+		} else if task.Image != nil {
+			action.addImageTaskToPod(task.Image, pod)
 		}
 	}
 
@@ -175,13 +175,15 @@ func (action *schedulePodAction) addBuilderTaskToPod(build *v1.Build, task *v1.B
 	action.addBaseTaskToPod(&task.BaseTask, pod)
 }
 
-func (action *schedulePodAction) addKanikoTaskToPod(task *v1.KanikoTask, pod *corev1.Pod) {
+func (action *schedulePodAction) addImageTaskToPod(task *v1.ImageTask, pod *corev1.Pod) {
 	pod.Spec.InitContainers = append(pod.Spec.InitContainers, corev1.Container{
 		Name:            task.Name,
 		Image:           task.Image,
 		ImagePullPolicy: corev1.PullIfNotPresent,
+		Command:         task.Command,
 		Args:            task.Args,
 		Env:             task.Env,
+		WorkingDir:      task.WorkingDir,
 		VolumeMounts:    task.VolumeMounts,
 	})
 
diff --git a/pkg/controller/integrationkit/build.go b/pkg/controller/integrationkit/build.go
index 0b3fe26cc..aa1f61d8d 100644
--- a/pkg/controller/integrationkit/build.go
+++ b/pkg/controller/integrationkit/build.go
@@ -20,6 +20,7 @@ package integrationkit
 import (
 	"context"
 	"fmt"
+	"strings"
 
 	"github.com/pkg/errors"
 
@@ -141,6 +142,20 @@ func (action *buildAction) handleBuildRunning(ctx context.Context, kit *v1.Integ
 
 		kit.Status.BaseImage = build.Status.BaseImage
 		kit.Status.Image = build.Status.Image
+
+		// Address the image by repository digest instead of tag if possible
+		if build.Status.Digest != "" {
+			image := kit.Status.Image
+			i := strings.LastIndex(image, ":")
+			if i > 0 {
+				image = image[:i]
+			}
+			kit.Status.Image = fmt.Sprintf("%s@%s", image, build.Status.Digest)
+		} else {
+			// otherwise rely on repository tag
+			kit.Status.Image = build.Status.Image
+		}
+
 		kit.Status.Phase = v1.IntegrationKitPhaseReady
 		kit.Status.Artifacts = make([]v1.Artifact, 0, len(build.Status.Artifacts))
 
diff --git a/pkg/platform/defaults.go b/pkg/platform/defaults.go
index 28cd00168..37307cda2 100644
--- a/pkg/platform/defaults.go
+++ b/pkg/platform/defaults.go
@@ -22,15 +22,16 @@ import (
 	"fmt"
 	"time"
 
+	corev1 "k8s.io/api/core/v1"
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/client"
 	"github.com/apache/camel-k/pkg/util/defaults"
 	"github.com/apache/camel-k/pkg/util/log"
 	"github.com/apache/camel-k/pkg/util/maven"
 	"github.com/apache/camel-k/pkg/util/openshift"
-	corev1 "k8s.io/api/core/v1"
-	k8serrors "k8s.io/apimachinery/pkg/api/errors"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 // ConfigureDefaults fills with default values all missing details about the integration platform.
@@ -57,7 +58,7 @@ func ConfigureDefaults(ctx context.Context, c client.Client, p *v1.IntegrationPl
 		if p.Status.Cluster == v1.IntegrationPlatformClusterOpenShift {
 			p.Status.Build.PublishStrategy = v1.IntegrationPlatformBuildPublishStrategyS2I
 		} else {
-			p.Status.Build.PublishStrategy = v1.IntegrationPlatformBuildPublishStrategyKaniko
+			p.Status.Build.PublishStrategy = v1.IntegrationPlatformBuildPublishStrategyBuildah
 		}
 	}
 
@@ -67,8 +68,8 @@ func ConfigureDefaults(ctx context.Context, c client.Client, p *v1.IntegrationPl
 			// The only global strategy we have for now
 			p.Status.Build.BuildStrategy = v1.IntegrationPlatformBuildStrategyPod
 		} else {
-			if p.Status.Build.PublishStrategy == v1.IntegrationPlatformBuildPublishStrategyKaniko {
-				// The build output has to be shared with Kaniko via a persistent volume
+			if p.Status.Build.PublishStrategy == v1.IntegrationPlatformBuildPublishStrategyBuildah {
+				// The build output has to be shared via a volume
 				p.Status.Build.BuildStrategy = v1.IntegrationPlatformBuildStrategyPod
 			} else {
 				p.Status.Build.BuildStrategy = v1.IntegrationPlatformBuildStrategyRoutine
@@ -81,7 +82,7 @@ func ConfigureDefaults(ctx context.Context, c client.Client, p *v1.IntegrationPl
 		return err
 	}
 
-	if verbose && p.Status.Build.PublishStrategy == v1.IntegrationPlatformBuildPublishStrategyKaniko && p.Status.Build.Registry.Address == "" {
+	if verbose && p.Status.Build.PublishStrategy != v1.IntegrationPlatformBuildPublishStrategyS2I && p.Status.Build.Registry.Address == "" {
 		log.Log.Info("No registry specified for publishing images")
 	}
 
diff --git a/pkg/platform/platform.go b/pkg/platform/platform.go
index f1b87653c..a025088b3 100644
--- a/pkg/platform/platform.go
+++ b/pkg/platform/platform.go
@@ -110,13 +110,3 @@ func GetProfile(p *v1.IntegrationPlatform) v1.TraitProfile {
 	}
 	return ""
 }
-
-// SupportsS2iPublishStrategy --
-func SupportsS2iPublishStrategy(p *v1.IntegrationPlatform) bool {
-	return p.Status.Build.PublishStrategy == v1.IntegrationPlatformBuildPublishStrategyS2I
-}
-
-// SupportsKanikoPublishStrategy --
-func SupportsKanikoPublishStrategy(p *v1.IntegrationPlatform) bool {
-	return p.Status.Build.PublishStrategy == v1.IntegrationPlatformBuildPublishStrategyKaniko && p.Status.Build.Registry.Address != ""
-}
diff --git a/pkg/trait/builder.go b/pkg/trait/builder.go
index b6c38b9a6..e0eca9c21 100644
--- a/pkg/trait/builder.go
+++ b/pkg/trait/builder.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 	"path"
 	"strconv"
+	"strings"
 
 	"github.com/pkg/errors"
 
@@ -33,7 +34,6 @@ import (
 	"github.com/apache/camel-k/pkg/builder/kaniko"
 	"github.com/apache/camel-k/pkg/builder/runtime"
 	"github.com/apache/camel-k/pkg/builder/s2i"
-	"github.com/apache/camel-k/pkg/platform"
 	"github.com/apache/camel-k/pkg/util/defaults"
 )
 
@@ -55,6 +55,16 @@ func newBuilderTrait() *builderTrait {
 	}
 }
 
+// IsPlatformTrait overrides base class method
+func (t *builderTrait) IsPlatformTrait() bool {
+	return true
+}
+
+// InfluencesKit overrides base class method
+func (t *builderTrait) InfluencesKit() bool {
+	return true
+}
+
 func (t *builderTrait) Configure(e *Environment) (bool, error) {
 	if t.Enabled != nil && !*t.Enabled {
 		return false, nil
@@ -67,23 +77,21 @@ func (t *builderTrait) Apply(e *Environment) error {
 	builderTask := t.builderTask(e)
 	e.BuildTasks = append(e.BuildTasks, v1.Task{Builder: builderTask})
 
-	if platform.SupportsKanikoPublishStrategy(e.Platform) {
-		kanikoTask, err := t.kanikoTask(e)
+	switch e.Platform.Status.Build.PublishStrategy {
+
+	case v1.IntegrationPlatformBuildPublishStrategyBuildah:
+		imageTask, err := t.buildahTask(e)
 		if err != nil {
 			return err
 		}
+		t.addVolumeMounts(builderTask, imageTask)
+		e.BuildTasks = append(e.BuildTasks, v1.Task{Image: imageTask})
 
-		mount := corev1.VolumeMount{Name: "camel-k-builder", MountPath: builderDir}
-		builderTask.VolumeMounts = append(builderTask.VolumeMounts, mount)
-		kanikoTask.VolumeMounts = append(kanikoTask.VolumeMounts, mount)
-
-		// Use an emptyDir volume to coordinate the Camel Maven build and the Kaniko image build
-		builderTask.Volumes = append(builderTask.Volumes, corev1.Volume{
-			Name: "camel-k-builder",
-			VolumeSource: corev1.VolumeSource{
-				EmptyDir: &corev1.EmptyDirVolumeSource{},
-			},
-		})
+	case v1.IntegrationPlatformBuildPublishStrategyKaniko:
+		imageTask, err := t.kanikoTask(e)
+		if err != nil {
+			return err
+		}
 
 		if e.Platform.Status.Build.IsKanikoCacheEnabled() {
 			// Co-locate with the Kaniko warmer pod for sharing the host path volume as the current
@@ -109,7 +117,7 @@ func (t *builderTrait) Apply(e *Environment) error {
 			}
 
 			// Use node affinity with the Kaniko warmer pod node name
-			kanikoTask.Affinity = &corev1.Affinity{
+			imageTask.Affinity = &corev1.Affinity{
 				NodeAffinity: &corev1.NodeAffinity{
 					RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
 						NodeSelectorTerms: []corev1.NodeSelectorTerm{
@@ -127,7 +135,7 @@ func (t *builderTrait) Apply(e *Environment) error {
 				},
 			}
 			// Mount the PV used to warm the Kaniko cache into the Kaniko image build
-			kanikoTask.Volumes = append(kanikoTask.Volumes, corev1.Volume{
+			imageTask.Volumes = append(imageTask.Volumes, corev1.Volume{
 				Name: "kaniko-cache",
 				VolumeSource: corev1.VolumeSource{
 					PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
@@ -135,26 +143,31 @@ func (t *builderTrait) Apply(e *Environment) error {
 					},
 				},
 			})
-			kanikoTask.VolumeMounts = append(kanikoTask.VolumeMounts, corev1.VolumeMount{
+			imageTask.VolumeMounts = append(imageTask.VolumeMounts, corev1.VolumeMount{
 				Name:      "kaniko-cache",
 				MountPath: kaniko.CacheDir,
 			})
 		}
 
-		e.BuildTasks = append(e.BuildTasks, v1.Task{Kaniko: kanikoTask})
+		t.addVolumeMounts(builderTask, imageTask)
+		e.BuildTasks = append(e.BuildTasks, v1.Task{Image: imageTask})
 	}
 
 	return nil
 }
 
-// IsPlatformTrait overrides base class method
-func (t *builderTrait) IsPlatformTrait() bool {
-	return true
-}
+func (t *builderTrait) addVolumeMounts(builderTask *v1.BuilderTask, imageTask *v1.ImageTask) {
+	mount := corev1.VolumeMount{Name: "camel-k-builder", MountPath: builderDir}
+	builderTask.VolumeMounts = append(builderTask.VolumeMounts, mount)
+	imageTask.VolumeMounts = append(imageTask.VolumeMounts, mount)
 
-// InfluencesKit overrides base class method
-func (t *builderTrait) InfluencesKit() bool {
-	return true
+	// Use an emptyDir volume to coordinate the Maven build and the image build
+	builderTask.Volumes = append(builderTask.Volumes, corev1.Volume{
+		Name: "camel-k-builder",
+		VolumeSource: corev1.VolumeSource{
+			EmptyDir: &corev1.EmptyDirVolumeSource{},
+		},
+	})
 }
 
 func (t *builderTrait) builderTask(e *Environment) *v1.BuilderTask {
@@ -175,11 +188,12 @@ func (t *builderTrait) builderTask(e *Environment) *v1.BuilderTask {
 		Maven:      e.Platform.Status.Build.Maven,
 	}
 
-	if platform.SupportsS2iPublishStrategy(e.Platform) {
-		task.Steps = append(task.Steps, builder.StepIDsFor(s2i.S2iSteps...)...)
-	} else if platform.SupportsKanikoPublishStrategy(e.Platform) {
-		task.Steps = append(task.Steps, builder.StepIDsFor(kaniko.KanikoSteps...)...)
+	switch e.Platform.Status.Build.PublishStrategy {
+	case v1.IntegrationPlatformBuildPublishStrategyBuildah, v1.IntegrationPlatformBuildPublishStrategyKaniko:
 		task.BuildDir = path.Join(builderDir, e.IntegrationKit.Name)
+
+	case v1.IntegrationPlatformBuildPublishStrategyS2I:
+		task.Steps = append(task.Steps, builder.StepIDsFor(s2i.S2iSteps...)...)
 	}
 
 	quarkus := e.Catalog.GetTrait("quarkus").(*quarkusTrait)
@@ -194,114 +208,102 @@ func (t *builderTrait) builderTask(e *Environment) *v1.BuilderTask {
 	return task
 }
 
-func (t *builderTrait) kanikoTask(e *Environment) (*v1.KanikoTask, error) {
-	organization := e.Platform.Status.Build.Registry.Organization
-	if organization == "" {
-		organization = e.Platform.Namespace
+func (t *builderTrait) buildahTask(e *Environment) (*v1.ImageTask, error) {
+	image := getImageName(e)
+
+	bud := []string{
+		"buildah",
+		"bud",
+		"--storage-driver=vfs",
+		"-f",
+		"Dockerfile",
+		"-t",
+		image,
+		".",
+	}
+
+	push := []string{
+		"buildah",
+		"push",
+		"--storage-driver=vfs",
+		"--digestfile=/dev/termination-log",
+		image,
+		"docker://" + image,
+	}
+
+	if t.Verbose {
+		bud = append(bud[:2], append([]string{"--log-level=debug"}, bud[2:]...)...)
+		push = append(push[:2], append([]string{"--log-level=debug"}, push[2:]...)...)
 	}
-	image := e.Platform.Status.Build.Registry.Address + "/" + organization + "/camel-k-" + e.IntegrationKit.Name + ":" + e.IntegrationKit.ResourceVersion
 
 	env := make([]corev1.EnvVar, 0)
-	baseArgs := []string{
+	volumes := make([]corev1.Volume, 0)
+	volumeMounts := make([]corev1.VolumeMount, 0)
+
+	if e.Platform.Status.Build.Registry.Secret != "" {
+		secret, err := getRegistrySecretFor(e, buildahRegistrySecrets)
+		if err != nil {
+			return nil, err
+		}
+		mountRegistrySecret(e.Platform.Status.Build.Registry.Secret, secret, &volumes, &volumeMounts, &env)
+	} else if e.Platform.Status.Build.Registry.Insecure {
+		bud = append(bud[:2], append([]string{"--tls-verify=false"}, bud[2:]...)...)
+		push = append(push[:2], append([]string{"--tls-verify=false"}, push[2:]...)...)
+	}
+
+	env = append(env, proxySecretEnvVars(e)...)
+
+	return &v1.ImageTask{
+		ContainerTask: v1.ContainerTask{
+			BaseTask: v1.BaseTask{
+				Name:         "buildah",
+				Volumes:      volumes,
+				VolumeMounts: volumeMounts,
+			},
+			Image:      fmt.Sprintf("quay.io/buildah/stable:v%s", defaults.BuildahVersion),
+			Command:    []string{"/bin/sh", "-c"},
+			Args:       []string{strings.Join(bud, " ") + " && " + strings.Join(push, " ")},
+			Env:        env,
+			WorkingDir: path.Join(builderDir, e.IntegrationKit.Name, "context"),
+		},
+		BuiltImage: image,
+	}, nil
+}
+
+func (t *builderTrait) kanikoTask(e *Environment) (*v1.ImageTask, error) {
+	image := getImageName(e)
+
+	args := []string{
 		"--dockerfile=Dockerfile",
-		"--context=" + path.Join(builderDir, e.IntegrationKit.Name, "package", "context"),
+		"--context=" + path.Join(builderDir, e.IntegrationKit.Name, "context"),
 		"--destination=" + image,
 		"--cache=" + strconv.FormatBool(e.Platform.Status.Build.IsKanikoCacheEnabled()),
 		"--cache-dir=" + kaniko.CacheDir,
 	}
 
-	args := make([]string, 0, len(baseArgs))
-	args = append(args, baseArgs...)
-
 	if t.Verbose {
 		args = append(args, "-v=debug")
 	}
 
-	if e.Platform.Status.Build.Registry.Insecure {
-		args = append(args, "--insecure")
-		args = append(args, "--insecure-pull")
-	}
-
+	env := make([]corev1.EnvVar, 0)
 	volumes := make([]corev1.Volume, 0)
 	volumeMounts := make([]corev1.VolumeMount, 0)
 
 	if e.Platform.Status.Build.Registry.Secret != "" {
-		secretKind, err := getSecretKind(e)
+		secret, err := getRegistrySecretFor(e, kanikoRegistrySecrets)
 		if err != nil {
 			return nil, err
 		}
-
-		volumes = append(volumes, corev1.Volume{
-			Name: "kaniko-secret",
-			VolumeSource: corev1.VolumeSource{
-				Secret: &corev1.SecretVolumeSource{
-					SecretName: e.Platform.Status.Build.Registry.Secret,
-					Items: []corev1.KeyToPath{
-						{
-							Key:  secretKind.fileName,
-							Path: secretKind.destination,
-						},
-					},
-				},
-			},
-		})
-
-		volumeMounts = append(volumeMounts, corev1.VolumeMount{
-			Name:      "kaniko-secret",
-			MountPath: secretKind.mountPath,
-		})
-
-		if secretKind.refEnv != "" {
-			env = append(env, corev1.EnvVar{
-				Name:  secretKind.refEnv,
-				Value: path.Join(secretKind.mountPath, secretKind.destination),
-			})
-		}
-		args = baseArgs
+		mountRegistrySecret(e.Platform.Status.Build.Registry.Secret, secret, &volumes, &volumeMounts, &env)
+	} else if e.Platform.Status.Build.Registry.Insecure {
+		args = append(args, "--insecure")
+		args = append(args, "--insecure-pull")
 	}
 
-	if e.Platform.Status.Build.HTTPProxySecret != "" {
-		optional := true
-		env = append(env, corev1.EnvVar{
-			Name: "HTTP_PROXY",
-			ValueFrom: &corev1.EnvVarSource{
-				SecretKeyRef: &corev1.SecretKeySelector{
-					LocalObjectReference: corev1.LocalObjectReference{
-						Name: e.Platform.Status.Build.HTTPProxySecret,
-					},
-					Key:      "HTTP_PROXY",
-					Optional: &optional,
-				},
-			},
-		})
-		env = append(env, corev1.EnvVar{
-			Name: "HTTPS_PROXY",
-			ValueFrom: &corev1.EnvVarSource{
-				SecretKeyRef: &corev1.SecretKeySelector{
-					LocalObjectReference: corev1.LocalObjectReference{
-						Name: e.Platform.Status.Build.HTTPProxySecret,
-					},
-					Key:      "HTTPS_PROXY",
-					Optional: &optional,
-				},
-			},
-		})
-		env = append(env, corev1.EnvVar{
-			Name: "NO_PROXY",
-			ValueFrom: &corev1.EnvVarSource{
-				SecretKeyRef: &corev1.SecretKeySelector{
-					LocalObjectReference: corev1.LocalObjectReference{
-						Name: e.Platform.Status.Build.HTTPProxySecret,
-					},
-					Key:      "NO_PROXY",
-					Optional: &optional,
-				},
-			},
-		})
-	}
+	env = append(env, proxySecretEnvVars(e)...)
 
-	return &v1.KanikoTask{
-		ImageTask: v1.ImageTask{
+	return &v1.ImageTask{
+		ContainerTask: v1.ContainerTask{
 			BaseTask: v1.BaseTask{
 				Name:         "kaniko",
 				Volumes:      volumes,
@@ -315,7 +317,7 @@ func (t *builderTrait) kanikoTask(e *Environment) (*v1.KanikoTask, error) {
 	}, nil
 }
 
-type secretKind struct {
+type registrySecret struct {
 	fileName    string
 	mountPath   string
 	destination string
@@ -323,36 +325,118 @@ type secretKind struct {
 }
 
 var (
-	secretKindGCR = secretKind{
+	plainDockerBuildahRegistrySecret = registrySecret{
+		fileName:    "config.json",
+		mountPath:   "/buildah/.docker",
+		destination: "config.json",
+		refEnv:      "REGISTRY_AUTH_FILE",
+	}
+
+	buildahRegistrySecrets = []registrySecret{
+		plainDockerBuildahRegistrySecret,
+	}
+)
+
+var (
+	gcrKanikoRegistrySecret = registrySecret{
 		fileName:    "kaniko-secret.json",
 		mountPath:   "/secret",
 		destination: "kaniko-secret.json",
 		refEnv:      "GOOGLE_APPLICATION_CREDENTIALS",
 	}
-	secretKindPlainDocker = secretKind{
+	plainDockerKanikoRegistrySecret = registrySecret{
 		fileName:    "config.json",
 		mountPath:   "/kaniko/.docker",
 		destination: "config.json",
 	}
-	secretKindStandardDocker = secretKind{
+	standardDockerKanikoRegistrySecret = registrySecret{
 		fileName:    corev1.DockerConfigJsonKey,
 		mountPath:   "/kaniko/.docker",
 		destination: "config.json",
 	}
 
-	allSecretKinds = []secretKind{secretKindGCR, secretKindPlainDocker, secretKindStandardDocker}
+	kanikoRegistrySecrets = []registrySecret{
+		gcrKanikoRegistrySecret,
+		plainDockerKanikoRegistrySecret,
+		standardDockerKanikoRegistrySecret,
+	}
 )
 
-func getSecretKind(e *Environment) (secretKind, error) {
+func proxySecretEnvVars(e *Environment) []corev1.EnvVar {
+	if e.Platform.Status.Build.HTTPProxySecret == "" {
+		return []corev1.EnvVar{}
+	}
+
+	return []corev1.EnvVar{
+		proxySecretEnvVar("HTTP_PROXY", e.Platform.Status.Build.HTTPProxySecret),
+		proxySecretEnvVar("HTTPS_PROXY", e.Platform.Status.Build.HTTPProxySecret),
+		proxySecretEnvVar("NO_PROXY", e.Platform.Status.Build.HTTPProxySecret),
+	}
+}
+
+func proxySecretEnvVar(name string, secret string) corev1.EnvVar {
+	optional := true
+	return corev1.EnvVar{
+		Name: name,
+		ValueFrom: &corev1.EnvVarSource{
+			SecretKeyRef: &corev1.SecretKeySelector{
+				LocalObjectReference: corev1.LocalObjectReference{
+					Name: secret,
+				},
+				Key:      name,
+				Optional: &optional,
+			},
+		},
+	}
+}
+
+func getRegistrySecretFor(e *Environment, registrySecrets []registrySecret) (registrySecret, error) {
 	secret := corev1.Secret{}
 	err := e.Client.Get(e.C, client.ObjectKey{Namespace: e.Platform.Namespace, Name: e.Platform.Status.Build.Registry.Secret}, &secret)
 	if err != nil {
-		return secretKind{}, err
+		return registrySecret{}, err
 	}
-	for _, k := range allSecretKinds {
+	for _, k := range registrySecrets {
 		if _, ok := secret.Data[k.fileName]; ok {
 			return k, nil
 		}
 	}
-	return secretKind{}, errors.New("unsupported secret type for registry authentication")
+	return registrySecret{}, errors.New("unsupported secret type for registry authentication")
+}
+
+func mountRegistrySecret(name string, secret registrySecret, volumes *[]corev1.Volume, volumeMounts *[]corev1.VolumeMount, env *[]corev1.EnvVar) {
+	*volumes = append(*volumes, corev1.Volume{
+		Name: "registry-secret",
+		VolumeSource: corev1.VolumeSource{
+			Secret: &corev1.SecretVolumeSource{
+				SecretName: name,
+				Items: []corev1.KeyToPath{
+					{
+						Key:  secret.fileName,
+						Path: secret.destination,
+					},
+				},
+			},
+		},
+	})
+
+	*volumeMounts = append(*volumeMounts, corev1.VolumeMount{
+		Name:      "registry-secret",
+		MountPath: secret.mountPath,
+	})
+
+	if secret.refEnv != "" {
+		*env = append(*env, corev1.EnvVar{
+			Name:  secret.refEnv,
+			Value: path.Join(secret.mountPath, secret.destination),
+		})
+	}
+}
+
+func getImageName(e *Environment) string {
+	organization := e.Platform.Status.Build.Registry.Organization
+	if organization == "" {
+		organization = e.Platform.Namespace
+	}
+	return e.Platform.Status.Build.Registry.Address + "/" + organization + "/camel-k-" + e.IntegrationKit.Name + ":" + e.IntegrationKit.ResourceVersion
 }
diff --git a/pkg/util/defaults/defaults.go b/pkg/util/defaults/defaults.go
index 3b7bbafd5..c9227f44d 100644
--- a/pkg/util/defaults/defaults.go
+++ b/pkg/util/defaults/defaults.go
@@ -28,6 +28,9 @@ const (
 	// DefaultRuntimeVersion --
 	DefaultRuntimeVersion = "1.1.0-SNAPSHOT"
 
+	// BuildahVersion --
+	BuildahVersion = "1.12.0"
+
 	// KanikoVersion --
 	KanikoVersion = "0.17.1"
 
diff --git a/pkg/util/tar/appender.go b/pkg/util/tar/appender.go
deleted file mode 100644
index 5ff62dc3c..000000000
--- a/pkg/util/tar/appender.go
+++ /dev/null
@@ -1,148 +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 tar
-
-import (
-	atar "archive/tar"
-	"io"
-	"os"
-	"path"
-
-	"github.com/pkg/errors"
-)
-
-// Appender provides a high level abstraction over writing tar files
-type Appender struct {
-	tarFile *os.File
-	writer  *atar.Writer
-}
-
-// NewAppender creates a new tar appender
-func NewAppender(fileName string) (*Appender, error) {
-	tarFile, err := os.Create(fileName)
-	if err != nil {
-		return nil, errors.Wrap(err, "cannot create tar file "+fileName)
-	}
-
-	writer := atar.NewWriter(tarFile)
-	appender := Appender{
-		tarFile: tarFile,
-		writer:  writer,
-	}
-	return &appender, nil
-}
-
-// Close closes all handles managed by the appender
-func (t *Appender) Close() error {
-	if err := t.writer.Close(); err != nil {
-		return err
-	}
-	err := t.tarFile.Close()
-	if err != nil {
-		return err
-	}
-	return nil
-}
-
-// AddFile adds a file content to the tarDir, using the original file name.
-// It returns the full path of the file inside the tar.
-func (t *Appender) AddFile(filePath string, tarDir string) (string, error) {
-	info, err := os.Stat(filePath)
-	if err != nil {
-		return "", err
-	}
-	_, fileName := path.Split(filePath)
-	if tarDir != "" {
-		fileName = path.Join(tarDir, fileName)
-	}
-
-	if err := t.writer.WriteHeader(&atar.Header{
-		Name:    fileName,
-		Size:    info.Size(),
-		Mode:    0644,
-		ModTime: info.ModTime(),
-	}); err != nil {
-		return "", err
-	}
-
-	file, err := os.Open(filePath)
-	if err != nil {
-		return "", err
-	}
-	defer file.Close()
-
-	_, err = io.Copy(t.writer, file)
-	if err != nil {
-		return "", errors.Wrap(err, "cannot add file to the tar archive")
-	}
-
-	return fileName, nil
-}
-
-// AddFileWithName adds a file content to the tarDir, using the given file name.
-// It returns the full path of the file inside the tar.
-func (t *Appender) AddFileWithName(fileName string, filePath string, tarDir string) (string, error) {
-	info, err := os.Stat(filePath)
-	if err != nil {
-		return "", err
-	}
-	if tarDir != "" {
-		fileName = path.Join(tarDir, fileName)
-	}
-
-	if err := t.writer.WriteHeader(&atar.Header{
-		Name:    fileName,
-		Size:    info.Size(),
-		Mode:    0644,
-		ModTime: info.ModTime(),
-	}); err != nil {
-		return "", err
-	}
-
-	file, err := os.Open(filePath)
-	if err != nil {
-		return "", err
-	}
-	defer file.Close()
-
-	_, err = io.Copy(t.writer, file)
-	if err != nil {
-		return "", errors.Wrap(err, "cannot add file to the tar archive")
-	}
-
-	return fileName, nil
-}
-
-// AddData appends the given content to a file inside the tar, creating it if it does not exist
-func (t *Appender) AddData(data []byte, tarPath string) error {
-	err := t.writer.WriteHeader(&atar.Header{
-		Name: tarPath,
-		Size: int64(len(data)),
-		Mode: 0644,
-	})
-
-	if err != nil {
-		return err
-	}
-
-	if _, err := t.writer.Write(data); err != nil {
-		return errors.Wrap(err, "cannot add data to the tar archive")
-	}
-
-	return nil
-}
diff --git a/pkg/util/util.go b/pkg/util/util.go
index faf23e292..50ff56735 100644
--- a/pkg/util/util.go
+++ b/pkg/util/util.go
@@ -20,18 +20,16 @@ package util
 import (
 	"bytes"
 	"encoding/xml"
+	"fmt"
+	"io"
 	"os"
-	"os/signal"
 	"path"
 	"regexp"
 	"sort"
-	"syscall"
-
-	"github.com/scylladb/go-set/strset"
-
-	corev1 "k8s.io/api/core/v1"
 
 	"github.com/pkg/errors"
+
+	"github.com/scylladb/go-set/strset"
 )
 
 // StringSliceJoin --
@@ -90,21 +88,6 @@ func StringSliceUniqueAdd(slice *[]string, item string) bool {
 	return true
 }
 
-// WaitForSignal --
-func WaitForSignal(sig chan os.Signal, exit func(int)) {
-	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM, syscall.SIGPIPE)
-	go func() {
-		s := <-sig
-		switch s {
-		case syscall.SIGINT, syscall.SIGTERM:
-			exit(130) // Ctrl+c
-		case syscall.SIGPIPE:
-			exit(0)
-		}
-		exit(1)
-	}()
-}
-
 // EncodeXML --
 func EncodeXML(content interface{}) ([]byte, error) {
 	w := &bytes.Buffer{}
@@ -121,6 +104,37 @@ func EncodeXML(content interface{}) ([]byte, error) {
 	return w.Bytes(), nil
 }
 
+// CopyFile --
+func CopyFile(src, dst string) (int64, error) {
+	sourceFileStat, err := os.Stat(src)
+	if err != nil {
+		return 0, err
+	}
+
+	if !sourceFileStat.Mode().IsRegular() {
+		return 0, fmt.Errorf("%s is not a regular file", src)
+	}
+
+	source, err := os.Open(src)
+	if err != nil {
+		return 0, err
+	}
+	defer source.Close()
+
+	err = os.MkdirAll(path.Dir(dst), 0777)
+	if err != nil {
+		return 0, err
+	}
+
+	destination, err := os.Create(dst)
+	if err != nil {
+		return 0, err
+	}
+	defer destination.Close()
+	nBytes, err := io.Copy(destination, source)
+	return nBytes, err
+}
+
 // WriteFileWithContent --
 func WriteFileWithContent(buildDir string, relativePath string, content []byte) error {
 	filePath := path.Join(buildDir, relativePath)
@@ -154,19 +168,7 @@ func WriteFileWithBytesMarshallerContent(buildDir string, relativePath string, c
 	return WriteFileWithContent(buildDir, relativePath, data)
 }
 
-// LookupEnvVar --
-func LookupEnvVar(vars []corev1.EnvVar, name string) *corev1.EnvVar {
-	for _, e := range vars {
-		if e.Name == name {
-			ev := e
-			return &ev
-		}
-	}
-
-	return nil
-}
-
-// FindAllDistinctStringSubmatch ..
+// FindAllDistinctStringSubmatch --
 func FindAllDistinctStringSubmatch(data string, regexps ...*regexp.Regexp) []string {
 	submatchs := strset.New()
 
diff --git a/pkg/util/tar/extract.go b/pkg/util/zip/zip.go
similarity index 60%
rename from pkg/util/tar/extract.go
rename to pkg/util/zip/zip.go
index 4592a6e09..a5f59ded3 100644
--- a/pkg/util/tar/extract.go
+++ b/pkg/util/zip/zip.go
@@ -15,45 +15,50 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package tar
+package zip
 
 import (
+	"archive/zip"
 	"io"
-	"io/ioutil"
 	"os"
-	"path"
-
-	tarutils "archive/tar"
+	"path/filepath"
+	"strings"
 )
 
-// Extract --
-func Extract(source string, destinationBase string) error {
-	file, err := os.Open(source)
+func Directory(pathToZip, destinationPath string) error {
+	destinationFile, err := os.Create(destinationPath)
 	if err != nil {
 		return err
 	}
-	defer file.Close()
-	reader := tarutils.NewReader(file)
-	for {
-		header, err := reader.Next()
-		if err == io.EOF {
-			break
+	myZip := zip.NewWriter(destinationFile)
+	err = filepath.Walk(pathToZip, func(filePath string, info os.FileInfo, err error) error {
+		if info.IsDir() {
+			return nil
 		}
 		if err != nil {
 			return err
 		}
-		targetName := path.Join(destinationBase, header.Name)
-		targetDir, _ := path.Split(targetName)
-		if err := os.MkdirAll(targetDir, 0777); err != nil {
+		relPath := strings.TrimPrefix(filePath, pathToZip)
+		zipFile, err := myZip.Create(relPath)
+		if err != nil {
 			return err
 		}
-		buffer, err := ioutil.ReadAll(reader)
+		fsFile, err := os.Open(filePath)
 		if err != nil {
 			return err
 		}
-		if err := ioutil.WriteFile(targetName, buffer, os.FileMode(header.Mode)); err != nil {
+		_, err = io.Copy(zipFile, fsFile)
+		if err != nil {
 			return err
 		}
+		return nil
+	})
+	if err != nil {
+		return err
+	}
+	err = myZip.Close()
+	if err != nil {
+		return err
 	}
 	return nil
 }
diff --git a/script/Makefile b/script/Makefile
index ff8e7d98c..215b2f303 100644
--- a/script/Makefile
+++ b/script/Makefile
@@ -18,6 +18,7 @@ VERSION := 1.0.0-RC2-SNAPSHOT
 LAST_RELEASED_VERSION := 1.0.0-M4
 RUNTIME_VERSION := 1.1.0-SNAPSHOT
 QUARKUS_VERSION := 1.1.0.Final
+BUILDAH_VERSION := 1.12.0
 KANIKO_VERSION := 0.17.1
 BASE_IMAGE := adoptopenjdk/openjdk8:slim
 LOCAL_REPOSITORY := /tmp/artifacts/m2
@@ -87,10 +88,11 @@ codegen:
 	@echo "  // Version -- " >> $(VERSIONFILE)
 	@echo "  Version = \"$(VERSION)\"" >> $(VERSIONFILE)
 	@echo "" >> $(VERSIONFILE)
-	@echo "" >> $(VERSIONFILE)
 	@echo "  // DefaultRuntimeVersion -- " >> $(VERSIONFILE)
 	@echo "  DefaultRuntimeVersion = \"$(RUNTIME_VERSION)\"" >> $(VERSIONFILE)
 	@echo "" >> $(VERSIONFILE)
+	@echo "  // BuildahVersion -- " >> $(VERSIONFILE)
+	@echo "  BuildahVersion = \"$(BUILDAH_VERSION)\"" >> $(VERSIONFILE)
 	@echo "" >> $(VERSIONFILE)
 	@echo "  // KanikoVersion -- " >> $(VERSIONFILE)
 	@echo "  KanikoVersion = \"$(KANIKO_VERSION)\"" >> $(VERSIONFILE)


Test patch:
diff --git a/e2e/knative_test.go b/e2e/knative_test.go
index 1648a139f..0b4dbf74f 100644
--- a/e2e/knative_test.go
+++ b/e2e/knative_test.go
@@ -34,8 +34,8 @@ func TestRunServiceCombo(t *testing.T) {
 
 		Expect(kamel("install", "-n", ns, "--trait-profile", "knative").Execute()).Should(BeNil())
 		Expect(kamel("run", "-n", ns, "files/knative2.groovy").Execute()).Should(BeNil())
-		Expect(kamel("run", "-n", ns, "files/knative1.groovy").Execute()).Should(BeNil())
 		Eventually(integrationPodPhase(ns, "knative2"), 10*time.Minute).Should(Equal(v1.PodRunning))
+		Expect(kamel("run", "-n", ns, "files/knative1.groovy").Execute()).Should(BeNil())
 		Eventually(integrationPodPhase(ns, "knative1"), 10*time.Minute).Should(Equal(v1.PodRunning))
 		Eventually(integrationLogs(ns, "knative1"), 5*time.Minute).Should(ContainSubstring("Received: Hello from knative2"))
 		Expect(kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil())
diff --git a/e2e/run_test.go b/e2e/run_test.go
index 189319a95..9eb83b633 100644
--- a/e2e/run_test.go
+++ b/e2e/run_test.go
@@ -95,10 +95,10 @@ func TestRunSimpleExamples(t *testing.T) {
 
 		t.Run("run yaml Quarkus", func(t *testing.T) {
 			RegisterTestingT(t)
-			Expect(kamel("run", "-n", ns, "files/yaml.yaml", "-t", "quarkus.enabled=true").Execute()).Should(BeNil())
-			Eventually(integrationPodPhase(ns, "yaml"), 5*time.Minute).Should(Equal(v1.PodRunning))
-			Eventually(integrationLogs(ns, "yaml"), 1*time.Minute).Should(ContainSubstring("running on Quarkus"))
-			Eventually(integrationLogs(ns, "yaml"), 1*time.Minute).Should(ContainSubstring("Magicstring!"))
+			Expect(kamel("run", "-n", ns, "--name", "yaml-quarkus", "files/yaml.yaml", "-t", "quarkus.enabled=true").Execute()).Should(BeNil())
+			Eventually(integrationPodPhase(ns, "yaml-quarkus"), 5*time.Minute).Should(Equal(v1.PodRunning))
+			Eventually(integrationLogs(ns, "yaml-quarkus"), 1*time.Minute).Should(ContainSubstring("running on Quarkus"))
+			Eventually(integrationLogs(ns, "yaml-quarkus"), 1*time.Minute).Should(ContainSubstring("Magicstring!"))
 			Expect(kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil())
 		})
 
diff --git a/pkg/trait/builder_test.go b/pkg/trait/builder_test.go
index acd8bac81..9cb0f821d 100644
--- a/pkg/trait/builder_test.go
+++ b/pkg/trait/builder_test.go
@@ -27,7 +27,6 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
-	"github.com/apache/camel-k/pkg/builder/kaniko"
 	"github.com/apache/camel-k/pkg/builder/s2i"
 	"github.com/apache/camel-k/pkg/util/camel"
 	"github.com/apache/camel-k/pkg/util/defaults"
@@ -107,16 +106,7 @@ func TestKanikoBuilderTrait(t *testing.T) {
 	assert.NotEmpty(t, env.BuildTasks)
 	assert.Len(t, env.BuildTasks, 2)
 	assert.NotNil(t, env.BuildTasks[0].Builder)
-	assert.Condition(t, func() bool {
-		for _, s := range env.BuildTasks[0].Builder.Steps {
-			if s == kaniko.Steps.Publisher.ID() {
-				return true
-			}
-		}
-
-		return false
-	})
-	assert.NotNil(t, env.BuildTasks[1].Kaniko)
+	assert.NotNil(t, env.BuildTasks[1].Image)
 }
 
 func createBuilderTestEnv(cluster v1.IntegrationPlatformCluster, strategy v1.IntegrationPlatformBuildPublishStrategy) *Environment {