Your source code scans clean. Your Docker images don't. Images are a blind spot for most secrets programs because the leak usually isn't in a repository at all — it's created at build time and shipped to a registry, where it sits quietly for years. This guide covers the three ways secrets get into images, how to find them, and how to keep them out.
How secrets end up in images
1. Build arguments
The classic:
Build args are baked into the image's layer metadata. Even if the Dockerfile only uses the arg for a single RUN step, the value persists in the build history — readable by anyone with the image.
2. COPY of env and config files
A .env in the build context is one careless line from being a permanent resident of the image. Same story for config files, key files, and anything else the app happens to need at build time.
3. RUN steps that pull credentials in
Cloning a private repo inside the build, downloading an artifact from an authenticated endpoint, installing a package with an embedded token — every one of these leaves credentials in intermediate layers, even when the final layer looks clean. Intermediate layers ship too.
Why "just delete it" doesn't work
Layers are immutable and content-addressed. Editing the Dockerfile and rebuilding creates a new image; the old layers — with the secret — remain in the registry, in caches, and on every machine that pulled them. Like git history, the only real fix is revocation plus rebuilding without the secret, never editing an existing image.
How to scan images for secrets
Scan every layer, not just the final filesystem. Secrets frequently live only in intermediate layers that never appear in the running container:
Vooda's image scanning unpacks each layer and runs the full 900+ rule engine against it, including build-arg metadata — the place most scanners miss. Pair it with a registry-wide scan so old tags and stale builds don't escape the net.
Keeping secrets out for good
- BuildKit secrets, not build args.
RUN --mount=type=secret,id=stripeexposes the value for exactly one step and never writes it to a layer. - Never COPY env files. Inject configuration at runtime — env vars, mounted secrets, or a secrets manager.
- Multi-stage builds. Credentials belong in build stages that produce artifacts, never in the final stage that ships.
- .dockerignore the sensitive files.
.env,*.pem,credentials/— if it isn't in the build context, it can't be copied. - Scan in CI before push. An image that never reaches the registry can't leak from it.
Frequently asked questions
How do secrets end up in Docker images?
Three common paths: build arguments baked into layer metadata, COPY commands pulling in .env or config files, and RUN steps that clone private repos or download authenticated artifacts.
Can I delete a secret from a Docker image?
No — layers are immutable. Deleting the line from the Dockerfile creates a new image; the old layers with the secret remain in your registry and every pulled copy. Revoke and rebuild, never edit in place.
How do I scan a Docker image for secrets?
Scan every layer, not just the final filesystem — secrets often live only in intermediate layers. Vooda unpacks each layer and runs the full rule engine, including build-arg metadata most scanners miss.
How do I keep secrets out of Docker images?
Use BuildKit secrets instead of build args, never COPY .env files, use multi-stage builds, add .dockerignore entries, and scan images in CI before pushing to any registry.
What should I do if a Docker image contains a leaked secret?
Revoke the credential at its provider first, rebuild the image without it, delete or expire the compromised image and its tags from every registry, and audit what the secret could access.