Safeguard
Supply Chain•

Myth vs fact: deleting a file in a later layer removes it from the image

Deleting a file in a later Dockerfile step hides it in the final filesystem but not in the earlier layer. Here is how it works and what to do.

Safeguard Security Team
3 min read

Myth: if a later Dockerfile step deletes a file, the file is gone from the image. Fact: the OCI image format records the deletion as a marker in the newer layer, while the earlier layer still contains the file. Anyone who pulls the image layers can extract it.

What the spec says

The OCI image specification describes a layer as a serialized set of changes, not a full copy of the filesystem. Layers are applied on top of each other to build the complete filesystem.

A deletion is recorded as a whiteout: an empty file with a special name that tells the runtime to treat a path as deleted. The name is a dot, the letters wh, a dot, and then the name of the removed entry. To hide a whole directory's contents there is an opaque marker with a longer special name.

The specification also says whiteouts apply only to resources in lower or parent layers. So the earlier layer is untouched. The merged view you see in a running container shows the file as missing, but the data is still stored in the layer that added it. The specification does not discuss storage impact directly. This follows from how the layered model works.

Why it bites teams

Here is a typical mistake.

COPY id_rsa /root/.ssh/id_rsa
RUN git clone git@example.com:org/private.git
RUN rm /root/.ssh/id_rsa

The final container has no key file. The layer created by the first COPY still holds it. The image is a stack of tar archives, and extracting one layer gives you the key.

The same applies to:

  • Package caches you remove in a separate RUN
  • Source trees deleted after a build step
  • Config files with credentials, edited later
  • Large temporary downloads (a size problem, not only a secret problem)

What actually works

Never write the secret into a layer. Use a build secret mount so the value exists only for one instruction. See the build secrets post.

Delete in the same instruction that creates. If a temporary file must exist, create it and remove it within one RUN so no layer records it. This is fine for caches and downloads, but not a safe pattern for real credentials.

Use a multi-stage build. Do the work in a build stage and copy only the output. The earlier stage's layers are not part of the final image. See multi-stage builds.

Squashing is a patch, not a plan. Flattening layers can hide old content, but a secret that already reached a registry should be treated as exposed.

How to check an image you already have

  1. Export the image to a tar archive with your build tool.
  2. List the layer archives inside.
  3. Search each layer for the file name or string you worry about.
  4. If you find a secret, rotate it before cleaning anything.

Scanners that read every layer can find secrets in files that the final filesystem no longer shows. Safeguard's SBOM and dependency scanning covers what the image contains, and you can try it on a free scan.

Quick check

Does docker history show the deleted file?

It shows the instructions that created each layer, which often reveals the command that added the file. The file's contents are in the layer archive, not the history output.

Is the file visible when I run the container?

No. The runtime applies the whiteout, so the path looks deleted inside the running container.

Sources

Last reviewed: 19 October 2026.

Never miss an update

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

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.