A Dockerfile line like ENV AWS_SECRET_ACCESS_KEY=AKIA... doesn't just set a variable for the running container — it writes that value into a filesystem layer that ships with the image, gets pushed to a registry, and stays retrievable with a single docker history --no-trunc command even after a later build stage appears to remove it. Docker image secrets leakage is this class of exposure: credentials, API keys, private SSH keys, and tokens baked into image layers, manifests, or build cache instead of injected at runtime. It happens through COPY . . sweeping up a .env file, ARG values persisting in the build history, or a base image silently inheriting a secret from an upstream build stage. Because container registries — Docker Hub, ECR, GCR, private Artifactory instances — are frequently public, misconfigured, or shared across teams, one leaked layer can hand cloud credentials to anyone who can pull the image. Below is how the leakage actually happens, documented incidents that resulted, and how to detect and stop it.
What Counts as Docker Image Secrets Leakage?
Docker image secrets leakage is any case where a credential — a cloud access key, database password, private key, npm/PyPI token, or webhook secret — ends up stored in an image's layers, metadata, or build cache and becomes retrievable by anyone who can pull or inspect that image. This is distinct from a secret leaking in source control, though the two are related: a secret committed to a repo often ends up in an image the moment someone runs docker build . against that repo without a .dockerignore entry excluding it. Three vectors account for most incidents: ENV/ARG instructions that persist the value in docker history, COPY/ADD operations that copy .env, .git, id_rsa, or .npmrc files into the build context, and multi-stage builds where an early stage clones a private repo with an embedded token and a later stage inherits that layer. Once a secret is in any layer, docker save myimage:latest | tar -xf - extracts every layer's tarball to disk, making the value plain-text-readable regardless of what the final CMD does.
How Do Secrets End Up Baked Into an Image's Layers?
Secrets get baked in because Docker layers are immutable and additive: deleting a file in a later RUN step does not remove it from the earlier layer where it was written, it only hides it from the final container filesystem. A common pattern is RUN git clone https://user:ghp_abc123@github.com/org/private-repo.git && npm install, which leaves the token in that layer's tar archive even if the .git directory is rm -rf'd two lines later — the deletion is itself a new layer, not an erasure of the old one. ARG values behave the same way: an ARG NPM_TOKEN passed at build time is captured in the image's build history and in docker inspect, unless BuildKit's secret-mount syntax is used instead. Base image inheritance compounds this — if a team publishes an internal base image that was built with a leaked ARG, every downstream image FROM-ing it inherits the exposed layer, often without anyone downstream realizing it originated three Dockerfiles upstream.
Which Real-World Incidents Show the Impact of Docker Secrets Leakage?
Real incidents show the impact ranges from mass credential harvesting to supply-chain compromise. In April 2019, Docker disclosed a Docker Hub database breach affecting roughly 190,000 accounts, in which GitHub and Bitbucket access tokens tied to autobuild repositories were exposed, forcing Docker to revoke those tokens and require reauthorization for affected autobuilds. Starting in 2020 and continuing through 2021, the TeamTNT threat group ran automated campaigns — documented by Cado Security, Trend Micro, and Palo Alto Networks Unit 42 — that scanned for exposed Docker daemons and harvested AWS credentials from ~/.aws/credentials files and environment variables baked into or mounted inside running containers, then used those keys to spin up cryptomining infrastructure in victim cloud accounts. In April 2021, Codecov disclosed that its Bash Uploader Docker image creation script had been altered by an attacker as early as January 31, 2021, allowing exfiltration of CI/CD environment variables — including cloud credentials and signing keys — from thousands of downstream customer pipelines that pulled the compromised image. And per GitGuardian's 2024 State of Secrets Sprawl report, 12.8 million secrets were found exposed in public GitHub commits during 2023 alone, a corpus that includes the Dockerfiles, CI YAML, and build-arg definitions that directly feed image builds.
How Can You Detect Secrets That Are Already Baked Into an Image?
You detect baked-in secrets by scanning every filesystem layer individually, not just the final running container, because a secret can be deleted in a later stage yet remain fully intact in an earlier one. docker history --no-trunc <image> surfaces the literal command strings used to build each layer, which catches careless ARG/ENV secrets immediately. Extracting the full image with docker save <image> -o image.tar and untarring each layer lets tools like gitleaks or TruffleHog scan the actual file contents, catching secrets that never appeared in a build command but were copied in as files — a .env or id_rsa swept up by COPY . .. The open-source tool dive visualizes layer-by-layer file changes and is specifically useful for spotting a large file added in one layer and deleted in the next, a strong signal something was hidden rather than removed. Production-grade container scanners go further by parsing the image manifest, all ARG/ENV history, and every layer's diff against known secret patterns (AWS key prefixes like AKIA, PEM headers, JWT structures) in one pass, which matters because manual layer-by-layer review does not scale past a handful of images.
How Do You Stop Secrets From Getting Into Images in the First Place?
You stop leakage by never letting a secret touch a layer that survives the build, which Docker has supported natively since BuildKit shipped with Docker 18.09 in January 2019 via the RUN --mount=type=secret syntax — the secret is mounted into the build container's filesystem for that single instruction and is never written to a layer or captured in history. Multi-stage builds should keep any credential-touching step (cloning a private repo, installing from an authenticated registry) in an early, discardable stage, with only the compiled artifact — not the stage's filesystem — copied into the final image via COPY --from=. A .dockerignore file should explicitly exclude .env, .git, .npmrc, .aws/, and *.pem before the first docker build ever runs against a repo, since COPY . . will otherwise include anything present in the build context. At runtime, secrets belong in an orchestrator's secret store — Kubernetes Secrets, HashiCorp Vault, AWS Secrets Manager — injected as environment variables or mounted files at container start, not compiled into the image days or weeks earlier. Finally, a pre-merge CI gate running gitleaks or detect-secrets against both source and generated Dockerfiles catches the mistake before an image is ever pushed to a registry, which is meaningfully cheaper than rotating a credential after it's already been pulled by external users.
How Safeguard Helps
Safeguard scans container image layers and manifests during build and at rest in the registry, flagging embedded credentials, tokens, and keys before they reach production — and because reachability analysis maps which packages and code paths in an image are actually invoked at runtime, teams can prioritize a leaked secret in an actively-used base layer over one sitting in dead code nobody executes. Griffin AI correlates a detected secret with the specific Dockerfile instruction, commit, and PR that introduced it, cutting triage time from hours of manual docker history digging to a direct pointer at the fix. Safeguard's SBOM generation and ingest capabilities give security teams a persistent inventory of every image layer and its provenance, so a secret found in a shared base image can be traced to every downstream image that inherited it. When a leak is confirmed, Safeguard can open an auto-fix PR that swaps the offending ARG/ENV instruction for a BuildKit secret mount or removes the file from the build context entirely, closing the loop from detection to remediation without a manual rewrite.