Build pipelines are not secure by default. Secrets—API keys, database credentials, private certificates—flow through CI/CD systems constantly, and most pipelines are designed with velocity in mind, not confidentiality. The result is a surprisingly common pattern: secrets that belong in a vault end up baked into container image layers, committed to artifact registries, and accessible to anyone with pull access.
The leak doesn’t require an attacker. It requires a Dockerfile.
What Most Teams Get Wrong?
The conventional wisdom is that secrets management means rotating secrets regularly and using a vault. Both are correct and both are insufficient for container pipelines. The build pipeline introduces an entirely different attack surface: the image itself.
When a secret is used during a build step—to pull a private package, authenticate to an internal registry, or configure a runtime service—it can end up in image layers that persist long after the build completes. Developers understand that they shouldn’t commit secrets to source code. Fewer understand that ENV MY_SECRET=value in a Dockerfile creates a permanent record in the image history, visible to anyone who runs docker history on the image.
Secret scanning catches secrets in source code. It doesn’t catch secrets baked into image layers. Those are invisible to scanners that don’t inspect the artifact itself.
What Secure Secrets Management in Pipelines Requires?
Build-time secrets must never become layer contents
Docker BuildKit’s –secret flag provides mount-based secrets that are available during the build but don’t persist in the image layers. This is the baseline expectation. RUN commands that use secrets should be structured so the secret is consumed and discarded within a single layer, not exported into the environment.
Registry access credentials deserve the same treatment as production secrets
The credentials used to push and pull images from private registries are high-value targets. If they’re stored as plain environment variables in CI configuration, a compromised runner exposes registry access to every image your team produces. Use short-lived OIDC tokens for registry authentication where possible, and treat registry credentials with the same rotation policy as production secrets.
Image inspection is the missing check
Container image security validation includes checking what’s actually inside the image—not just scanning for CVEs, but auditing layer contents for unexpected credentials, private keys, or configuration files that shouldn’t have been included. Automated inspection at the point of image promotion (before pushing to a production registry) catches build-time mistakes that no code scanner will find.
Multi-stage builds isolate build-time secrets from runtime images
Build-time dependencies—including the credentials to fetch them—don’t belong in the final runtime image. Multi-stage builds enforce this separation structurally: the build stage has access to secrets and private registries; the runtime stage receives only compiled artifacts, not the credentials used to build them. This pattern eliminates an entire class of accidental secret inclusion.
Runtime credential access should match the declared footprint
After deployment, containers should access only the secrets they’re supposed to access. Runtime monitoring that baselines normal credential access and alerts on deviations catches post-deployment credential theft: a compromised container reaching for secrets outside its declared scope.
Practical Steps for Eliminating Secrets Leakage
Audit your existing images before your pipelines. Run history inspection on your current production images. Before implementing new pipeline controls, understand your current exposure. Many teams discover credentials in production images that have been there for months.
Replace ENV-based secrets with BuildKit secret mounts. This is a Dockerfile change, not an infrastructure change. The pattern is straightforward: RUN –mount=type=secret,id=my_secret cat /run/secrets/my_secret. Implementing this eliminates the most common build-time secrets leakage pattern.
Require container security software checks at image promotion gates. Promotion from a staging registry to a production registry is the right control point. Images that haven’t passed automated inspection—including content auditing for unexpected credentials—should not be promoted. This is a policy gate, not a manual review process.
Extend your SAST tooling to image layer inspection. SAST tools scan source code. They don’t scan built artifacts. Deploy layer inspection tooling as a separate pipeline step that specifically checks image contents for patterns matching credentials, private keys, and configuration secrets.
Implement short-lived credential patterns throughout. Long-lived credentials compound the damage of any leak. OIDC-based authentication for CI runners, short-lived registry tokens, and automatic secret rotation reduce the window of exposure when a secret does escape.
Frequently Asked Questions
What is the best way for developers to avoid leaking secrets in container build pipelines?
The most reliable approach is to never put secrets in Dockerfile ENV instructions or image layers in the first place. Use Docker BuildKit’s –mount=type=secret flag so secrets are available during the build but never written to image layers. Multi-stage builds provide additional protection by ensuring build-time credentials stay in the build stage and never reach the final runtime image.
How do you pass a secret to a Docker container securely?
At build time, use BuildKit secret mounts: RUN –mount=type=secret,id=my_secret cat /run/secrets/my_secret. At runtime, inject secrets via environment variables from a vault (such as HashiCorp Vault or AWS Secrets Manager) rather than baking them into the image. For CI authentication to registries, use short-lived OIDC tokens instead of long-lived credentials stored in environment variables.
How can secrets end up baked into container image layers?
Any ENV MY_SECRET=value line in a Dockerfile creates a permanent record in the image history, visible to anyone who runs docker history on the image. Similarly, a RUN command that downloads credentials and doesn’t clean them up in the same layer will preserve those credentials in the layer diff. Secret scanning tools that analyze source code will not catch credentials baked into built image layers—only image-layer inspection tooling will find them.
Why is image layer inspection important for secrets management?
Standard SAST tools scan source code, not built artifacts. Secrets that were never in source code—registry credentials used during a build step, private certificates fetched at build time—are invisible to code scanners but visible in image layer inspection. Automated layer inspection at image promotion gates is the control that catches build-time secrets leakage before it reaches a production registry.
Why This Problem Scales Badly?
A team with ten engineers and five services has manageable surface area. A team with a hundred engineers, dozens of services, and a continuous deployment pipeline creates orders of magnitude more opportunities for a developer to make a well-intentioned mistake that bakes a secret into an image.
The teams that have eliminated secrets leakage from their pipelines share a common characteristic: they stopped relying on developer discipline and built structural controls that make the secure path the default path. Multi-stage builds, BuildKit secret mounts, and automated image inspection at promotion gates aren’t audits of developer behavior—they’re systems that make the mistake impossible.
At scale, discipline doesn’t scale. Architecture does.