Understanding Kaniko
Kaniko is an open-source tool developed by Google that builds container images from a Dockerfile without requiring a Docker daemon. Unlike traditional Docker builds that rely on a running Docker engine with privileged access to the host filesystem, Kaniko executes the build process entirely in userspace. It works by extracting the filesystem from a base image, then executing each Dockerfile instruction one by one, snapshotting the filesystem after each step, and finally pushing the complete image layers to a container registry.
The name "Kaniko" comes from the Japanese word for "planer" — a woodworking tool that shaves thin layers off wood, which is a fitting metaphor for how Kaniko builds container image layers. Under the hood, Kaniko parses the Dockerfile, resolves multi-stage builds, handles ARG and ENV substitution, and manages the layer cache with precision. It does all this without mounting the host's Docker socket or requiring root privileges on the node, making it exceptionally well-suited for CI/CD pipelines and Kubernetes-native builds.
Core Architecture
Kaniko runs as a single binary inside a container. When you invoke it, the following sequence occurs:
- Base image retrieval — Kaniko pulls the specified base image from a registry and extracts its filesystem into a local directory
- Instruction execution — Each Dockerfile command (RUN, COPY, ADD, etc.) is executed against the extracted filesystem
- Snapshotting — After each instruction, Kaniko takes a diff of the filesystem, creating a new layer tar that matches what Docker would produce
- Layer upload — Layers are pushed to the target registry along with the manifest
- Cache management — Layers are cached using the registry or a local cache, enabling faster rebuilds
Why Kaniko Matters
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →In modern cloud-native development, building container images inside Kubernetes clusters has become a standard requirement. However, the traditional Docker-in-Docker (DinD) approach introduces significant security and operational concerns. Kaniko addresses these pain points directly:
- No privileged access required — Kaniko runs without the Docker socket or privileged mode, reducing the attack surface in shared CI clusters
- Cluster-native builds — Images are built inside standard Kubernetes pods, benefiting from resource limits, node scheduling, and cluster-wide logging
- Reproducibility — By avoiding host filesystem dependencies, Kaniko builds are fully hermetic and reproducible across environments
- Registry caching — Layer caching is pushed to and pulled from container registries, meaning cache state survives pod restarts and node migrations
- Multi-stage build support — Kaniko handles complex multi-stage Dockerfiles correctly, including copying artifacts between stages
Organizations adopting GitOps workflows, Tekton pipelines, Jenkins X, or GitHub Actions on Kubernetes find Kaniko indispensable. It decouples image building from infrastructure specifics and aligns perfectly with the principle of treating CI/CD systems as cattle, not pets.
Getting Started with Kaniko
The primary way to use Kaniko is by running its official container image as part of your pipeline. The image is available at gcr.io/kaniko-project/executor. You can also download the standalone binary for debugging purposes, but the containerized approach is the recommended production method.
Basic Invocation
The simplest Kaniko command requires specifying a Dockerfile context and a destination registry. Here's the minimal set of flags:
docker run \
-v $(pwd):/workspace \
gcr.io/kaniko-project/executor:latest \
--context=/workspace \
--dockerfile=/workspace/Dockerfile \
--destination=myregistry.io/my-image:tag
When running inside a Kubernetes pod, you would define these parameters as container arguments rather than using Docker directly. Let's look at a complete Kubernetes Pod manifest for Kaniko:
apiVersion: v1
kind: Pod
metadata:
name: kaniko-build
spec:
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:latest
args:
- "--context=git://github.com/myorg/myrepo"
- "--git-branch=main"
- "--destination=myregistry.io/my-image:1.0.0"
- "--cache=true"
- "--cache-repo=myregistry.io/cache"
- "--snapshot-mode=time"
volumeMounts:
- name: docker-config
mountPath: /kaniko/.docker
restartPolicy: Never
volumes:
- name: docker-config
secret:
secretName: registry-credentials
This Pod definition demonstrates several key features: pulling context from a Git repository, enabling registry-based caching, using timestamp-based snapshots for faster layer detection, and mounting registry credentials from a Kubernetes Secret.
Complete Configuration Reference
Kaniko exposes a rich set of flags that give you fine-grained control over the build process. Understanding these options thoroughly is essential for production deployments where reliability, speed, and security are paramount.
Context Sources
The build context is the set of files available to the Dockerfile. Kaniko supports multiple context sources, each suited to different pipeline architectures:
- Local directory — Mounted via a volume, useful for simple CI setups
- Git repository — Kaniko clones the repo internally, supports branches, commits, and tags
- Tar archive — A pre-assembled tar file containing the build context
- Object storage — Google Cloud Storage or AWS S3 buckets
- OCI image — Use an existing container image as the build context
Here are examples for each context type:
# Local directory context (mounted volume)
executor --context=/workspace --dockerfile=/workspace/Dockerfile
# Git repository context
executor --context=git://github.com/user/repo \
--git-branch=feature-branch \
--git-single-branch=true \
--git-depth=1
# Specific Git commit
executor --context=git://github.com/user/repo \
--git-commit=abc123def456
# Tar archive context
executor --context=tar:///path/to/context.tar.gz
# GCS bucket context
executor --context=gs://my-bucket/path/to/context.tar.gz \
--gcs-bucket=my-bucket
# S3 bucket context
executor --context=s3://my-bucket/path/to/context.tar.gz \
--s3-endpoint=https://s3.amazonaws.com \
--s3-force-path-style=true
# OCI image context (use an image as the build base files)
executor --context=oci://myregistry.io/base-context-image:latest
Destination Configuration
The destination flag tells Kaniko where to push the final image. You can specify multiple destinations simultaneously, pushing the same image to several registries in one build:
# Single destination
--destination=registry.example.com/project/image:v1.0.0
# Multiple destinations (comma-separated)
--destination=registry1.example.com/image:latest,\
registry2.example.com/image:latest,\
quay.io/org/image:v1.0.0
# Destination with digest verification
--destination=registry.example.com/image:tag \
--push-retry=3 \
--push-retry-delay=500ms
When specifying destinations, always use fully qualified registry URLs. Kaniko supports Docker Hub, Google Container Registry, AWS ECR, Azure Container Registry, Quay.io, Harbor, and any OCI-compliant registry.
Registry Authentication
Authentication is handled through Docker-style config files mounted into the container at /kaniko/.docker/config.json. This file can contain credentials for multiple registries:
{
"auths": {
"registry.example.com": {
"auth": "base64encodedusername:password",
"email": "user@example.com"
},
"gcr.io": {
"auth": "base64encoded_access_token"
},
"public.ecr.aws": {
"auth": "base64encoded_credentials"
}
},
"credHelpers": {
"asia.gcr.io": "gcr",
"eu.gcr.io": "gcr",
"gcr.io": "gcr",
"marketplace.gcr.io": "gcr",
"staging-k8s.gcr.io": "gcr",
"us.gcr.io": "gcr"
}
}
To create this secret in Kubernetes from an existing Docker credentials file:
kubectl create secret generic kaniko-auth \
--from-file=config.json=.docker/config.json \
--namespace=build-pipeline
For Amazon ECR specifically, you can use the --aws-access-key-id and --aws-secret-access-key flags, or mount AWS credentials:
executor --context=/workspace \
--destination=123456789.dkr.ecr.us-east-1.amazonaws.com/my-app:latest \
--aws-access-key-id=AKIAIOSFODNN7EXAMPLE \
--aws-secret-access-key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY \
--aws-region=us-east-1
For Google Container Registry, Kaniko can automatically use the application default credentials when running in GKE or on a GCE instance with appropriate scopes.
Snapshot Configuration
Snapshots are how Kaniko determines which files changed after each Dockerfile instruction. The snapshot mode significantly impacts build performance:
# Time-based snapshotting (fastest, default)
--snapshot-mode=time
# Full filesystem hash comparison (most accurate)
--snapshot-mode=full
# Skip snapshotting entirely (use only when you understand implications)
--snapshot-mode=redo
- Time mode — Compares file modification timestamps before and after each instruction. Extremely fast but may miss changes if timestamps are not updated properly by certain operations
- Full mode — Computes checksums of every file. Guarantees accurate layer creation but is slower, especially with large filesystems
- Redo mode — Skips the snapshot comparison and includes everything changed since the last snapshot. Useful for debugging but can produce bloated layers
For most workloads, time-based snapshots offer the best balance of speed and correctness. Switch to full mode if you encounter issues with layers not capturing file changes.
Caching Strategies
Kaniko's caching mechanism is one of its most powerful features. Rather than relying on a local Docker layer cache that disappears when the build pod terminates, Kaniko can use a remote registry to persist layer cache:
# Enable registry-based caching
--cache=true
--cache-repo=registry.example.com/build-cache
--cache-copy-layers=true
# Configure cache TTL (default 2 weeks)
--cache-ttl=336h
# Use a specific cache directory locally
--cache-dir=/workspace/cache
# Inline cache (embed cache metadata in the image itself)
--cache=true
--cache-repo=registry.example.com/my-image
--cache-inline=true
The cache workflow operates as follows:
- Before each instruction, Kaniko queries the cache repo for a matching layer based on the instruction's key (command string + base image digest)
- If found, the cached layer is mounted directly, skipping execution of that instruction
- After the build, newly created layers are pushed to the cache repo with appropriate metadata
For multi-stage builds, Kaniko caches each stage independently. This means changing only a final stage's instructions doesn't invalidate the cache for earlier stages.
Build Arguments and Environment Variables
Passing build arguments mirrors Docker's --build-arg behavior exactly:
# Single build argument
--build-arg=VERSION=1.2.3
# Multiple build arguments
--build-arg=BASE_IMAGE=alpine:3.18 \
--build-arg=NODE_VERSION=18 \
--build-arg=NPM_TOKEN=secret
# From a file
--build-arg-file=/workspace/build-args.properties
The build-arg file format is straightforward key-value pairs, one per line:
# build-args.properties
BASE_IMAGE=ubuntu:22.04
PYTHON_VERSION=3.11
APP_ENV=production
FEATURE_FLAG_NEW_UI=true
Environment variables for the build itself (not the resulting image) can be set directly in the pod spec or via the --env-file flag, though this is distinct from Dockerfile ENV instructions which affect the image permanently.
Multi-Stage Build Configuration
Kaniko fully supports multi-stage Dockerfiles. You can target a specific stage, control which stages are cached, and even use the output of one build as the context for another:
# Build only a specific stage
--target=builder
# Build all stages but push only the final
--target=production
# Use a previous stage's image as a base
# (This works automatically in multi-stage Dockerfiles)
Consider this multi-stage Dockerfile and the corresponding Kaniko configuration:
# Dockerfile
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o server ./cmd/server
FROM alpine:3.18 AS production
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/server /usr/local/bin/server
EXPOSE 8080
CMD ["server"]
# Kaniko build command for the above Dockerfile
executor \
--context=/workspace \
--dockerfile=/workspace/Dockerfile \
--destination=registry.example.com/server:latest \
--cache=true \
--cache-repo=registry.example.com/cache \
--target=production \
--build-arg=GOARCH=amd64
Kaniko builds both stages, caches the builder stage layers separately from the production stage, and only pushes the final production image. If you change only the source code in the builder stage, the production base layer cache remains valid.
Advanced Feature Flags
Kaniko provides experimental and advanced features controlled via feature flags:
# Enable inline caching metadata
--cache-inline=true
# Skip pushing final image (dry run for validation)
--no-push=true
# Push only to cache, not to destination
--push-only-to-cache=true
# Skip TLS verification (testing only, never in production)
--skip-tls-verify=false
# Enable logging timestamps
--log-timestamp=true
# Set log level
--verbosity=debug
# Custom image ignore list for snapshotting
--ignore-path=/workspace/.git
--ignore-path=/workspace/node_modules
# Custom platform for multi-arch builds
--custom-platform=linux/amd64
# Use a custom kaniko directory
--kaniko-dir=/tmp/kaniko-temp
# Enable reproducible builds
--reproducible=true
For production pipelines, the most relevant flags are --reproducible=true which strips timestamps and ensures byte-for-byte identical layers across builds, and --ignore-path which prevents large directories like .git from being accidentally included in layers.
Volume Mounts and Secrets
Beyond the standard context and Docker config mounts, Kaniko supports additional volume mounts for specialized scenarios:
apiVersion: v1
kind: Pod
metadata:
name: kaniko-advanced-build
spec:
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:latest
args:
- "--context=/workspace"
- "--dockerfile=/workspace/Dockerfile"
- "--destination=registry.example.com/app:latest"
- "--cache=true"
- "--cache-repo=registry.example.com/cache"
- "--cache-dir=/kaniko/cache"
volumeMounts:
- name: build-context
mountPath: /workspace
- name: docker-config
mountPath: /kaniko/.docker
- name: cache-volume
mountPath: /kaniko/cache
- name: ssl-certs
mountPath: /etc/ssl/certs
readOnly: true
- name: custom-ca
mountPath: /kaniko/ssl/certs
volumes:
- name: build-context
persistentVolumeClaim:
claimName: workspace-pvc
- name: docker-config
secret:
secretName: registry-credentials
- name: cache-volume
emptyDir: {}
- name: ssl-certs
hostPath:
path: /etc/ssl/certs
- name: custom-ca
configMap:
name: custom-ca-bundle
restartPolicy: Never
Kaniko in CI/CD Pipelines
GitHub Actions Integration
Running Kaniko in GitHub Actions is straightforward. Here's a complete workflow that builds an image and pushes it to a registry:
name: Build and Push with Kaniko
on:
push:
branches: [main]
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker config
run: |
echo '${{ secrets.REGISTRY_CONFIG }}' > /tmp/config.json
- name: Build with Kaniko
uses: docker://gcr.io/kaniko-project/executor:latest
with:
args: |
--context=/github/workspace
--dockerfile=/github/workspace/Dockerfile
--destination=${{ secrets.REGISTRY }}/${{ github.repository }}:${{ github.ref_name }}
--cache=true
--cache-repo=${{ secrets.REGISTRY }}/cache
--snapshot-mode=time
--reproducible=true
--verbosity=info
Tekton Pipeline Task
For Kubernetes-native CI/CD with Tekton, Kaniko integrates as a reusable Task:
apiVersion: tekton.dev/v1
kind: Task
metadata:
name: kaniko-build
spec:
params:
- name: IMAGE
description: The destination image reference
- name: DOCKERFILE_PATH
description: Path to Dockerfile
default: ./Dockerfile
- name: CONTEXT_PATH
description: Build context path
default: ./
steps:
- name: build-and-push
image: gcr.io/kaniko-project/executor:latest
args:
- "--context=$(params.CONTEXT_PATH)"
- "--dockerfile=$(params.DOCKERFILE_PATH)"
- "--destination=$(params.IMAGE)"
- "--cache=true"
- "--cache-repo=$(params.IMAGE)-cache"
- "--reproducible=true"
env:
- name: AWS_REGION
valueFrom:
secretKeyRef:
name: aws-credentials
key: region
volumeMounts:
- name: docker-config
mountPath: /kaniko/.docker
volumes:
- name: docker-config
secret:
secretName: registry-credentials
Jenkins Pipeline (Declarative)
In a Jenkins environment, you can run Kaniko as a container within your pipeline stages:
pipeline {
agent {
kubernetes {
yaml """
apiVersion: v1
kind: Pod
spec:
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:latest
command: ['sleep']
args: ['infinity']
volumeMounts:
- name: docker-config
mountPath: /kaniko/.docker
volumes:
- name: docker-config
secret:
secretName: registry-auth
"""
}
}
stages {
stage('Build Image') {
steps {
container('kaniko') {
sh '''
/kaniko/executor \
--context=./ \
--dockerfile=./Dockerfile \
--destination=registry.example.com/app:${BUILD_ID} \
--cache=true \
--cache-repo=registry.example.com/cache \
--cache-ttl=720h \
--snapshot-mode=time
'''
}
}
}
}
}
Debugging and Troubleshooting
When builds fail, Kaniko provides detailed logs. Enable verbose output and understand common failure modes:
# Enable debug logging
--verbosity=debug
# Show timestamps for performance analysis
--log-timestamp=true
# Save the full build output to a file
--log-format=text
Common Issues and Solutions
- Authentication failures — Verify the config.json file is mounted at the correct path and contains valid, non-expired credentials. For ECR, ensure the AWS region matches the registry endpoint
- Missing layer cache — If cache misses are unexpected, check that the cache-repo is accessible and that the base image digest hasn't changed. Use
--verbosity=debugto see cache lookup keys - Context timeout — Large Git repositories may cause timeouts. Use
--git-depth=1and--git-single-branch=trueto reduce clone time - Snapshot misses — If layers are missing file changes, switch to
--snapshot-mode=fullto force checksum comparison - Multi-stage artifacts not copying — Ensure stage names are correct and that the
--targetflag specifies the correct final stage
Performance Optimization
Building container images efficiently in CI pipelines requires careful tuning. Here are the key optimization levers:
# Optimized production configuration
executor \
--context=/workspace \
--dockerfile=/workspace/Dockerfile \
--destination=registry.example.com/app:latest \
--cache=true \
--cache-repo=registry.example.com/cache \
--cache-copy-layers=true \
--cache-ttl=168h \
--snapshot-mode=time \
--reproducible=true \
--push-retry=3 \
--push-retry-delay=1s \
--ignore-path=/workspace/.git \
--ignore-path=/workspace/tmp \
--verbosity=info
Layer Optimization Strategies
- Order Dockerfile instructions by change frequency — Put rarely-changing base dependencies early and frequently-changing application code late
- Combine RUN instructions — Use
&&to chain commands, reducing layer count and cache overhead - Use .dockerignore — Exclude unnecessary files from the build context to speed up snapshotting
- Leverage multi-stage builds — Keep final images small by separating build tools from runtime dependencies
- Warm the cache — Run periodic scheduled builds to keep cache layers fresh in the registry
Here's an optimized Dockerfile example that works exceptionally well with Kaniko's caching:
FROM node:18-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --production
FROM node:18-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:18-alpine AS runtime
RUN apk add --no-cache tini curl
COPY --from=builder /app/dist ./dist
COPY --from=deps /app/node_modules ./node_modules
EXPOSE 3000
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "dist/server.js"]
Security Best Practices
Kaniko's architecture inherently improves security compared to Docker-in-Docker, but additional hardening measures are recommended for production environments:
- Never run Kaniko with privileged access — The container should run with minimal capabilities. No host Docker socket mounting is ever required
- Use dedicated service accounts — In Kubernetes, bind Kaniko pods to a service account with only the permissions needed to read Secrets and mount volumes
- Rotate registry credentials regularly — Use short-lived tokens where possible (AWS ECR, GCR) rather than long-lived passwords
- Enable reproducible builds — The
--reproducible=trueflag ensures consistent image digests, critical for supply chain security - Scan base images before building — Kaniko doesn't scan images itself, so integrate vulnerability scanning in your pipeline before the Kaniko step
- Use HTTPS exclusively for registries — Never disable TLS verification (
--skip-tls-verify) in production - Limit build context — Use
.dockerignorefiles aggressively to prevent sensitive files from leaking into image layers
Network Policies for Kaniko Pods
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: kaniko-restricted-egress
spec:
podSelector:
matchLabels:
app: kaniko-build
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- port: 53
protocol: UDP
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
ports:
- port: 443
protocol: TCP
This network policy restricts Kaniko pods to only communicate with external registries over HTTPS (port 443) and DNS for service discovery, blocking all lateral movement within the cluster.
Multi-Architecture Builds
Kaniko supports building images for multiple CPU architectures. While Kaniko itself runs on a single architecture, you can orchestrate parallel builds for different platforms:
# Build for AMD64
executor \
--context=/workspace \
--dockerfile=/workspace/Dockerfile \
--destination=registry.example.com/app:latest-amd64 \
--custom-platform=linux/amd64
# Build for ARM64
executor \
--context=/workspace \
--dockerfile=/workspace/Dockerfile \
--destination=registry.example.com/app:latest-arm64 \
--custom-platform=linux/arm64
After building individual architecture images, use a manifest tool to combine them into a multi-arch manifest list. This is typically done as a separate pipeline step after Kaniko completes:
# Create a multi-arch manifest (separate step, not Kaniko)
docker manifest create registry.example.com/app:latest \
--amend registry.example.com/app:latest-amd64 \
--amend registry.example.com/app:latest-arm64
docker manifest push registry.example.com/app:latest
Custom Builds with Kaniko
Using Custom Base Images with Dependencies
Sometimes you need build-time tools that aren't in your base image. Kaniko executes RUN instructions in the context of the current intermediate image, so you can install tools on the fly:
FROM alpine:3.18
RUN apk add --no-cache curl jq
COPY script.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/script.sh
ENTRYPOINT ["/usr/local/bin/script.sh"]
Injecting Secrets at Build Time
For sensitive build-time secrets (API keys, tokens) that should never appear in image layers, use Dockerfile secret mounting (supported by Kaniko):
# Dockerfile with secret mount
FROM alpine:3.18
RUN --mount=type=secret,id=npm_token \
export NPM_TOKEN=$(cat /run/secrets/npm_token) && \
npm install --production
COPY . .
RUN npm run build
Mount the secret into the Kaniko container at build time:
apiVersion: v1
kind: Pod
metadata:
name: kaniko-secret-build
spec:
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:latest
args:
- "--context=/workspace"
- "--dockerfile=/workspace/Dockerfile"
- "--destination=registry.example.com/app:latest"
volumeMounts:
- name: npm-token-secret
mountPath: /run/secrets/npm_token
readOnly: true
volumes:
- name: npm-token-secret
secret:
secretName: npm-token
items:
- key: token
path: npm_token
The secret is available during the build but does not appear in any committed image layer, preserving security.
Monitoring and Observability
Kaniko emits structured logs that can be integrated into your observability stack. When running in Kubernetes, use a sidecar or your cluster's log aggregation to capture build metrics:
# Key metrics exposed in logs
--log-timestamp=true
--verbosity=info
# Example log output analysis
# Track: context download time, layer pull time,
# instruction execution time, layer push time,
# total build duration
For production pipelines, consider wrapping Kaniko with a metrics collector that records build duration, cache hit ratio, and failure rate. These metrics are invaluable for optimizing both your Dockerfiles and your infrastructure.
Conclusion
Kaniko represents a fundamental shift in how container images are built in cloud-native environments. By eliminating the Docker daemon dependency, it enables secure, scalable, and reproducible image builds directly within Kubernetes clusters. Its comprehensive configuration surface — from context sources and caching strategies to snapshot modes and authentication methods — gives developers precise control over every aspect of the build process.
The configuration patterns covered in this guide form the foundation for production-grade image building pipelines. Whether you're implementing a simple CI job with GitHub Actions or orchestrating complex multi-architecture builds across Tekton workflows, Kaniko's flag-driven interface adapts cleanly to your needs. The emphasis on registry-based caching, reproducible builds, and minimal privilege execution aligns with modern software supply chain security requirements.
As container ecosystems continue evolving toward OCI standards and fully declarative build systems, Kaniko's userspace execution model and registry-native caching will only become more relevant. Invest time in understanding its configuration deeply — the payoff manifests in faster builds, tighter security, and pipelines that run reliably regardless of the underlying infrastructure.