Chapter 26Lesson 05260–360 min

Checkpoint Lab — Containers, Reproducible Runtimes, and Distributed Test Environments

Containerize a deterministic Robot Framework suite, prove host/container semantic equivalence, inject and diagnose a result-mount path failure, preserve evidence, and document the filesystem/network ownership contract.

Checkpoint labDeterministic suiteMount failureEvidence packetCleanup

Checkpoint outcomes

  • Build a deterministic non-root Robot image and record image/base/dependency provenance.
  • Predict and verify source, process-user, result-volume and output-state changes before execution.
  • Run the same suite on host and in the container and prove semantic equivalence.
  • Inject a wrong result-mount destination, preserve the stopped container, recover its evidence, and repair the runtime contract.
  • Produce an operator-ready evidence ledger describing filesystem, network, user and cleanup boundaries.

Current compatibility baseline — verified 2026-08-31. Robot Framework 7.4.2 is the stable course baseline and requires Python 3.8+. The mandatory container lab uses the Docker Official Image python:3.12.14-slim-bookworm and installs the Robot Framework 7.4.2 universal wheel by exact version and SHA-256 hash. Docker image tags are mutable, so the lesson records the resolved image digest and explains full digest pinning for controlled pipelines. Pabot 5.2.2 is discussed as the current stable optional parallel executor; 5.3.0b1 is prerelease and is not required. No browser library, external API/database/SSH service, CI provider, Kubernetes cluster, paid platform, real credential, or production target is required.

1. Checkpoint scenario and safety boundary

You will reuse the rf26-container-lab project from Lesson 2. Everything stays local: one Python/Robot image, one deterministic Robot suite, Docker-managed named volumes and host evidence directories. No public ports, production systems, credentials, browsers, databases or external services are involved.

Checkpoint state transitions
flowchart TD
    A[Host source] --> B[rf26 image]
    B --> C[Host baseline PASS]
    B --> D[Container PASS]
    D --> E[Correct result volume]
    B --> F[Injected wrong mount target]
    F --> G[Robot PASS but volume empty]
    G --> H[Inspect + recover stopped-container results]
    H --> I[Fix destination + rerun]
    I --> J[Evidence packet + cleanup]

The injected failure is deliberately about evidence persistence, not the Robot assertions. This demonstrates an important production lesson: application/test success and operational evidence success are separate contracts.

2. Exact preflight and version manifest

python --version
python -m robot --version
docker version
docker info

docker image inspect python:3.12.14-slim-bookworm --format '{{json .RepoDigests}}'   > evidence/checkpoint-base-digest.json

docker image inspect rf26-robot:7.4.2 --format '{{.Id}} {{.Config.User}}'   > evidence/checkpoint-image.txt

Expected course assumptions: Robot 7.4.2, Python 3.12.14 inside the image, non-root image user 10001:10001. If your local Docker Engine/Desktop differs, record it; do not pretend the engine itself is part of the image.

3. Predict before mutation

Prediction Before action Expected after action Independent verification
Image build does not create host Robot result files results directories empty image exists; result dirs still empty docker image inspect + host directory listing
Correct named volume captures Robot outputs new volume empty output.xml/log/report/proof exist in mounted destination inspect mounts + copy/list results
Wrong mount target does not fail test assertions wrong volume empty Robot exits 0 but writes to image-owned /workspace/results container exit + mount inspection + internal file listing
Removing container before recovery would destroy internal-only evidence stopped broken container exists evidence recoverable until rm docker cp before docker rm

Write your own expected image ID/user, mount destination and artifact list into evidence/predictions.md before running the mutation steps.

4. Establish the host baseline and retain its exit code

