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.
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) 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.
IMAGE='your-workspace-image:tag' docker history "$IMAGE"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.
docker system dfdocker builder prune \ --all \ --force docker system dfIn 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:
- a clean rebuild of a new image without embedding the secret and without reusing the problematic local image history where possible;
- 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.
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.
# 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:
- Patch the runtime - move to a safe PHP version;
- Pin the image - guarantee that Compose uses the patched image;
- Clean the build - remove unwanted history or metadata from the new image;
- 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.

