Back to Blog
DevOpsJuly 7, 20264 min read

Docker BuildKit Secrets: Securely Managing Private Credentials

Learn to use Docker BuildKit secrets to stop leaking private keys into your container image layers. Stop using build arguments for sensitive credentials.

DockerBuildKitSecurityDevOpsCI/CD

We’ve all been there: you need to pull a private dependency during a build, so you pass a GitHub token as a build argument. It works, it’s fast, and you move on. But that token is now burned into your image’s history forever, visible to anyone who runs docker history. Managing build-time secrets the right way is the only way to avoid shipping your production keys to a registry.

Why Build Arguments Fail Security Audits

When you use ARG in a Dockerfile, that value becomes part of the image metadata. If you inspect the layers or look at the build cache, those secrets are exposed. Even if you try to RUN rm the file later in the same layer, the data remains in the previous layer's snapshot.

I once spent about three hours debugging a security alert in a CI pipeline because a developer had passed an AWS secret key through a standard ARG. We had to rotate every credential in that account. That’s when I stopped relying on build arguments for anything sensitive and fully committed to BuildKit.

The Power of Docker BuildKit Secrets

Docker BuildKit secrets allow you to mount a sensitive file into the build container temporarily. The file exists in memory during the RUN command and vanishes the moment the command finishes. It never touches the filesystem of the image layers.

Comparing Build Arguments vs Secrets

FeatureBuild Arguments (ARG)BuildKit Secrets (--secret)
VisibilityStored in image historyNever stored in image
PersistencePersists in metadataVolatile (in-memory)
Use CaseNon-sensitive configAPI keys, SSH keys, tokens
ScopeAvailable during buildAvailable during RUN only

Implementing Secure Secrets

To use secrets, you first need to ensure BuildKit is enabled (it usually is by default in modern Docker versions). You don't need to change your docker-compose setup much, but you do need to update your Dockerfile syntax.

Here is how I typically handle a private SSH key for fetching private Go modules:

Dockerfile
# syntax=docker/dockerfile:1
FROM golang:1.21-alpine AS builder

# Define the secret mount
RUN --mount=type=secret,id=git_token \
    git config --global url."https://$(cat /run/secrets/git_token)@github.com/".insteadOf "https://github.com/" && \
    go mod download

Then, trigger the build from your terminal:

Bash
docker build --secret id=git_token,src=my_token.txt -t my-app:latest .

The id in the RUN instruction must match the id in the CLI flag. The src points to the file on your local machine containing the secret.

Avoiding Common Pitfalls

I’ve seen engineers try to use ENV to capture the secret, which is a major mistake. If you do RUN export MY_SECRET=$(cat /run/secrets/my_secret) && some-command, that environment variable might be captured in the shell history or logs if you aren't careful. Keep the secret strictly within the RUN command scope.

Also, remember that Docker build cache debugging is easier when you understand that secret mounts don't invalidate the cache the same way changing an ARG does. Because the secret isn't part of the layer's content, changing the value of the secret won't trigger a rebuild of the layer—only changing the instruction itself will.

When to Use What?

If you are just setting a VERSION or a BUILD_DATE, stick to ARG. It’s simple and doesn't require external file management. But if you are handling anything that grants access to your infrastructure, stop using ARG immediately. The overhead of managing a temporary file is negligible compared to the risk of leaking a production credential.

If you find yourself struggling with complex build pipelines, I’ve found that AI Automation & Agentic Workflow Development can help bridge the gap between messy manual build scripts and clean, automated CI/CD processes.

FAQ

Can I use Docker BuildKit secrets with Docker Compose? Yes. In your docker-compose.yml, use the secrets block under the build configuration. It maps local files to the build process just like the CLI flag.

Does the secret file end up in the final image? No. The /run/secrets/ directory is a tmpfs mount. It is unmounted as soon as the RUN command completes, leaving zero footprint in your image layers.

What if I need the secret for multiple steps? You need to add the --mount=type=secret... flag to every RUN instruction that requires it. It doesn't persist across layers, which is exactly why it’s secure.

I’m still experimenting with how to handle secrets in multi-stage builds where different stages need different permissions. The key is to keep the secret lifecycle as short as possible. Don’t over-engineer it; if you only need the token for a git clone, mount it once, use it, and let it go.

Similar Posts