0% прочитано

Updating a Docker Image Is Not Enough: What to Do About Old Layers and Secrets

A new PHP base image does not erase the history of an already-built Docker image. Using a real Laradock case, this article shows how to inspect metadata without printing secrets, how build cache differs from image layers, and why secrets require a separate clean rebuild and credential rotation.

2 листопада 2026 р. 10 хв читанняDocker

During a PHP security update, I could have stopped at a simple result: the new PHP-FPM, workspace, and phpMyAdmin images were already using patched PHP versions.

But a separate check revealed another issue: the Workspace image metadata contained non-empty BLACKFIRE_CLIENT_ID and BLACKFIRE_CLIENT_TOKEN variables. We did not print their values, but their presence alone meant that updating the PHP base image did not close the secrets question.

This distinction matters: a patched runtime, old image layers, image metadata, and build cache are different things. None of them should be considered cleaned simply because a new container reports a safe PHP version.

A New Base Image Does Not Rewrite Old History

A Docker image is made of layers. Building a new image does not open an old image and remove whatever was previously stored in its history.

A new image may use a patched PHP base image while older local images, their layers, or metadata remain on the machine until they are replaced or removed separately.

That means "PHP in the new container is patched" and "old images no longer contain sensitive data" are two different claims.

Inspect Metadata Without Printing Values

During a security audit, printing the whole .Config.Env is a bad idea because it may contain real credentials.

I only needed the variable names and whether their values were non-empty. The secret values themselves never had to appear in the console.

Print only potentially sensitive ENV names and SET/EMPTY state
IMAGE='your-workspace-image:tag' docker image inspect "$IMAGE" \  --format '{{json .Config.Env}}' |python3 -c 'import jsonimport sys env = json.load(sys.stdin) or [] for item in env:    if "=" not in item:        continue     key, value = item.split("=", 1) 
Never paste a full docker image inspect or environment dump into a public article, issue, or chat. Even if a secret looks old or test-only, its value should still be treated as sensitive.

Image Metadata and Layer History Need Separate Checks

Even if a variable has been removed from the current container configuration, that does not prove it never appeared in previous layers or build commands.

Docker history can be inspected locally, but its output can also contain sensitive fragments, so raw output should not be published.

Inspect image history locally
IMAGE='your-workspace-image:tag' docker history "$IMAGE"
If docker history --no-trunc is needed for investigation, run it locally and do not paste the raw output into public material without sanitizing it.

Build Cache Is Not the Same as Old Docker Images

After many rebuilds, my local Docker build cache had grown to many gigabytes. We inspected disk usage and then cleaned the build cache specifically.

Before doing that, the distinction matters: docker builder prune operates on build cache. It is not a command to remove all old images, containers, or volumes.

Check Docker disk usage before cleanup
docker system df
Clean build cache and verify the result
docker builder prune \  --all \  --force docker system df

In my case, the cleanup reclaimed about 16.33 GB of build cache. Afterward the build cache was 0B, while images, containers, and local volumes remained present.

After cleanup, I verified the image pins again to make sure removing build cache had not changed the required PHP image set.

Why builder prune Does Not Solve Secrets in Old Layers

Cleaning build cache reclaimed disk space, but it did not prove that secrets had disappeared from old Workspace images.

If an image still exists, its own layers and metadata remain part of that image. Build-cache cleanup and security cleanup are not the same operation.

A Secret Requires Two Separate Actions: Clean Rebuild and Rotation

If a credential reached image metadata or may have entered layer history, I do not consider removing it from the new Dockerfile sufficient.

Two separate actions are needed:

  1. a clean rebuild of a new image without embedding the secret and without reusing the problematic local image history where possible;
  2. credential rotation if the credential was real and may have been accessible from the old image.

Rotation matters because a clean new image does not automatically invalidate the old credential value.

In my Laradock setup, this clean rebuild and credential rotation remained a separate security follow-up. I do not mark them complete just because the PHP runtime was already patched.

What a Future Clean Rebuild Should Look Like

The exact command depends on the Laradock Dockerfile, build arguments, and required PHP extensions, so I do not replace the real rebuild with one universal command.

The principle is clear: the new build should start from a verified base, avoid passing secrets through persistent ARG or ENV, avoid using the problematic old image as a source layer, and then pass metadata and history checks again.

Example clean rebuild shape, not a universal Laradock command
# Generic example only.# Use the real Laradock Dockerfile, build args and service name. docker compose build \  --pull \  --no-cache \  workspace

--pull checks for a newer base image and --no-cache avoids reusing the previous build cache. These flags alone do not guarantee that secrets are absent: the Dockerfile and credential injection method still need review.

What to Verify After a Clean Rebuild

After a clean rebuild, I would not immediately jump to a normal docker compose up. First, the new image should be verified independently:

  • the new immutable image ID;
  • absence of unwanted credential names in .Config.Env;
  • local review of docker history;
  • the expected PHP version and required extensions;
  • the effective Compose model points to the new image;
  • old images are removed only after rollback and retention policy are understood.

Do Not Mix Four Different Tasks

After this case, I separate the work into four independent tasks:

  1. Patch the runtime - move to a safe PHP version;
  2. Pin the image - guarantee that Compose uses the patched image;
  3. Clean the build - remove unwanted history or metadata from the new image;
  4. Rotate the credential - invalidate the old secret if it may have been exposed.

Completing the first item does not automatically complete the other three.

A patched PHP version proves the runtime is updated. It does not prove that old Docker layers, metadata, and credentials are already safe.

Summary

In my Laradock environment, the PHP runtime was successfully patched, the images were pinned in Compose, and about 16.33 GB of build cache was cleaned separately.

But Workspace metadata revealed a separate security follow-up: credentials were present in image environment metadata, while historical layers still required a clean rebuild and, if necessary, rotation.

That is why I no longer treat "a new Docker image" as the end of a security update. Runtime, Compose pinning, metadata, layers, cache, and secret lifecycle all need separate verification.