Checkpoint Lab — Running Containers: create, run, start, stop, restart, rm, exec, attach, inspect, and Lifecycle State
This checkpoint produces a lifecycle timeline for one digest-identified image and one lab-owned container. You will predict each state transition, capture inspect evidence before and after it, exercise exec and attach without losing the primary process, preserve exit evidence, restart the same object, and prove exact cleanup.
Learning objectives
- Build an evidence-backed timeline through created, running, exec, attached/detached, stopped, restarted, exited, and removed states.
-
Predict at least two state changes before issuing the command and
compare the prediction with
docker inspectevidence. - Capture exact image digest, container ID/name, PID, exit code, restart count, labels, timestamps, mounts, networks, logs, and events where applicable.
- Prove that restarting reuses the same container object while creating a new primary process instance.
- Perform guarded cleanup that removes only the checkpoint-owned container and preserves unrelated images, volumes, networks, and workloads.
1. Checkpoint mission and deliverable
Create an evidence-backed timeline for one lab-owned container through created → running → exec → attach/detach → stopped → restarted → exited → removed. Your deliverable is a directory of small text files that lets another engineer reconstruct what Docker object existed, which image content it used, what the primary process did, and what each lifecycle command changed.
2. Preflight and assumptions
set -eu
EVIDENCE='ch05-evidence'
NAME='devops-academy-ch05-checkpoint'
LABEL='devops-academy.lab=ch05-checkpoint'
SOURCE='busybox:1.37.0'
mkdir -p "$EVIDENCE"
docker context show | tee "$EVIDENCE/context.txt"
docker version | tee "$EVIDENCE/docker-version.txt"
docker info --format 'OSType={{.OSType}} Arch={{.Architecture}} Driver={{.Driver}}' | tee "$EVIDENCE/docker-info.txt"
docker pull "$SOURCE" | tee "$EVIDENCE/pull.txt"
PINNED=$(docker image inspect "$SOURCE" --format '{{index .RepoDigests 0}}')
printf '%s\n' "$PINNED" | tee "$EVIDENCE/image-digest.txt"
test -n "$PINNED"
Record whether the daemon is native Linux, Docker Desktop, or Windows-container based, because host PID and signal observations are platform-sensitive. The checkpoint does not require Compose, Buildx, BuildKit, containerd, or runc commands, but the evidence packet may record their versions if available from previous chapters.
3. Guard exact resource ownership
if docker ps -a --filter "name=^/${NAME}$" --format '{{.ID}}' | grep -q .; then
echo "STOP: $NAME already exists; do not delete it unless you created it for this checkpoint." >&2
exit 1
fi
docker ps -a --filter "label=$LABEL" --format 'ID={{.ID}} Name={{.Names}} Status={{.Status}}' | tee "$EVIDENCE/preexisting-labels.txt"
A clean lab should show no matching container. If something appears, choose another checkpoint label/name rather than assuming ownership.
4. Write predictions before creating anything
Create ch05-evidence/predictions.txt with at least
these statements in your own words:
-
After
docker create, the container object will exist but.State.Runningwill be false and there will be no active primary PID. -
After
docker start, the container ID will remain the same while a primary PID becomes active. -
After a bounded
docker execfinishes, the primary PID and container ID should remain stable. - After graceful stop, the object will remain inspectable with an exit code and finish timestamp.
- After restart/start, the same container ID will have a new primary-process instance.
-
After
docker rm, inspecting that container ID/name should fail because the Docker object was removed.
5. Transition 1 — create the object, do not start it
docker create -it \
--name "$NAME" \
--label "$LABEL" \
--stop-timeout 4 \
"$PINNED" \
sh -c 'trap "echo checkpoint-term; exit 0" TERM; echo checkpoint-start; while :; do sleep 1; done' \
| tee "$EVIDENCE/create-id.txt"
CID=$(docker inspect "$NAME" --format '{{.Id}}')
printf '%s\n' "$CID" | tee "$EVIDENCE/container-id.txt"
docker inspect "$NAME" --format \
'ID={{.Id}} Name={{.Name}} Image={{.Image}} Status={{.State.Status}} Running={{.State.Running}} Pid={{.State.Pid}} Exit={{.State.ExitCode}} RestartCount={{.RestartCount}} Started={{.State.StartedAt}} Finished={{.State.FinishedAt}} Command={{.Path}} {{json .Args}} Labels={{json .Config.Labels}} Mounts={{json .Mounts}} Networks={{json .NetworkSettings.Networks}}' \
| tee "$EVIDENCE/01-created.txt"
Verify the first prediction now. If the object is already running, stop: you did not execute the intended lifecycle path.
6. Transition 2 — start and capture primary-process evidence
docker start "$NAME" | tee "$EVIDENCE/start.txt"
sleep 1
PID1=$(docker inspect "$NAME" --format '{{.State.Pid}}')
printf '%s\n' "$PID1" | tee "$EVIDENCE/pid-first.txt"
docker inspect "$NAME" --format \
'ID={{.Id}} Status={{.State.Status}} Running={{.State.Running}} Pid={{.State.Pid}} Exit={{.State.ExitCode}} RestartCount={{.RestartCount}} Started={{.State.StartedAt}} Finished={{.State.FinishedAt}}' \
| tee "$EVIDENCE/02-running.txt"
docker logs --timestamps "$NAME" | tee "$EVIDENCE/logs-after-start.txt"
Confirm that CID did not change and that a process PID
now exists. If you are on Docker Desktop, treat that PID as
daemon/VM-side runtime evidence rather than a direct Windows/macOS
host process identity.
7. Transition 3 — run and finish a bounded exec process
docker exec "$NAME" sh -c 'echo checkpoint-exec; id; ps' \
| tee "$EVIDENCE/exec.txt"
docker inspect "$NAME" --format 'ID={{.Id}} Status={{.State.Status}} Pid={{.State.Pid}}' \
| tee "$EVIDENCE/03-after-exec.txt"
Explain in your review why the exec command ending did not end the container. The primary process remained the lifecycle owner.
8. Transition 4 — attach and detach without terminating PID 1
In a second terminal, run
docker attach devops-academy-ch05-checkpoint. Detach
with Ctrl-p, Ctrl-q. Do not press Ctrl-c. Back in
the evidence terminal:
docker inspect "$NAME" --format 'ID={{.Id}} Status={{.State.Status}} Pid={{.State.Pid}}' \
| tee "$EVIDENCE/04-after-attach.txt"
docker logs --timestamps "$NAME" | tee "$EVIDENCE/logs-after-attach.txt"
If you cannot perform an interactive attach safely, write
attach not observed — terminal limitation in the
evidence directory and explain the expected stream relationship. Do
not fabricate an observation.
9. Transition 5 — stop gracefully and preserve exit state
docker stop --timeout 4 "$NAME" | tee "$EVIDENCE/stop.txt"
docker inspect "$NAME" --format \
'ID={{.Id}} Status={{.State.Status}} Running={{.State.Running}} Pid={{.State.Pid}} Exit={{.State.ExitCode}} RestartCount={{.RestartCount}} Started={{.State.StartedAt}} Finished={{.State.FinishedAt}}' \
| tee "$EVIDENCE/05-stopped.txt"
docker logs --timestamps "$NAME" | tee "$EVIDENCE/logs-after-stop.txt"
Look for the checkpoint-term marker. Record the actual
exit code. Do not alter it to match prose expectations.
10. Transition 6 — restart/start the same object and compare PIDs
docker start "$NAME" | tee "$EVIDENCE/start-again.txt"
sleep 1
PID2=$(docker inspect "$NAME" --format '{{.State.Pid}}')
printf '%s\n' "$PID2" | tee "$EVIDENCE/pid-second.txt"
docker inspect "$NAME" --format \
'ID={{.Id}} Status={{.State.Status}} Pid={{.State.Pid}} RestartCount={{.RestartCount}} Started={{.State.StartedAt}}' \
| tee "$EVIDENCE/06-restarted.txt"
printf 'Container ID=%s\nFirst PID=%s\nSecond PID=%s\n' "$CID" "$PID1" "$PID2" \
| tee "$EVIDENCE/identity-comparison.txt"
This step deliberately uses docker start after a
stopped object so the semantics are obvious. You may repeat with
docker restart --timeout 4 only after preserving
evidence and explaining that restart combines stop/start on the same
object.
11. Transition 7 — create an intentional clean exit while retaining evidence
To place the same object into an exited state with a deterministic
command would require changing its configured command, which Docker
does not do through start. Instead, stop it gracefully
again and treat that stopped/exited state as the retained
post-process object:
docker stop --timeout 4 "$NAME" | tee "$EVIDENCE/final-stop.txt"
docker inspect "$NAME" --format \
'ID={{.Id}} Status={{.State.Status}} Running={{.State.Running}} Pid={{.State.Pid}} Exit={{.State.ExitCode}} Started={{.State.StartedAt}} Finished={{.State.FinishedAt}}' \
| tee "$EVIDENCE/07-final-exited.txt"
The important concept is that exited is a process state
while the container object is still present. Preserve this packet
before deletion.
12. Optional event timeline
If you recorded events in another terminal during the checkpoint, save only the bounded lab-relevant window. A practical method is:
docker events \
--since '30m' \
--until '0s' \
--filter "container=$CID" \
| tee "$EVIDENCE/events.txt"
Event retention/format can vary with daemon lifetime and timing. If the bounded query returns nothing, record that limitation; do not manufacture an event list.
13. Review the evidence before cleanup
Your packet should let you answer all of these:
- Which exact image digest was used?
- What was the full container ID and human-readable name?
- What state existed immediately after create, start, exec, attach, stop, and second start?
- Did the primary PID change when the process was recreated?
- What stop timeout and command were configured?
- What exit code/timestamps were retained?
- Were there any mounts or non-default network attachments?
- Which observations are platform-specific or not observed?
14. Transition 8 — exact removal and proof of cleanup
docker inspect "$NAME" --format 'ID={{.Id}} Name={{.Name}} Labels={{json .Config.Labels}} Status={{.State.Status}}' \
| tee "$EVIDENCE/pre-remove.txt"
docker rm "$NAME" | tee "$EVIDENCE/remove.txt"
if docker inspect "$NAME" >"$EVIDENCE/post-remove.txt" 2>&1; then
echo 'Unexpected: container still exists' >&2
exit 1
else
echo 'Expected: container object no longer resolves' | tee -a "$EVIDENCE/post-remove.txt"
fi
docker ps -a --filter "label=$LABEL" --format 'ID={{.ID}} Name={{.Names}} Status={{.Status}}' \
| tee "$EVIDENCE/post-cleanup-label-query.txt"
Do not remove the source image, networks, volumes, or other containers. The checkpoint owns only the exact object it created.
15. Operational review and Chapter 06 handoff
Chapter 05 added a precise operational rule to the Docker model: container object state and primary-process state must be observed separately. You can now distinguish create/start/run, exec/attach, graceful stop/hard kill, explicit restart/restart policy, and object removal without collapsing them into “Docker restarted the app.”
Chapter 06 deepens the process/filesystem side of that model:
writable layers, docker cp, exec workflows, process
trees, signals, and PID 1. Carry forward the evidence discipline
from this checkpoint—especially the difference between a Docker
object, PID 1, an exec process, and runtime filesystem mutation.
Knowledge check
Which identity should remain stable across the stop/start sequence: full container ID or primary PID?
The container ID remains stable because the Docker object is retained. The primary process is recreated and can receive a different PID.
Why does the checkpoint save
07-final-exited.txt before
docker rm?
Removal deletes the Docker object and its inspectable post-process state. The evidence packet preserves exit code/timestamps/configuration before deletion.
If the attach step cannot be observed, what is the correct evidence response?
Record that it was not observed and the reason. Do not fabricate terminal behavior or treat logs as proof that attach occurred.
What would docker rm -f add to this checkpoint's
normal cleanup?
Nothing useful. The object is deliberately stopped first. Force removal would only introduce unnecessary hard-kill semantics if it were still running.
Does a successfully restarted container prove the external application is healthy?
No. It proves the container process started. Health, readiness, network reachability, data durability, and external dependency state are separate evidence layers.
Official references and version notes
- docker container command group — current container-management surface, including create, run, start, stop, restart, kill, rm, exec, attach, inspect, pause, and wait.
- docker container create — creates a container object without starting its primary process and records runtime configuration such as restart policy and stop timeout.
- docker container run — create-and-start convenience behavior, foreground/detached operation, automatic removal, signal proxying, and stop configuration.
- docker container start — starts an existing stopped container and optionally attaches standard streams.
- docker container stop — graceful stop signal, timeout, and eventual SIGKILL escalation semantics.
- docker container kill — immediate/default SIGKILL behavior and explicit signal selection.
- docker container restart — stop-then-start semantics, configurable signal, and timeout behavior.
-
docker container rm
— exact container removal; force-removing a running container uses
SIGKILL and
-vaffects anonymous volumes. - docker container exec — starts an additional command only while the container's primary PID 1 is running; exec commands are not automatically restarted with the container.
- docker container attach — attaches local standard streams to the existing ENTRYPOINT/CMD process, signal-proxy implications, detach keys, and throughput caveats.
- docker container inspect — low-level container configuration and state evidence, including PID, exit code, restart count, timestamps, mounts, and networks.
- Start containers automatically — restart-policy behavior, successful-start monitoring, manual-stop interaction, and distinction from live restore.
-
Docker Engine 29 release notes
— current Engine 29 baseline; Engine 29.7 added the daemon-level
default-stop-timeoutoption and 29.8.1 is the current patch release at chapter verification time.
Verified 2026-09-21: Docker Engine
29.8.1 is the current Engine 29 patch release.
Docker's current CLI documentation defines
docker exec as a command that exists only while the
container's primary process is running;
docker stop sends the configured stop signal and
escalates to SIGKILL after the timeout;
docker kill defaults to SIGKILL; and
docker rm --force kills a running container before
removing the object. Linux and Windows defaults and
process-isolation behavior differ, so every executable lab records
the actual client/server, OS type, architecture, stop
configuration, image digest, and container state observed on the
learner's environment.
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.