rm -rf results-host/* results-container/* 2>/dev/null || true

python -m robot   --variable RUNTIME_LABEL:host-checkpoint   --outputdir results-host   tests/runtime_contract.robot
printf 'host_exit=%s
' "$?" > evidence/checkpoint-host-exit.txt

PowerShell users should preserve $LASTEXITCODE instead. Verify four host artifacts: output.xml, log.html, report.html, and proof.txt.

5. Correct container run: explicit named volume, retained container, exported evidence

docker volume create rf26-checkpoint-results

docker create --name rf26-checkpoint   --mount type=volume,src=rf26-checkpoint-results,dst=/workspace/results   rf26-robot:7.4.2   --variable RUNTIME_LABEL:container-checkpoint   --outputdir /workspace/results   /workspace/tests/runtime_contract.robot

docker start --attach rf26-checkpoint
docker inspect rf26-checkpoint --format '{{.State.ExitCode}}'   > evidence/checkpoint-container-exit.txt
docker inspect rf26-checkpoint --format '{{json .Mounts}}'   > evidence/checkpoint-container-mounts.json
docker cp rf26-checkpoint:/workspace/results/. results-container/

Verify the mount destination is exactly /workspace/results. The container's Robot exit code should be 0 and the exported host directory should contain the same four artifact types as the host baseline.

6. Prove semantic equivalence

python tools/compare_results.py
sha256sum results-host/proof.txt results-container/proof.txt 2>/dev/null || true

The XML comparison script proves matching test names/statuses; the proof files demonstrate matching deterministic domain evidence. The HTML/XML result files themselves need not be byte-identical because runtime metadata differs.

7. Inject the mount-path failure without deleting the failed runtime

docker volume create rf26-broken-results

docker create --name rf26-broken   --mount type=volume,src=rf26-broken-results,dst=/workspace/wrong-results   rf26-robot:7.4.2   --variable RUNTIME_LABEL:broken-mount   --outputdir /workspace/results   /workspace/tests/runtime_contract.robot

docker start --attach rf26-broken

docker inspect rf26-broken --format '{{.State.ExitCode}}'   > evidence/broken-exit.txt
docker inspect rf26-broken --format '{{json .Mounts}}'   > evidence/broken-mounts.json

Expected surprise: Robot can still PASS because /workspace/results exists and is writable inside the image, but the named volume is attached to /workspace/wrong-results. If the container were started with --rm, the test evidence at the real output directory would disappear on removal.

8. Diagnose independently: prove the volume is empty and the stopped container still owns the missing evidence

# List the wrongly mounted volume using the same image as a harmless inspector.
docker run --rm --entrypoint python   --mount type=volume,src=rf26-broken-results,dst=/check   rf26-robot:7.4.2   -c 'import os; print(os.listdir("/check"))'   > evidence/broken-volume-list.txt

# Recover the actual Robot result directory before deleting the stopped container.
mkdir -p recovered-broken
docker cp rf26-broken:/workspace/results/. recovered-broken/
find recovered-broken -maxdepth 1 -type f -printf "%f\n"   > evidence/broken-internal-results.txt

docker cp works from a stopped container, so the exact first-run files can be recovered without restarting the process. The authoritative diagnosis is the mount destination from docker inspect plus the recovered files that actually existed at /workspace/results.

This is why the diagnostic sequence preserves the stopped container: you can recover first-failure evidence without rerunning and changing timestamps/state.

9. Apply the least destructive correction and rerun only the broken contract

docker rm rf26-broken
# Keep rf26-broken-results until after diagnosis; it proves the wrong mount was empty.

docker create --name rf26-repaired   --mount type=volume,src=rf26-broken-results,dst=/workspace/results   rf26-robot:7.4.2   --variable RUNTIME_LABEL:repaired-mount   --outputdir /workspace/results   /workspace/tests/runtime_contract.robot

docker start --attach rf26-repaired
docker inspect rf26-repaired --format '{{json .Mounts}}'   > evidence/repaired-mounts.json
mkdir -p repaired-results
docker cp rf26-repaired:/workspace/results/. repaired-results/

The only material change is the mount destination. No test code, timeout, retry, privilege or assertion was changed because none of those layers caused the defect.

10. Build the evidence ledger/operator runbook

RF26 checkpoint evidence ledger
-------------------------------
Source revision / git status:
Base image tag + resolved digest:
Built image ID:
Container configured user:
Robot version / Python version:
Host command + exit code:
Correct container command + exit code:
Correct volume destination:
Wrong volume destination:
Recovered internal result path:
Repaired volume destination:
Host/container semantic comparison:
Network exposure: none (no published ports)
Secrets used: none
CPU/memory limits: record if configured
Cleanup owner/date:
Artifacts retained:
  - host output.xml/log.html/report.html/proof.txt
  - container output.xml/log.html/report.html/proof.txt
  - base/image provenance
  - correct/broken/repaired mount inspections
  - broken-volume listing
  - recovered first-failure evidence

This ledger is deliberately operational. A reviewer can see what changed, which layer failed, how evidence was preserved, and why the repair was limited to a mount destination.

11. Verification checklist

  • The image is built from python:3.12.14-slim-bookworm; its resolved base digest is recorded.
  • Robot 7.4.2 is hash-pinned in the image.
  • The image runs as numeric non-root user 10001:10001.
  • Host and correct-container runs have the same Robot test names and PASS statuses.
  • Both retained result sets contain proof.txt with rf26-container-contract.
  • The intentionally wrong volume destination is visible in broken-mounts.json.
  • The wrong volume does not contain the Robot result files.
  • First-failure results were recovered from the stopped broken container before deletion.
  • The repaired run changes the mount destination, not the Robot assertions/timeouts.
  • No ports, production endpoints, credentials, privileged mode or Docker socket mounts were used.

12. Cleanup/rollback after evidence is accepted

docker rm -f rf26-checkpoint rf26-repaired 2>/dev/null || true
docker volume rm rf26-checkpoint-results rf26-broken-results 2>/dev/null || true

# Optional after review:
docker image rm rf26-robot:7.4.2

# Do NOT run docker system prune as part of this lab.
# Retain evidence/, results-host/, results-container/, recovered-broken/, repaired-results/
# until your review/retention policy says they can be removed.

Cleanup is narrowly scoped to named resources created by the lab. A global prune could destroy unrelated developer or CI evidence and is intentionally excluded.

13. What Chapter 26 adds to a production Robot operating model

You can now treat the container runtime as a governed execution boundary rather than a black box. The production model records image/dependency provenance, non-root identity, runtime mounts and networks, capacity, Robot exit status and durable evidence. It also distinguishes Pabot worker isolation from container/CI isolation and avoids assuming that a fresh container resets external state.

Chapter 27 moves from runtime reproducibility to source maintainability: the Robot Framework Style Guide, current Robocop linting/formatting, documentation and naming conventions become enforceable review/CI contracts.

Knowledge check

Why is the injected failure valuable even though the Robot tests still PASS?

Why keep the broken container stopped instead of immediately rerunning with the correct mount?

Which state changed in the repair: Robot model, image, or container runtime?

If this lab later used Pabot inside four CI jobs, what additional evidence would be required?

Why is “no published ports” part of the evidence ledger?

14. Checkpoint complete

The checkpoint demonstrates deterministic host/container behavior, explicit image and package provenance, non-root runtime identity, durable result handling, a recoverable mount-path failure and narrowly scoped cleanup. You have a repeatable pattern for containerizing Robot Framework without confusing containerization with isolation or losing the evidence needed to debug it.

Next lesson

Style Guides, Robocop, Documentation, Naming, and Maintainability: Core Concepts and Mental Model

Continue with Style Guides, Robocop, Documentation, Naming, and Maintainability: Core Concepts and Mental Model. It builds directly on the state, evidence, and operating assumptions established here, so carry those constraints forward rather than treating the next page as an isolated topic.

References and version anchors

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.