Checkpoint Lab — Container Filesystems, Writable Layers, docker cp, exec Workflows, Processes, Signals, and PID 1
This checkpoint starts with one digest-identified base image, intentionally creates runtime filesystem drift, captures docker diff/copy/process evidence, demonstrates graceful signal and PID 1 behavior, then turns the intended file change into a declared image build and proves a replacement container starts from reproducible state.
Learning objectives
- Predict and verify at least two filesystem/process state changes before performing them.
- Produce a compact evidence packet containing version/context, image digest, container identity, mount state, docker diff results, PID/process evidence, signal/exit evidence, and copied-file provenance.
- Demonstrate the difference between runtime mutation and declared image content by replacing a modified container with one created from a newly built image.
- Use exact lab labels/names for cleanup and prove no broad prune, host-sensitive mount, real secret, or production daemon was involved.
- Bridge the evidence model into Chapter 07, where Dockerfile instructions become the primary mechanism for declaring image state.
1. Checkpoint mission and success criteria
Your goal is to prove—not merely state—that runtime filesystem changes and process behavior are separate from declared image state. You will create one digest-identified container, mutate it deliberately, record the delta, copy evidence, observe PID 1 and signal handling, rebuild the intended change into a new image, replace the original container, and prove the manual drift is gone.
2. Preflight and assumptions record
mkdir -p ch06-checkpoint/evidence
cd ch06-checkpoint
date -u +%Y-%m-%dT%H:%M:%SZ | tee evidence/00-started-at.txt
docker context show | tee evidence/01-context.txt
docker version | tee evidence/02-version.txt
docker info | tee evidence/03-info.txt
docker buildx version | tee evidence/04-buildx.txt || true
docker compose version | tee evidence/05-compose.txt || true
SOURCE='busybox:1.37.0'
docker pull "$SOURCE"
PINNED=$(docker image inspect "$SOURCE" --format '{{index .RepoDigests 0}}')
printf 'source=%s\npinned=%s\n' "$SOURCE" "$PINNED" | tee evidence/06-source-image.txt
Record any platform limitation: Windows container mode, remote context, Desktop VM boundary, unavailable Buildx plugin, or restricted daemon access. Do not fabricate unsupported observations.
3. Predict state changes before execution
Write evidence/07-predictions.txt with at least these
predictions in your own words:
- The original container ID will remain stable while it runs/stops, but its writable layer will gain files after copy/exec.
- The image digest/ID will not change when runtime files are added.
- The manually added files will disappear when the original container is removed and a replacement is created from a separately built image that does not declare them.
- The new declared file will be present in the replacement because it is part of the new image.
- A graceful TERM-aware primary process should log its handler and exit before timeout escalation.
The checkpoint is incomplete if you only perform commands without comparing actual evidence to predictions.
4. Create the original container and capture immutable/runtime identity
ORIGINAL='devops-academy-ch06-checkpoint-original'
docker run -d \
--name "$ORIGINAL" \
--label devops-academy.lab=ch06-checkpoint \
"$PINNED" \
sh -c 'trap "echo checkpoint-received-TERM; exit 0" TERM; mkdir -p /academy; echo original-runtime > /academy/runtime.txt; while :; do sleep 1; done'
docker inspect "$ORIGINAL" \
--format 'ID={{.Id}} Image={{.Image}} Status={{.State.Status}} Pid={{.State.Pid}} Started={{.State.StartedAt}}' | tee evidence/08-original-inspect.txt
docker inspect "$ORIGINAL" --format '{{json .Mounts}}' | tee evidence/09-original-mounts.json
docker top "$ORIGINAL" | tee evidence/10-original-top.txt
docker diff "$ORIGINAL" | tee evidence/11-original-diff.txt
Confirm that no unexpected host-sensitive bind mount or Docker socket is present. If it is, stop: you are not in the intended disposable lab.
5. Create controlled runtime drift
printf 'manual-copy-only\n' > manual.txt
docker cp manual.txt "$ORIGINAL":/academy/manual.txt
docker exec "$ORIGINAL" sh -c 'printf "exec-hotfix-only\n" > /academy/hotfix.txt; echo exec-pid=$$'
docker diff "$ORIGINAL" | tee evidence/12-diff-after-mutation.txt
docker exec "$ORIGINAL" sh -c 'sha256sum /academy/runtime.txt /academy/manual.txt /academy/hotfix.txt' | tee evidence/13-runtime-hashes.txt
Classify each changed path. The runtime and copied/hotfix files
exist in this container, but none of them changes
$PINNED.
6. Preserve non-secret evidence before shutdown
docker cp "$ORIGINAL":/academy/manual.txt evidence/manual-from-container.txt
docker cp "$ORIGINAL":/academy/hotfix.txt evidence/hotfix-from-container.txt
sha256sum manual.txt evidence/manual-from-container.txt | tee evidence/14-copy-proof.txt
docker exec "$ORIGINAL" sh -c 'echo PID1=$(cat /proc/1/stat | cut -d" " -f1); tr "\\0" " " < /proc/1/cmdline; echo; ps' | tee evidence/15-process.txt
The evidence packet contains only synthetic text. If a real incident file can contain credentials or regulated data, use your organization's secure evidence process instead of blindly copying it to a workstation.
7. Demonstrate signal and exit behavior
docker stop --timeout 5 "$ORIGINAL"
docker logs "$ORIGINAL" | tee evidence/16-original-logs.txt
docker inspect "$ORIGINAL" \
--format 'Status={{.State.Status}} Exit={{.State.ExitCode}} Started={{.State.StartedAt}} Finished={{.State.FinishedAt}}' | tee evidence/17-original-exit.txt
docker diff "$ORIGINAL" | tee evidence/18-stopped-diff.txt
Expected evidence includes the TERM-handler log. If the container required SIGKILL, preserve that result as a diagnostic failure instead of editing the evidence to match the lesson.
8. Encode the intended change as image state
Assume only the policy intent “every replacement needs
/academy/declared.txt” should survive. The
copied/hotfix files were diagnostic experiments and must not be
promoted.
printf 'declared-release-state\n' > declared.txt
cat > Dockerfile <<'EOF'
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
COPY declared.txt /academy/declared.txt
CMD ["sh", "-c", "trap 'echo replacement-received-TERM; exit 0' TERM; while :; do sleep 1; done"]
EOF
docker build \
--label devops-academy.lab=ch06-checkpoint \
--build-arg BASE_IMAGE="$PINNED" \
-t devops-academy-ch06-checkpoint:declared . | tee evidence/19-build.txt
docker image inspect devops-academy-ch06-checkpoint:declared --format 'ImageID={{.Id}} Created={{.Created}}' | tee evidence/20-new-image.txt
Record the local image ID. If you were publishing a release, later chapters would add registry digest, SBOM/provenance/signature, scan, and promotion evidence. This checkpoint intentionally remains local and free.
9. Remove the mutated object only after evidence capture, then create a replacement
docker rm "$ORIGINAL"
REPLACEMENT='devops-academy-ch06-checkpoint-replacement'
docker run -d \
--name "$REPLACEMENT" \
--label devops-academy.lab=ch06-checkpoint \
devops-academy-ch06-checkpoint:declared
docker inspect "$REPLACEMENT" --format 'ID={{.Id}} Image={{.Image}} Status={{.State.Status}} Pid={{.State.Pid}}' | tee evidence/21-replacement-inspect.txt
docker exec "$REPLACEMENT" sh -c '
printf "declared="; cat /academy/declared.txt;
for f in runtime.txt manual.txt hotfix.txt; do
if [ -e "/academy/$f" ]; then echo "$f=unexpected-present"; else echo "$f=absent"; fi;
done
' | tee evidence/22-replacement-files.txt
docker diff "$REPLACEMENT" | tee evidence/23-replacement-diff.txt
The replacement must have a different container ID. Its image identity is the newly built local image, the declared file is present, and the original runtime drift is absent. That is the observable difference between “fixed one container” and “changed the deployable artifact.”
10. Optional init evidence
If your platform supports Linux containers, run a short optional
comparison using --init and save process output as
evidence/24-init.txt. Mark it “not observed” if
unavailable rather than treating optional evidence as mandatory.
docker run --rm --init "$PINNED" sh -c 'ps; echo PID1:; tr "\\0" " " < /proc/1/cmdline; echo' \
| tee evidence/24-init.txt
11. Review the evidence packet
Your evidence directory should answer these questions independently:
- Which context/daemon/version ran the lab?
- Which immutable base digest was used?
- What was the original container ID and primary PID?
- Were any mounts present?
- Which paths changed and when?
- Which files were copied and what were their hashes?
- Which process was PID 1 and what signal evidence was observed?
- What was the original exit state?
- What new image identity was built?
- Did the replacement contain only declared intended state?
12. Guarded cleanup and rollback
docker ps -a --filter label=devops-academy.lab=ch06-checkpoint --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}'
docker stop --timeout 5 "$REPLACEMENT"
docker rm "$REPLACEMENT"
docker image rm devops-academy-ch06-checkpoint:declared
docker ps -a --filter label=devops-academy.lab=ch06-checkpoint
date -u +%Y-%m-%dT%H:%M:%SZ | tee evidence/25-finished-at.txt
Keep the evidence directory. Do not use
docker system prune, delete Docker's data root, or
remove unrelated images/volumes. If cleanup fails, inspect the exact
dependency instead of escalating destructively.
13. Operational review and Chapter 07 handoff
Chapter 06 added two operational invariants to the course. First,
runtime filesystem mutation is not equivalent to image state.
Second, process launch form determines PID 1 and signal behavior.
You can now prove both using docker diff, copy
evidence, process inspection, logs, exit state, and replacement
testing.
Chapter 07 moves the durable side of that model into
Dockerfile fundamentals: FROM,
RUN, COPY, ADD,
WORKDIR, ENV, ARG,
USER, CMD, and ENTRYPOINT.
The checkpoint's tiny Dockerfile is therefore a bridge, not a
substitute for the next chapter.
Knowledge check
What must disappear in the replacement container if the checkpoint worked correctly?
The manually copied and exec-created runtime files. They belonged to the original container's writable state, not the new image.
What state should survive because it was declared in the new image?
/academy/declared.txt, because the Dockerfile
explicitly copies it into the built image.
Why is the original container removed only after the signal/diff/copy evidence is saved?
Removal deletes the container object and writable layer, destroying post-failure runtime evidence that cannot be reconstructed later.
If the TERM-handler message is missing, should you still mark graceful shutdown as passed?
No. Preserve the observed logs/exit/timing and diagnose the signal path. Evidence outranks the expected exercise result.
What is the Chapter 07 bridge from this checkpoint?
Durable container filesystem and startup behavior must be declared through image-build instructions rather than reconstructed with runtime copy/exec commands.
Official references and version notes
- Docker storage overview — explains the ephemeral per-container writable layer and why persistent data belongs in explicit mounts rather than the container layer.
- Storage drivers and writable layers — copy-on-write concepts, writable-layer behavior, and the Engine 29 distinction between the containerd image store and classic storage-driver examples.
- docker container diff — reports added, changed, and deleted paths in the container filesystem relative to its initial image-backed state.
- docker container cp — current copy semantics, stopped-container support, destination ownership, archive mode, symlink handling, and path rules.
- docker container exec — starts a new command only while the container's primary PID 1 is running and does not make image changes durable.
-
docker container run
— current runtime options including
--init, stop signals/timeouts, mounts, TTY behavior, and security controls. - docker container stop — graceful signal delivery and timeout-to-SIGKILL escalation.
- docker container attach — documents special PID 1 signal behavior and terminal signal-proxy implications.
-
JSONArgsRecommended build check
— why exec-form
CMD/ENTRYPOINTavoids an unintended shell parent and improves signal handling. -
Docker build best practices
— entrypoint scripts should normally
execthe final application so it becomes PID 1 and receives signals directly. - Bind mounts — mounting over an existing container path obscures image/writable-layer content until the container is recreated without that mount.
-
Docker Engine 29 release notes
— Engine 29.8.1 is the current patch baseline at verification
time; recent 29.x releases also include multiple
docker cpsecurity and compatibility fixes.
Verified 2026-09-21: Docker Engine
29.8.1 is the current Engine 29 patch release.
Current Docker documentation states that each container gets a
unique writable layer above immutable image layers and that
deleting the container deletes that layer;
docker cp can copy to or from running or stopped
containers; docker exec starts an additional process
only while the primary process is running; and
--init uses Docker's Tini-backed init to perform
normal init duties such as reaping child processes. Because Engine
29.5.x–29.7.x contained important docker cp security
and compatibility fixes, learners should record their actual
Engine/CLI versions and current security status rather than
treating file-copy behavior as version-independent.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.