Safeguard
Container Security

Docker Secrets Management: Stop Baking Credentials Into Images

A secret written into a Docker layer is recoverable forever, even after you delete it. Learn the build-time and runtime patterns that keep credentials out of your images entirely.

Marcus Chen
Cloud Security Engineer
6 min read

There is a specific, avoidable mistake behind a large share of leaked credentials in cloud-native systems: a secret written into a container image layer. It feels harmless in the moment — you need a token to npm ci or clone a private repo during the build, so you pass it as a build argument or copy an .env file, maybe even delete it in a later step. But Docker images are made of immutable, cacheable layers, and "deleting" a file in layer seven does not remove it from layer three where it was written. Anyone who can pull the image can run docker history or unpack the layer tarballs and recover the secret in seconds. GitGuardian's annual State of Secrets Sprawl reports have documented millions of secrets exposed publicly each year, and container images pushed to public registries are a well-worn subset of that leakage. The fix is not to be more careful about deleting secrets — it is to never write them into a layer in the first place.

Why ARG and ENV are not safe for secrets

Two Dockerfile mechanisms are constantly misused for secrets. ARG values are visible in the build history and, if referenced, can end up embedded in layer metadata. ENV values are worse: they persist into the running container's environment and are trivially dumped by anything that can read /proc/self/environ or run env. Consider what this leaves behind:

# ANTI-PATTERN: do not do this
FROM node:20-slim
ARG NPM_TOKEN                # visible in build args
ENV NPM_TOKEN=$NPM_TOKEN     # persists into the image and runtime
RUN npm ci
RUN unset NPM_TOKEN          # too late — it is already in the layers

The unset accomplishes nothing. The token is baked into the layer where ENV set it and recoverable forever.

Build-time secrets: use BuildKit secret mounts

Docker BuildKit provides --mount=type=secret, which exposes a secret to a single RUN step as a file under /run/secrets/, without writing it to any layer. The secret exists only for the duration of that command and never persists.

# syntax=docker/dockerfile:1
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN --mount=type=secret,id=npm_token \
    NPM_TOKEN="$(cat /run/secrets/npm_token)" npm ci
COPY . .

Supply the secret at build time from an environment variable or file, not as a build arg:

NPM_TOKEN=$(vault kv get -field=token secret/npm) \
  docker build --secret id=npm_token,env=NPM_TOKEN -t app .

Inspecting the resulting image with docker history shows the RUN step but not the token — because it was never part of any layer.

Add a defensive .dockerignore

The build context is copied into the daemon, so a stray .env or private key can be pulled in by a broad COPY . . even if you never meant to include it. Treat .dockerignore like .gitignore and exclude credentials and VCS metadata by default:

.git
.env
.env.*
*.pem
id_rsa
**/credentials
.aws
.npmrc

Runtime secrets: inject, never embed

Build-time and runtime secrets are different problems. A database password or API key your application needs while running should never live in the image at all — it should be injected at runtime by the platform. In Docker Swarm, docker secret mounts secrets into a tmpfs at /run/secrets/. In Kubernetes, prefer an external secrets manager over native Secrets, which are only base64-encoded:

  • External Secrets Operator syncs secrets from AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, or HashiCorp Vault into the cluster on demand.
  • Vault Agent / CSI drivers mount secrets directly into a pod's filesystem, fetched at startup and never stored in etcd.
  • If you must use native Kubernetes Secrets, enable encryption at rest with a KMS provider so the values in etcd are actually encrypted.

The principle is the same in every case: the image is a public-by-assumption artifact; the secret arrives separately, at run time, scoped to the workload that needs it.

Rotate on the assumption you will leak

Even a disciplined team leaks a secret eventually — through a debug log, a misconfigured bucket, or a laptop that walks out the door. The controls above shrink the odds; rotation limits the damage when the odds catch up with you. Prefer credentials that are short-lived by design: cloud IAM roles assumed by the workload, database credentials brokered by Vault with a lease, and registry tokens minted per-build via OIDC. When a secret must be long-lived, automate its rotation on a schedule and make revocation a one-command operation, so a suspected exposure is a rotation, not a fire drill. A secret you can rotate in minutes is a contained incident; one that is hardcoded across a dozen images is a weekend.

Secrets handling checklist

SituationDo thisNot this
Token needed during buildBuildKit --mount=type=secretARG / ENV
Secret in the build contextExclude via .dockerignoreCOPY . . and hope
Runtime credentialInject from a secrets managerBake into ENV
Native K8s SecretEncrypt at rest + limit RBACRely on base64
Existing imageScan layers for leaked secretsAssume it is clean

How Safeguard helps

The hardest part of secrets hygiene is catching the one commit or one Dockerfile that slips through, and that requires scanning built images, not just source. Safeguard's container security scanning inspects image layers for embedded secrets — API keys, tokens, private keys — so a credential that made it into a layer through a cached build or a vendored dependency is caught before the image ships. Because secrets often enter through configuration and manifests too, Safeguard's IaC scanning flags plaintext secrets and insecure secret references in your Kubernetes and Terraform files at pull-request time, and Griffin AI helps prioritize and remediate what it finds. See how our approach compares to cloud-posture-first tools in Safeguard vs Wiz.

A leaked secret is one bad build away. Create a free Safeguard account to scan your images and manifests, or read the documentation to get started.

Never miss an update

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