The container build process is one of the most privileged operations in a software pipeline. It has access to source code, build secrets, registry credentials, and network connectivity. Yet most teams treat the build tool as a transparent pass-through—Dockerfile in, image out, nothing to worry about.
BuildKit and Buildah both address this gap, but in different ways. Understanding their security features is the difference between a build pipeline that protects your supply chain and one that is the supply chain risk.
BuildKit: Docker's Next-Generation Builder
BuildKit replaced the legacy Docker builder starting with Docker 18.09. It is the default in Docker 20.10+ and can be used standalone outside Docker entirely.
Secret Mounts: The Right Way to Handle Build Secrets
The oldest container security mistake is COPYing secrets into an image:
# WRONG: secret persists in image layers
COPY .npmrc /root/.npmrc
RUN npm install
RUN rm /root/.npmrc # Still in the layer
BuildKit introduced --mount=type=secret, which injects secrets at build time without persisting them in any layer:
# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm install
docker build --secret id=npmrc,src=$HOME/.npmrc -t myapp .
The secret is available during the RUN instruction only. It is never written to any layer. Even extracting every layer of the final image reveals nothing.
This works for:
- NPM tokens (
.npmrc) - PyPI credentials (
pip.conf) - SSH keys for private repositories
- API keys for build-time services
SSH Forwarding
For builds that need to clone private Git repositories, BuildKit forwards the host's SSH agent without copying keys:
# syntax=docker/dockerfile:1
RUN --mount=type=ssh \
git clone git@github.com:private/repo.git /src
docker build --ssh default -t myapp .
The SSH socket is forwarded. No private keys enter the build context or image layers.
Build Attestations and Provenance
BuildKit 0.11+ generates SLSA provenance attestations during builds:
docker build --attest type=provenance,mode=max -t myapp .
The attestation records:
- Who built the image
- What Dockerfile was used
- What build arguments were provided
- The complete build dependency tree
- Builder version and configuration
# Inspect the attestation
docker buildx imagetools inspect myapp:latest --format '{{json .Provenance}}'
This is a supply chain security control. Without provenance, you cannot verify that an image was built from the source code you think it was.
Rootless BuildKit
BuildKit runs without root privileges:
# Start rootless BuildKit daemon
rootlesskit buildkitd &
# Build using rootless builder
buildctl build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=. \
--output type=image,name=myapp:latest
In CI/CD environments, rootless BuildKit eliminates the need for Docker-in-Docker (DinD) with privileged mode, which is one of the most dangerous CI patterns.
Cache Security
BuildKit caches build layers aggressively for performance. But cache poisoning is a real risk in shared CI environments:
# Export cache to a registry (encrypted in transit)
docker build --cache-to type=registry,ref=registry.example.com/cache:latest \
--cache-from type=registry,ref=registry.example.com/cache:latest \
-t myapp .
Ensure your cache registry has the same access controls as your image registry. A poisoned cache layer can inject malicious code into builds.
Buildah: OCI-Native Build Tool
Buildah is the build component of the Podman ecosystem. It builds OCI images without requiring a daemon, a Dockerfile, or root privileges.
Daemonless Builds
Like Podman, Buildah does not need a daemon:
# Build from a Dockerfile
buildah bud -t myapp:latest .
# Or build interactively (scriptable)
container=$(buildah from alpine:3.18)
buildah run $container apk add --no-cache curl
buildah config --cmd "/app/run" $container
buildah commit $container myapp:latest
The scripted approach gives you programmatic control over each layer, which is useful for security tooling that needs to inspect or modify layers during the build process.
Rootless by Design
Buildah runs rootless without any special configuration on properly configured systems:
# As a regular user
buildah bud -t myapp:latest .
# Works immediately with user namespace mapping
No setup scripts. No daemon configuration. The user namespace isolation is built into Buildah's architecture.
Squash and Layer Control
Buildah gives fine-grained control over layer creation:
# Squash all new layers into one (reduces layer count)
buildah bud --squash -t myapp:latest .
# Squash only new layers (preserves base image layers)
buildah bud --squash-all -t myapp:latest .
# No caching (ensures clean build)
buildah bud --no-cache -t myapp:latest .
Squashing is useful when you must use a Dockerfile that copies then removes secrets. The squashed layer will not contain the intermediate state. But this is a workaround—using BuildKit secret mounts is the correct solution.
Image Signing with Buildah
Buildah integrates with sigstore/cosign for image signing:
# Build and push
buildah bud -t registry.example.com/myapp:v1.0 .
buildah push registry.example.com/myapp:v1.0
# Sign the image
cosign sign registry.example.com/myapp:v1.0
The signing step produces a signature that Kubernetes admission controllers can verify before allowing the image to run.
Security Comparison
| Feature | BuildKit | Buildah |
|---|---|---|
| Secret mounts | --mount=type=secret | Environment-based |
| SSH forwarding | --mount=type=ssh | Manual setup |
| Rootless | Supported | Default |
| Daemonless | Standalone mode | Always |
| Build attestations | SLSA provenance | Via cosign |
| Layer control | Dockerfile-based | Programmatic |
| Dockerfile compat | Full | Full |
When to Choose BuildKit
- You are in a Docker ecosystem and want incremental security improvements
- You need build attestations and provenance metadata
- Your CI/CD uses Docker-based tooling
- You want secret mounts in Dockerfiles
When to Choose Buildah
- You are in a Podman/Red Hat ecosystem
- You need programmatic build control (scripted builds)
- Your security policy requires daemonless, rootless builds
- You want to eliminate Docker dependency entirely
CI/CD Build Security Patterns
Kaniko: Builds in Kubernetes Without Privileges
For Kubernetes-native CI/CD, Kaniko builds images inside a container without requiring a Docker daemon or privileged mode:
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/myapp"
- "--destination=registry.example.com/myapp:latest"
volumeMounts:
- name: kaniko-secret
mountPath: /kaniko/.docker
volumes:
- name: kaniko-secret
secret:
secretName: registry-credentials
Build Policy Enforcement
Enforce build security policies in CI:
# GitHub Actions: secure build pipeline
- name: Build with BuildKit
env:
DOCKER_BUILDKIT: 1
run: |
docker build \
--secret id=npm_token,env=NPM_TOKEN \
--attest type=provenance,mode=max \
--attest type=sbom \
--no-cache \
-t $IMAGE:$TAG .
- name: Verify no secrets in image
run: |
docker save $IMAGE:$TAG | trufflehog docker --image -
- name: Sign the image
run: cosign sign $IMAGE@$(docker inspect --format='{{.RepoDigests}}' $IMAGE:$TAG)
How Safeguard.sh Helps
Safeguard.sh analyzes your container build configurations and identifies insecure patterns: secrets copied into layers, builds running as root, missing provenance attestations, and unsigned images. The platform integrates with your CI/CD pipeline to verify that builds follow your security policies before images reach a registry. For teams adopting BuildKit secret mounts or Buildah rootless builds, Safeguard.sh validates the configuration and confirms that secrets are not leaking through any layer of the final image.