Container Security

BuildKit and Buildah: Building Containers Without Giving Away the Keys

Container build tools have direct access to your source code, secrets, and registries. BuildKit and Buildah offer security features that most teams ignore. Here is what to use and why.

Nayan Dey
Cloud Security Engineer
6 min read

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.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.