Fix Docker BuildKit Secret Mount: Secure Build-Time Credentials
Quick Answer
- Conclusion: Use
--mount=type=secretwith BuildKit to pass sensitive data (API tokens, passwords, SSH keys) during Docker builds without persisting them to image layers or metadata. - First checks: Enable BuildKit with
DOCKER_BUILDKIT=1or usedocker buildx; verify theidin--secretmatches theidin--mount=type=secretexactly; ensure file paths are absolute and readable by the Docker daemon. - Minimal command:
docker build --secret id=my_token,env=MY_TOKEN -t my-image .withRUN --mount=type=secret,id=my_token,env=MY_TOKENin Dockerfile. - Version boundary: Requires Docker Engine 18.09+ with BuildKit enabled;
docker buildxis recommended for multi-platform builds.
What Problem It Solves
Traditional Docker builds using ARG or ENV for secrets (API tokens, passwords, SSH keys) leave those values visible in image history (docker history) and final image layers. Anyone with image access can extract them. BuildKit secret mounts solve this by making secrets available only during RUN instruction execution, then automatically unmounting them—never writing to any layer or metadata.
When This Error or Setup Appears
You need BuildKit secret mounts when:
- Cloning private Git repositories during build
- Installing packages from private npm, pip, or Maven registries
- Authenticating with external APIs during build steps
- Injecting AWS/GCP credentials for build-time resource access
- Using SSH agent forwarding for private dependency fetching
- Running multi-stage builds where intermediate stages need temporary credentials
Common in CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins) and local development with private dependencies.
Minimal Working Configuration
Enable BuildKit (one of these):
BASHexport DOCKER_BUILDKIT=1 # or docker buildx create --use
Dockerfile (Dockerfile):
DOCKERFILEFROM alpine:latest # Mount secret as file (default path: /run/secrets/<id>) RUN --mount=type=secret,id=my_token \ cat /run/secrets/my_token # Mount secret as environment variable RUN --mount=type=secret,id=db_password,env=DB_PASSWORD \ echo "Password length: ${#DB_PASSWORD}" # Mount SSH agent socket RUN --mount=type=ssh \ ssh -o StrictHostKeyChecking=no git@github.com
Build command:
BASH# Secret from environment variable docker build --secret id=my_token,env=MY_TOKEN -t my-image . # Secret from file docker build --secret id=db_password,src=/path/to/password.txt -t my-image . # SSH agent forwarding docker build --ssh default -t my-image .
Parameters and Environment Variables
Build-time Secrets (--secret)
| Parameter | Required | Description |
|---|---|---|
id | Yes | Identifier matching --mount=type=secret,id=<id> in Dockerfile |
src | No | File path (absolute) containing secret value |
env | No | Environment variable name containing secret value |
If both src and env are omitted, BuildKit looks for the id as an environment variable name.
Pre-defined Git Authentication Secrets
These secrets are automatically available when using BuildKit with Git contexts:
| Secret Name | Description |
|---|---|
GIT_AUTH_TOKEN | HTTP Basic auth with username x-access-token |
GIT_AUTH_HEADER | Raw Authorization header value |
Usage:
BASHdocker build --secret id=GIT_AUTH_TOKEN,env=GITHUB_TOKEN \ https://github.com/user/private-repo.git#main
HTTP Authentication for COPY/ADD
| Secret Pattern | Description |
|---|---|
HTTP_AUTH_TOKEN_<host> | Adds Authorization: Bearer <token> header |
HTTP_AUTH_HEADER_<host> | Adds custom Authorization header |
Example:
BASHdocker build \ --secret id=HTTP_AUTH_TOKEN_artifacts.example.com,env=ARTIFACT_TOKEN \ -t my-image .
SSH Agent Forwarding (--ssh)
| Parameter | Description |
|---|---|
default | Forward the default SSH agent socket |
id=<key> | Forward a specific SSH key file |
Root Cause Analysis
The fundamental issue is Docker image layer immutability. Each RUN instruction creates a new layer. With ARG or ENV, the value is baked into that layer's metadata and content. Even if you delete the secret file in a later RUN, the previous layer still contains it.
BuildKit solves this by:
- Mounting secrets as tmpfs filesystems during
RUNexecution - Unmounting them immediately after the instruction completes
- Never recording the secret in layer diffs or image history
The secret is only accessible within the specific RUN instruction that uses --mount=type=secret.
Common Errors and Fixes
| Error | Solution |
|---|---|
secret not found: id=my_secret | Ensure id matches exactly between --secret and --mount=type=secret. Verify the source (src or env) exists and is accessible. |
file not found: /path/to/secret | Use absolute paths only. In CI, check working directory. Use $(pwd)/secret.txt for relative paths. |
process did not complete successfully: exit code 1 | Debug by adding RUN ls /run/secrets/ before the failing command to verify secret mounting. |
failed to mount secret: permission denied | Set file permissions to 600 or 644: chmod 600 /path/to/secret. Ensure Docker daemon user can read the file. |
Production Notes and Security Checks
-
File permissions: Set secret files to
600(chmod 600 /path/to/secret). Avoid world-readable secrets. -
Avoid secret leakage in logs: Never
echoorprintsecret values in DockerfileRUNcommands. Use--quietflags where possible. -
CI/CD best practices: Store secrets in CI secret managers (GitHub Secrets, GitLab CI Variables). Pass via environment variables:
BASHdocker build --secret id=my_token,env=MY_TOKEN . -
Remote builds: When using
docker buildx build --push, secrets traverse the network. Use TLS-encrypted Docker daemon connections. -
Concurrent builds: Avoid sharing secret files between concurrent builds. Use environment variables or per-build temporary files.
-
Secret size limit: BuildKit typically limits individual secrets to ~1MB. For larger data, consider splitting or alternative approaches.
-
Multi-stage builds: Secrets are only available in stages that explicitly mount them. This is intentional—secrets don't carry forward between stages.
FAQ
Q: How is BuildKit secret mount different from Docker ARG or ENV?
A: ARG and ENV persist values to image metadata (docker history) and final layers. Anyone with image access can extract them. BuildKit secret mounts use tmpfs that exists only during RUN execution—no trace remains in the image. This is the primary security advantage.
Q: How do I use BuildKit secrets in GitHub Actions?
A: Store secrets in GitHub Secrets, then pass them as environment variables:
YAML- name: Build Docker image run: | docker build \ --secret id=my_token,env=MY_TOKEN \ -t my-image . env: MY_TOKEN: ${{ secrets.MY_TOKEN }}
Q: Can I use both file and environment variable sources for the same secret?
A: Yes. In Dockerfile: --mount=type=secret,id=mysecret,target=/tmp/secret,env=MY_SECRET. This makes the secret available both as a file at /tmp/secret and as the environment variable MY_SECRET during that RUN instruction.