Where Secrets Really End Up in Docker Images: Testing 8 Common Methods

A detailed analysis of eight different approaches for handling secrets during Docker image builds reveals that most methods leave sensitive data exposed in layers, configuration files, or build history. Experiments conducted on Ubuntu 24.04.1 LTS with Docker Engine 29.1.3 and BuildKit v0.26.2 showed that simply using RUN rm after COPY or overwriting files fails to remove secrets from immutable layers. Environment variables via ENV and build arguments via ARG both persist in image metadata and history. Only BuildKit Secrets with --mount=type=secret and multi-stage builds that avoid copying secrets into the final stage successfully prevent leakage. The study demonstrates that secrets can be extracted from saved images without running any containers. Developers are advised to adopt these two secure patterns to protect credentials in container supply chains.

HabrTranslated from Russian

Read at source

A comprehensive technical investigation examined exactly where secrets end up when building Docker images using eight different techniques. The tests used Ubuntu 24.04.1 LTS, Docker Engine 29.1.3, and BuildKit v0.26.2, with a test secret string SUPER_SECRET_KOTY_HULIGANY_2026 placed in various ways.

The first method, COPY, adds the secret file directly into an image layer. Even after running docker history, the file remains fully recoverable by extracting layers from a saved tar archive. Adding a subsequent RUN rm command only marks the file as deleted in a new layer; the original data stays intact in the previous layer.

Using ENV stores the secret value in the image configuration JSON under config.Env and also appears in build history. The ARG instruction keeps the value in history.created_by fields even though it does not appear in the final container environment.

The dedicated BuildKit Secrets feature, invoked with --mount=type=secret and --secret build flag, mounts the secret only temporarily during the RUN step. After the build completes, the value is absent from all layers, configuration JSON files, and history entries.

Multi-stage builds also proved effective when the secret is created and used only in an intermediate stage and never copied into the final image. No trace of the secret appears in the published artifact.

Combining echo and rm inside a single RUN instruction still leaves the secret string visible in build history and created_by metadata. Overwriting a copied file with a new value similarly fails because the original layer remains unchanged.

The experiments confirm that only two approaches — BuildKit Secrets and properly isolated multi-stage builds — avoid embedding secrets in the final Docker image. All other common patterns leave extractable data that can be recovered without ever starting a container.

Meridian digestThe week's most consequential security stories, distilled into one briefing.See the digest

More in Supply Chain & Open Source

Passwordless Kubernetes Image Pulling with Artifact Keeper via ServiceAccount Tokens
Malicious Code Injected into LiteLLM Open Source AI Package via Supply Chain Attack
GhostAction Campaign Compromises 790 GitHub Repositories via Malicious Workflows