Cursus
I've seen countless Docker images accidentally exposed with hardcoded API keys, database passwords, and authentication tokens embedded in their layers. These security breaches often occur during the build process when developers need temporary access to private resources but inadvertently bake credentials into the final image.
The challenge is that traditional approaches to handling credentials during builds, like environment variables or build arguments, aren't designed for ephemeral use. They leave permanent traces in image metadata or layers, creating security vulnerabilities that persist long after the build completes. This creates a dilemma: how do you authenticate to private resources during builds without compromising security?
Docker build secrets solve this critical security challenge by providing a mechanism to use sensitive data during image builds without leaving traces in the final artifact.
In this tutorial, I'll walk you through implementing build secrets effectively, from basic concepts to advanced CI/CD integration, ensuring your container builds remain secure.
If you are new to Docker, I recommend taking one of our courses, such as Introduction to Docker, Containerization and Virtualization with Docker and Kubernetes, or Intermediate Docker.
What Are Docker Build Secrets?
Let's start by understanding what makes build secrets different from other credential management approaches. Docker build secrets are ephemeral pieces of sensitive information that are made available during the build process but never stored in the image layers or filesystem. They exist only in memory during specific build steps and disappear once those steps are complete.
The necessity of build secrets stems from modern application development workflows.
You often need to authenticate to private package repositories, clone private Git repositories, download licensed software, or access internal APIs during builds.
Without build secrets, developers resort to dangerous workarounds, like hardcoding credentials in Dockerfiles, passing secrets as build arguments (which persist in image metadata), or creating overly permissive base images with embedded credentials.
The risks of inadequate secrets handling are substantial. Exposed credentials can lead to unauthorized access to production systems, data breaches, compliance violations, and significant reputational damage. Common scenarios where secrets leak include API keys for package managers, SSH keys for Git operations, authentication tokens for internal services, and database credentials for build-time migrations.
Docker BuildKit: The Foundation of Secure Build Secrets
Before diving into implementation, you need to understand BuildKit, the modern build engine that makes secure secrets possible. BuildKit is Docker's next-generation builder that replaced the classic build engine, offering significant improvements in performance, caching, and security.
BuildKit uses a graph-based execution model that tracks dependencies between build steps. This architecture allows BuildKit to mount secrets as temporary file systems that exist only during specific RUN instructions, ensuring they never become part of the image layers.
Unlike classic Docker builds, where everything in the build context becomes part of the build cache, BuildKit treats secrets as special resources that bypass the layer cache entirely.
Enabling BuildKit is straightforward. For Docker 23.0 and later, BuildKit is the default builder. For earlier versions, set the environment variable DOCKER_BUILDKIT=1 before running build commands:
export DOCKER_BUILDKIT=1
docker build -t myapp .
Alternatively, enable it permanently by configuring the Docker daemon or using Docker Buildx, which always uses BuildKit. The key difference between classic builds and BuildKit-enabled builds is that secrets in classic builds would persist in intermediate layers if not carefully managed, while BuildKit guarantees secrets are ephemeral and never written to disk as part of the image.
With BuildKit's secure foundation in place, let's explore the different types of secrets it enables and how to implement each one effectively.
Types and Implementation of Docker Build Secrets
BuildKit supports three primary mechanisms for handling secrets during builds, each designed for specific use cases. Understanding when to use each type ensures optimal security and functionality.
Secret mounts: general-purpose sensitive data
Secret mounts provide the most flexible approach for handling sensitive data during builds. They allow you to mount secrets as files at specified paths during RUN instructions, making credentials available to build commands without persisting them in layers.
The process involves two steps: passing the secret to the build command and mounting it in the Dockerfile. First, create a secret file locally or use an environment variable. Then reference it during the build:
docker build --secret id=api_key,src=./api_key.txt -t myapp .
In your Dockerfile, mount the secret during the RUN instruction that needs it:
RUN --mount=type=secret,id=api_key \
API_KEY=$(cat /run/secrets/api_key) && \
curl -H "Authorization: Bearer $API_KEY" https://api.example.com/package > /app/data.json
By default, secrets mount at /run/secrets/<id>, but you can specify custom targets using the target parameter.
Best practices include mounting secrets only in the specific RUN commands that need them, never copying secrets to the image filesystem, and using multi-stage builds to separate secret-requiring stages from the final image.
The source can be a file path, or use env to pass secrets from environment variables: --secret id=token,env=API_TOKEN.
To mount a secret as an environment variable instead of a file, use the env option:
RUN --mount=type=secret,id=db_user,env=DB_USER \
--mount=type=secret,id=db_password,env=DB_PASSWORD \
./setup-database.sh
You can use both target and env options together to mount a secret as both a file and an environment variable.
Best practices include mounting secrets only in specific RUN commands that need them, never copying secrets to the image filesystem, and using multi-stage builds to separate secret-requiring stages from final images.
SSH mounts: secure access to private resources
SSH mounts specifically handle SSH-based authentication, primarily for accessing private Git repositories during builds. Rather than copying SSH keys into the image (a security nightmare), SSH mounts provide temporary access to your SSH agent during build time.
To use SSH mounts, ensure your SSH agent is running locally with the required keys loaded. Then reference the SSH mount in your Dockerfile:
RUN --mount=type=ssh \
git clone git@github.com:myorg/private-repo.git /app
Build the image with SSH forwarding enabled:
docker build --ssh default -t myapp .
For multiple hosts with different keys, you can specify named SSH mounts:
RUN --mount=type=ssh,id=github \
git clone git@github.com:myorg/repo.git
Then provide specific keys during the build:
docker build --ssh github=$HOME/.ssh/github_key -t myapp .
You’ll find that this approach maintains security by never exposing private keys in the image while enabling authenticated access to private repositories during the build process.
Git authentication for remote contexts
When your Docker build needs to access private Git repositories, either as the build context itself or when fetching dependencies during the build, you need a way to authenticate without embedding credentials. This is particularly common when building from a private repository URL or using ADD instructions to fetch private code.
BuildKit provides two pre-defined secrets for Git authentication: GIT_AUTH_TOKEN and GIT_AUTH_HEADER. These are "pre-flight" secrets that authenticate the builder before any Dockerfile instructions execute, securing the initial repository fetch.
The most common pattern uses GIT_AUTH_TOKEN for token-based HTTPS authentication:
GIT_AUTH_TOKEN=$(cat ~/.github-token) docker build \
--secret id=GIT_AUTH_TOKEN \
https://github.com/myorg/private-repo.git
This works seamlessly with ADD instructions fetching private repositories:
FROM alpine
ADD https://github.com/myorg/private-configs.git /configs
For ephemeral CI/CD environments, inject tokens from your platform's secret store:
- name: Build from private repo
env:
GIT_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
docker build --secret id=GIT_AUTH_TOKEN https://github.com/myorg/app.git
The GIT_AUTH_HEADER secret provides an alternative for custom authentication schemes when standard token authentication isn't sufficient.
How to Use Docker Build Secrets
Now that we've covered the types of build secrets, let's explore how to implement them effectively in real-world scenarios.
Creating and structuring secrets for build time
Proper secret preparation is crucial for security. Store secrets in files outside your Docker build context, ensure they're added to .gitignore and .dockerignore, and use restrictive file permissions (600 or 400).
For multiple secrets, create a dedicated secrets directory:
mkdir -p .secrets
echo "my-api-key" > .secrets/api_key
chmod 600 .secrets/*
Then reference them during builds:
docker build \
--secret id=api_key,src=.secrets/api_key \
--secret id=db_password,src=.secrets/db_password \
-t myapp .
For environment variable-based secrets common in CI/CD:
docker build \
--secret id=api_key,env=API_KEY \
--secret id=db_pass,env=DB_PASSWORD \
-t myapp .
Integration with multi-stage builds
Multi-stage builds are powerful when combined with build secrets. Use secrets in early stages for fetching dependencies, then copy only necessary artifacts to the final stage:
# Build stage - secrets used here
FROM python:3.11 AS builder
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=secret,id=api_key \
API_KEY=$(cat /run/secrets/api_key) && \
curl -H "Authorization: Bearer $API_KEY" https://api.company.com/data.json -o data.json && \
pip install -r requirements.txt
# Final stage - no secrets present
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=builder /app/data.json ./data.json
COPY . .
CMD ["python", "app.py"]
This ensures secrets exist only during the builder stage and never appear in the final image.
Multi-stage builds elegantly handle single secrets, but real-world applications often require more complexity.
Let's examine how to manage scenarios involving multiple secrets or intricate build requirements
Handling multiple secrets and complex scenarios
Complex builds often require multiple secrets within the same RUN instruction. BuildKit supports mounting multiple secrets simultaneously:
RUN --mount=type=secret,id=api_key \
--mount=type=secret,id=db_password \
API_KEY=$(cat /run/secrets/api_key) && \
DB_PASS=$(cat /run/secrets/db_password) && \
./configure.sh
For scenarios requiring secrets in multiple commands, combine commands in a single RUN instruction to minimize secret mounts while maintaining security boundaries.
Docker Build Secrets Security Best Practices
Implementing build secrets correctly requires following established security practices. Let me share the patterns I've found most effective for maintaining security throughout the build process.
Preventing secret exposure and leakage
After building an image, verify secrets haven't leaked by inspecting the image layers:
docker history myapp:latest
docker save myapp:latest | tar -x
Examine the extracted layers for any sensitive data. Additionally, distinguish between build-time secrets (temporary, used during builds) and runtime secrets (needed when containers run). Never use build secrets for runtime credentials. Use Docker secrets, Kubernetes secrets, or environment variables injected at runtime instead.
Always add secret files to .gitignore:
# .gitignore
.secrets/
*.key
And to .dockerignore to prevent accidental inclusion in build contexts:
# .dockerignore
.secrets/
*.key
.env
Managing secrets in CI/CD environments
CI/CD platforms provide native secret management that integrates seamlessly with Docker build secrets. In GitHub Actions:
- name: Build Docker image
env:
API_KEY: ${{ secrets.API_KEY }}
run: |
docker build \
--secret id=api_key,env=API_KEY \
-t myapp .
The key principle is leveraging platform-provided secret stores rather than storing secrets in repositories. CI/CD secrets are injected as environment variables, which BuildKit can consume directly through the env source type.
Secret permissions and access control
When building as non-root users, ensure secret files have appropriate read permissions. If your Dockerfile uses USER to switch to a non-root user, mount secrets in locations accessible to that user:
FROM python:3.11-slim
RUN useradd -m appuser
USER appuser
RUN --mount=type=secret,id=token,uid=1000 \
cat /run/secrets/token > /dev/null
For enhanced security compliance, implement secret rotation strategies. Use short-lived tokens when possible, implement automated rotation in CI/CD pipelines, and maintain audit logs of secret usage.
Consider secrets with expiration dates and automated renewal processes to minimize exposure windows.
So far, we've focused on single-container builds, but modern applications often consist of multiple services. Let's see how Docker Compose extends build secret capabilities to multi-service architectures.
Integrating Build Secrets With Docker Compose
Docker Compose simplifies managing build secrets across multi-service applications. In your docker-compose.yml, define secrets at the top level and reference them in service build configurations:
services:
app:
build:
context: .
secrets:
- api_key
- db_password
secrets:
api_key:
file: ./.secrets/api_key
db_password:
environment: DB_PASSWORD
Then in your Dockerfile, mount the secrets as usual:
RUN --mount=type=secret,id=api_key \
--mount=type=secret,id=db_password \
./setup.sh
Build with Compose:
docker-compose build
This pattern scales well for applications with multiple services requiring different secrets, maintaining centralized secret management while preserving security boundaries between services.
As you implement build secrets across your projects, you'll inevitably encounter issues. Let's address the most common challenges and their solutions to help you troubleshoot effectively.
Common Challenges and Troubleshooting
Even with proper implementation, you'll encounter challenges. Here are the most common issues and solutions.
Syntax errors and mount declaration issues
The --mount syntax is strict. Common mistakes include missing commas between parameters, incorrect parameter names, and wrong mount types. The correct syntax is:
RUN --mount=type=secret,id=secret_name,target=/path \
command
If builds fail with "secret not found," verify BuildKit is enabled, and the Dockerfile ID matches the --secret id in your build command.
Secrets not available or not found
When secrets aren't accessible, verify the source file exists and is readable:
cat .secrets/api_key
docker build --secret id=api_key,src=.secrets/api_key .
For environment variables, confirm they're set:
echo $API_KEY
docker build --secret id=api_key,env=API_KEY .
Environment variable vs. file confusion
With build secrets, secrets are always mounted as files by default at /run/secrets/<id>. To use them as environment variables, read the file content:
RUN --mount=type=secret,id=token \
export TOKEN=$(cat /run/secrets/token) && \
curl -H "Authorization: Bearer $TOKEN" https://api.example.com
Never use build arguments (ARG) for secrets, as they persist in image metadata.
Persistence and layer caching issues
Secrets don't persist between RUN instructions by design. If multiple RUN commands need the same secret, either combine them or remount the secret:
RUN --mount=type=secret,id=token \
command1
RUN --mount=type=secret,id=token \
command2
BuildKit ensures secrets never enter the layer cache.
Once you've mastered the fundamentals and common troubleshooting, you may encounter scenarios requiring more sophisticated approaches. Let's explore advanced use cases and edge cases that go beyond standard implementations.
Docker Build Secrets Advanced Uses
For complex deployment scenarios, you'll need to handle additional considerations beyond basic implementation.
Secrets in complex CI/CD pipelines
Multi-stage CI/CD pipelines often require secrets across different stages. Implement stage-specific secrets rather than sharing credentials. Use CI/CD platform features for secret scoping, limiting which pipeline stages can access which secrets. Automate secret cleanup after builds to minimize exposure windows.
Secrets with non-Docker builders
Alternative builders like Buildah and Kaniko have varying support for BuildKit's secret syntax. Buildah supports similar secret mounting through --secret flags. Kaniko, designed for Kubernetes environments, uses different mechanisms, typically Kubernetes secrets mounted as volumes. When using non-Docker builders, consult their specific documentation, as implementations differ significantly.
Secrets and security compliance
Build secrets align with security compliance frameworks by providing auditable, ephemeral credential access.
For GDPR compliance, ensure secrets don't leak personally identifiable information into layers. SOC2 requirements benefit from building secret audit trails—log when secrets are used and by whom. Maintain records of secret access for compliance audits.
Rotating and updating secrets
Establish processes for secret rotation without disrupting builds. Use versioned secret files or environment variable naming conventions that allow switching between versions.
Implement automated rotation in CI/CD pipelines where secrets are refreshed from central stores. For tokens with expiration dates, monitor expiration and automate renewal processes.
Conclusion: Best Practices and Recommendations
Docker build secrets represent a fundamental security improvement for containerized application development. Throughout this tutorial, we've explored how to implement secrets securely, from basic secret mounts to complex CI/CD integration.
The key takeaways are straightforward: always use BuildKit's secret mounting mechanisms rather than build arguments or hardcoded credentials, implement multi-stage builds to isolate secret-using stages from final images, leverage CI/CD platform secret management for automated workflows, and regularly audit images to verify secrets haven't leaked into layers.
For organizations adopting these practices, start with a secret management policy defining which secrets require build-time access, establish automated rotation schedules, implement comprehensive testing to verify secrets don't persist in images, and train development teams on proper secret handling patterns. By treating build secrets as a critical security component, you'll significantly reduce your attack surface and build more secure containerized applications.
To keep learning, I recommend checking out the following resources:
Docker Build Secrets FAQs
What are Docker build secrets?
Docker build secrets are ephemeral pieces of sensitive information made available during the build process but never stored in image layers, existing only in memory during specific build steps.
What types of build secrets does Docker support?
Docker supports three types: secret mounts for general-purpose sensitive data, SSH mounts for secure Git repository access, and Git authentication secrets (GIT_AUTH_TOKEN and GIT_AUTH_HEADER) for private remote contexts.
How do I prevent secrets from appearing in my Docker images?
Use BuildKit's --mount=type=secret syntax in RUN instructions combined with multi-stage builds, never use build arguments or environment variables for secrets, and regularly audit images using docker history to verify no leakage.
Can I use Docker build secrets with Docker Compose?
Yes, Docker Compose supports build secrets through the secrets section in your compose file, allowing you to define secrets at the top level and reference them in service build configurations.
How do build secrets work in CI/CD pipelines?
CI/CD platforms inject secrets as environment variables that can be passed to Docker builds using --secret id=name,env=VARIABLE_NAME, enabling automated workflows without storing credentials in repositories.
As the Founder of Martin Data Solutions and a Freelance Data Scientist, ML and AI Engineer, I bring a diverse portfolio in Regression, Classification, NLP, LLM, RAG, Neural Networks, Ensemble Methods, and Computer Vision.
- Successfully developed several end-to-end ML projects, including data cleaning, analytics, modeling, and deployment on AWS and GCP, delivering impactful and scalable solutions.
- Built interactive and scalable web applications using Streamlit and Gradio for diverse industry use cases.
- Taught and mentored students in data science and analytics, fostering their professional growth through personalized learning approaches.
- Designed course content for retrieval-augmented generation (RAG) applications tailored to enterprise requirements.
- Authored high-impact AI & ML technical blogs, covering topics like MLOps, vector databases, and LLMs, achieving significant engagement.
In each project I take on, I make sure to apply up-to-date practices in software engineering and DevOps, like CI/CD, code linting, formatting, model monitoring, experiment tracking, and robust error handling. I’m committed to delivering complete solutions, turning data insights into practical strategies that help businesses grow and make the most out of data science, machine learning, and AI.


