I’ve been running Kaniko for several years without any major issues.
Recently, when I was updating and applying patches to my CI systems on Argo Workflows, I discovered that the Kaniko project is now (June 3, 2025) archived.
It can now be added to Google Graveyard
Kaniko#
Kaniko is nice since it’s super simple to deploy and get working.
docker run --rm -v $(pwd):/workspace \
gcr.io/kaniko-project/executor:latest \
--dockerfile=Dockerfile --context=/workspace \
--no-push --reproducible --skip-unused-stages=true
The major downside I’ve run into is getting secrets into the container.
I’ve found two workarounds (helpful blog):
- Copy the short-lived secret in with the Dockerfile, with the hope that it doesn’t get copied into the final image.
- Mount the secret into
/kaniko/SECRET_NAME in the kaniko container, this /kaniko path is available from inside the image during runtime so it can be read from there.
BuildKit#
I settled on switching to using BuildKit as Kaniko’s replacement.
It requires a little more setup but covers all of Kaniko’s features with the following upsides:
- Faster builds
- Slightly smaller images (in KB)
- Fully secret support
- Easier to run locally (available directly in docker)
docker run --rm --privileged --entrypoint buildctl-daemonless.sh \
-v $HOME/.npmrc:/secrets/npmrc:ro -v $(PWD):/workspace \
-e BUILDKITD_FLAGS='--oci-worker-no-process-sandbox' \
moby/buildkit:rootless \
build --frontend dockerfile.v0 --local context='/workspace' --local dockerfile='/workspace' \
--secret id=npmrc,src=/secrets/npmrc \
--output type=image,name=ttl.sh/buildkit-demo:dummy,push=true
Pushing to ECR#
The default BuildKit image cannot automatically authenticate to an AWS ECR repository.
To work around that the following Dockerfile can be used to install the “amazon-ecr-credential-helper” and setup the config.
1
2
3
4
5
6
7
|
FROM moby/buildkit:rootless
RUN mkdir -p ~/.docker && \
echo "{\"credsStore\":\"ecr-login\"}" > ~/.docker/config.json
# https://github.com/awslabs/amazon-ecr-credential-helper/releases
ADD --chown=root:user --chmod=050 https://amazon-ecr-credential-helper-releases.s3.us-east-2.amazonaws.com/0.10.1/linux-amd64/docker-credential-ecr-login /usr/local/bin/docker-credential-ecr-login
|
If you’re running locally, you’ll need to mount your local AWS credentials into the container.
-v $HOME/.aws:/home/user/.aws:ro -e AWS_PROFILE=ecr-dummy-profile
This setup was inspired by the setup from this blog
Misc#
⚠️
If your trying to push to multiple registries with output type image and oci/docker the image digests will be different.
My solution for getting the hashes to be the same is just export as an oci tar and then using crane in both environments to upload the images to the registries.
I’ll upload the fixed code to GitHub when I get around to the full argo workflows build-out.
If you need the container digest (hash) the following argument can be added.
During the build a json object containing image hash and other information is written to that path.
--metadata-file /workspace/metadata.json
Multiple outputs can be setup (remote or local) in my case to support multi-repos I use Crane which requires the image archive.
--output type=oci,dest=/workspace/image.tar
Improvements#
My setup could be improved, for one, it does not support any types of caching.
A dedicated build node could be set up for image caching or a cache image could be pulled to reuse layers. (sparkfabrik blog)
Argo Workflows#
BuildKit is pretty much a drop in replacement in Argo Workflows with only needing a few changes.
- Kubernetes Nodes must support user namespaces if running non-privileged (bottlerocket)
- Image digest must be parsed.
{{=jsonpath(tasks.buildkit.outputs.parameters.digest, ''$.["containerimage.digest"]'')}}
Here is an example Helm WorkflowTemplate that builds images.
I’ll eventually have a full Argo Workflows CI system document but for now this will make due.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
|
---
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: build-nodejs
namespace: {{ .Values.namespace }}
spec:
arguments:
parameters:
- name: build_path
- name: commit
- name: destination_image
- name: ecr_repository
- name: project
- name: release_stage
- name: source_workflow
- name: stack
entrypoint: entrypoint
serviceAccountName: argo-nodejs-build
podGC:
strategy: OnWorkflowCompletion
deleteDelayDuration: 600s
templates:
- name: entrypoint
inputs:
parameters:
- name: build_path
- name: commit
- name: destination_image
- name: ecr_repository
- name: project
- name: release_stage
- name: stack
dag:
tasks:
- name: code-artifact-auth
template: code-artifact-auth
- name: buildkit
depends: code-artifact-auth
template: buildkit
arguments:
parameters:
- name: build_path
value: "{{ `{{ inputs.parameters.build_path }}` }}"
- name: destination_image
value: "{{ `{{ inputs.parameters.destination_image }}` }}"
- name: notification
depends: buildkit
templateRef:
name: success-notification
template: post
arguments:
parameters:
- name: commit
value: "{{ `{{ inputs.parameters.commit }}` }}"
- name: releaseStage
value: "{{ `{{ inputs.parameters.release_stage }}` }}"
- name: repo
value: "{{ `{{ inputs.parameters.stack }}` }}/{{ `{{ inputs.parameters.project }}` }}"
- name: webhook_secret
value: success-notification
- name: commit-changes
depends: buildkit.Succeeded
templateRef:
name: git-committer
template: commit-image
arguments:
parameters:
- name: release_stage
value: "{{ `{{ inputs.parameters.release_stage }}` }}"
- name: hash
value: '{{ `{{=jsonpath(tasks.buildkit.outputs.parameters.digest, ''$.["containerimage.digest"]'')}}` }}'
- name: project
value: "{{ `{{ inputs.parameters.project }}` }}"
- name: image_tag
value: "{{ `{{ inputs.parameters.commit }}` }}"
- name: stack
value: "{{ `{{ inputs.parameters.stack }}` }}"
- name: code-artifact-auth
podSpecPatch: '{"serviceAccountName": "argo-nodejs-build"}'
container:
image: public.ecr.aws/aws-cli/aws-cli:2.31.18@sha256:d3f5b7078a71fd7cb488719571c50abd252f4320cdf375601f2d6d40caf97b55
command: ["sh", "-c"]
args: [
'echo "@example:registry=https://example-0123456789.d.codeartifact.us-east-1.amazonaws.com/npm/npm/" > /source/.npmrc &&
echo "//example-0123456789.d.codeartifact.us-east-1.amazonaws.com/npm/npm/:_authToken=$(aws codeartifact get-authorization-token --domain example --query authorizationToken --duration-seconds 900 --output text)" >> /source/.npmrc'
]
resources:
limits:
cpu: 25m
memory: 100Mi
requests:
cpu: 5m
memory: 5Mi
volumeMounts:
- mountPath: /source
name: source-code
- name: buildkit
podSpecPatch: '{"serviceAccountName": "argo-nodejs-build"}'
inputs:
parameters:
- name: build_path
- name: destination_image
container:
command:
- buildctl-daemonless.sh
workingDir: "/source/{{ `{{ inputs.parameters.build_path }}` }}"
args:
- --log-format
- json
- build
- --frontend
- dockerfile.v0
- --local
- context=.
- --local
- dockerfile=.
- --secret
- id=npmrc,src=/buildkit/secrets/.npmrc
- --output
- type=image,name={{ `{{ inputs.parameters.destination_image }}` }},push=true
- --output
- type=docker,dest=/source/image.tar
- --metadata-file
- /tmp/digest
securityContext:
seccompProfile:
type: Unconfined
appArmorProfile:
type: Unconfined
runAsUser: 1000
runAsGroup: 1000
image: 0123456789.dkr.ecr.us-east-1.amazonaws.com/buildkit:dummy@sha256:abc123
env:
- name: BUILDKITD_FLAGS
value: --oci-worker-no-process-sandbox
resources:
limits:
cpu: 200m
memory: 1Gi
requests:
cpu: 50m
memory: 100Mi
volumeMounts:
- mountPath: /source
name: source-code
- mountPath: /buildkit/secrets/.npmrc
name: source-code
readOnly: true
subPath: .npmrc
outputs:
parameters:
- name: digest
valueFrom:
default: "{}"
path: /tmp/digest
volumes:
- name: source-code
persistentVolumeClaim:
claimName: "{{ `{{ workflow.parameters.source_workflow }}` }}-source-code"
- name: git-secret
secret:
defaultMode: 256
secretName: argo-workflows-ssh
|