A catalogue of verifiable labs for the Containers course on www.stephane-robert.info, driven by the dsoxlab CLI.
A set of exercises that measure what you can do. Each lab lays down a starting state, gives you a situation, and checks the result by querying the Docker engine: the image built, its size, the user running it, the container's mounts, what the application answers.
You do not need to know how to program: every lab provides the code it needs. You work on the image, the runtime, the network, storage, distribution and hardening.
Each module ends with a test: five checks of equal weight, a pass mark of 75 out of 100, four hints that cost points, and one critical requirement that no total makes up for. A test grades the system's final state, never the commands typed: two paths reaching the same state get the same mark.
It is not a course. The courses live on the blog, and this repository does not rewrite them:
www.stephane-robert.info explains, teaches
this repository makes you practise, measures, proves
Every lab carries a doc_url to the lesson it proves, anchored on the exact
section. If a lab resists you, the lesson is one click away.
It is not a Kubernetes course. Kubernetes has its own catalogue,
kubernetes-dsoxlab-training,
with CKA, CKAD and CKS labs on a real kubeadm cluster. Nothing here mentions
kubectl, Helm or manifests.
It is not a full DevSecOps course. Trivy, Cosign, GitHub Actions and GitLab CI appear when a container skill calls for them, and no further: they have their own guides.
| What you need | Why |
|---|---|
| The provisioned Docker VM | every lab queries its engine; no workstation Docker is required |
uv |
it installs dsoxlab and the catalogue's tooling |
mise, optional |
it provides hadolint, dive and trivy at pinned versions |
infra/bootstrap.yaml installs Docker and its tooling in the provisioned VMs.
The first lab checks that baseline. Labs needing simultaneous hosts declare
roles on their runtime target; the catalogue table indicates the required
number of VMs.
# 1. dsoxlab, the tool that runs the labs
uv tool install dsoxlab
# 2. this catalogue
git clone https://github.com/stephrobert/containers-dsoxlab-training.git
cd containers-dsoxlab-training
# 3. the pinned tooling, optional but recommended
mise installEvery lab runs inside a throwaway virtual machine, which
dsoxlab provision brings up for you and dsoxlab destroy removes. You
install nothing on your own machine, Docker included: provisioning installs
the engine in Debian 13. Allow a few minutes
of provisioning before the first lab.
export LAB_HOME=$PWD
dsoxlab list-labs # what the catalogue holds
dsoxlab provision # prepare the VM baseline
dsoxlab run docker-verifier-son-moteur # lay down the starting point
dsoxlab challenge docker-verifier-son-moteur # the mission
# ... you work in challenge/work ...
dsoxlab check docker-verifier-son-moteur # the scoreuv tool upgrade dsoxlab # the engine, published on PyPI
git -C <this repo> pull # the catalogueThe engine and the catalogue move independently: a new dsoxlab never requires
re-cloning the labs, and new labs never require upgrading the tool, as long as
the contract version matches.
The catalogue is described by data, not code, which is what keeps the dsoxlab
engine domain-agnostic:
meta.ymlat the root declares the repository identity and the order of the eight sections shown bylist-labs, which carry the ids of the course modules, plus the Swarm path declared outside the course;lab.yamlper lab declares itsskills,level,runtime(type, fixtures, services),distros,doc_urland avalidationblock. Alab.fr.yamloverridestitleanddescriptionin French, and nothing else.
dsoxlab validate-structure checks the whole contract, and the sections
declared in meta.yml with no lab yet are exactly how this catalogue states
what it still owes.
| Command | What it does |
|---|---|
dsoxlab list-labs |
the catalogue, in teaching order |
dsoxlab run <id> |
copies the starting point into challenge/work |
dsoxlab challenge <id> |
the mission: what to reach, not how |
dsoxlab check <id> |
runs the tests and returns a score out of 100 |
dsoxlab hint <id> |
one hint, at a cost deducted from the score |
dsoxlab submit <id> |
records the result for good |
dsoxlab progress |
where you stand, lab by lab |
dsoxlab clean <id> |
removes the working directory |
| Runtime | What the lab asks for |
|---|---|
vm |
a throwaway Debian 13, brought up by dsoxlab provision and torn down by dsoxlab destroy. The lab installs Docker there; the tests inspect the images, containers and networks that this engine actually holds. |
You do not install Docker on your own machine. That is a decision of this
catalogue, taken on 2026-10-28: a virtual machine is thrown away, whereas a
Docker daemon left on your working machine stays, with its docker group, its
service and its images. The labs therefore cost you a dsoxlab provision of a
few minutes, and nothing else.
In exchange they can teach what a container on your own machine could not: the daemon configuration, rootless mode, seccomp, AppArmor, the firewall, a registry with its certificate. And a lab that fills the disk on purpose fills its own.
The tests read state, never your commands.
You reach the result by whatever path you like. A test in this catalogue looks like this:
assert inspecter_image(tag)["Config"]["ExposedPorts"] # what the image declares
code, body = conteneur.repondre("/health") # what the application returns
assert conteneur.utilisateur() != "0" # who actually runs itAnd never like this:
assert "docker build" in history # which proves nothingHints cost points, and that is deliberate.
Four hints per lab, from the vaguest to the most explicit. Each one costs. The last names the tool that shows the fault when everything looks right without being right, but none gives the Dockerfile away.
| Type | What you get |
|---|---|
lab |
an explanation and a focused mission |
challenge |
an objective and constraints, no recipe |
capstone |
a real situation, and the problem is not announced |
You progressively lose the guidance. That is the point of the path.
| Block | What it teaches | What it requires |
|---|---|---|
| Docker | images, runtime, data, network, security, internals | a throwaway VM |
| Compose | a multi-service application, described and diagnosed | the same VM |
| Swarm | a distributed orchestrator: Raft, quorum, overlay, secrets | several VMs |
Swarm is an advanced, clearly separate path. It is not a stepping stone to Kubernetes in this repository: it is an orchestrator in its own right, with its consensus and its control state to protect.
| Lab (id) | Title | Level | Certif | Runtime | Companion guide |
|---|---|---|---|---|---|
docker-verifier-son-moteur |
Is the engine actually working? Four answers, not one | c1 | - | 1 VM | guide |
docker-premier-conteneur |
Run, stop, remove: three verbs that do not overlap | c1 | - | 1 VM | guide |
docker-cycle-de-vie |
The life cycle: restarting is not recreating | c1 | - | 1 VM | guide |
container-entrypoint-et-cmd |
ENTRYPOINT and CMD: which one do your arguments replace? | c1 | - | 1 VM | guide |
container-signaux-et-pid-1 |
Who is PID 1 decides whether your container stops or gets killed | c1 | - | 1 VM | guide |
docker-inspecter-un-conteneur |
Interrogate a running container | c1 | - | 1 VM | guide |
epreuve-mettre-un-service-en-marche |
Nothing runs on this host | c1 | - | 1 VM | guide |
docker-anatomie-dune-image |
The anatomy of an image: layers, sharing, configuration | c1 | - | 1 VM | guide |
reseau-bridge-par-defaut |
The default network, and what it does not do | c1 | - | 1 VM | guide |
reseau-bridge-dedie |
A network of your own: naming and isolating | c1 | - | 1 VM | guide |
reseau-publication-de-ports |
Publishing a port, and only where it belongs | c1 | - | 1 VM | guide |
reseau-interne |
Two isolations that are not alike | c1 | - | 1 VM | guide |
epreuve-relier-deux-conteneurs |
Exam: expose an application without exposing its database | c1 | - | 1 VM | guide |
donnees-volumes-et-montages |
Where data survives: the three kinds of mount | c1 | - | 1 VM | guide |
donnees-permissions-uid-gid |
Two containers, one volume, rights that hold | c1 | - | 1 VM | guide |
container-arret-transactionnel |
Finish an HTTP transaction before stopping | c1 | - | 1 VM | guide |
epreuve-remettre-trois-conteneurs-en-service |
Three containers down, three different causes | c1 | - | 1 VM | guide |
remettre-un-conteneur-en-service |
The shop cannot find its catalogue | c1 | - | 1 VM | guide |
| Lab (id) | Title | Level | Certif | Runtime | Companion guide |
|---|---|---|---|---|---|
internes-namespaces |
A namespace is a view, not a wall | c1 | - | 1 VM | guide |
internes-cgroups-v2 |
Cgroups: what a container may consume | c1 | - | 1 VM | guide |
internes-stockage-des-images |
Where Docker stores a container's files | c1 | - | 1 VM | guide |
internes-chaine-dexecution |
Who really starts a container | c1 | - | 1 VM | guide |
container-limite-cpu |
Capping a processor, and proving it | c1 | - | 1 VM | guide |
container-ulimits-et-shm |
Shared memory and descriptors | c1 | - | 1 VM | guide |
troubleshoot-exit-127 |
Three failures, three exit codes to read | c1 | - | 1 VM | guide |
troubleshoot-conflit-de-port |
Two port conflicts, two causes | c1 | - | 1 VM | guide |
troubleshoot-permission-refusee |
Three refused accesses, three causes | c1 | - | 1 VM | guide |
troubleshoot-boucle-de-redemarrage |
A restart loop, and two outcomes | c1 | - | 1 VM | guide |
epreuve-contenir-un-conteneur-gourmand |
The batch job that takes the host down with it | c4 | - | 1 VM | guide |
| Lab (id) | Title | Level | Certif | Runtime | Companion guide |
|---|---|---|---|---|---|
images-identifier-image |
Image identity: the tag lies, the digest does not | c2 | - | 1 VM | guide |
images-ordre-des-couches |
The same Dockerfile, and a cache that finally serves | c2 | - | 1 VM | guide |
images-contexte-de-build |
A file that exists, and cannot be found | c2 | - | 1 VM | guide |
images-optimiser-la-taille |
What a layer keeps | c2 | - | 1 VM | guide |
images-migrer-vers-alpine |
Changing base is not changing a line | c2 | - | 1 VM | guide |
images-build-multi-etapes |
What comes from the base cannot be removed | c2 | - | 1 VM | guide |
images-non-root |
USER changes the identity, and no right | c2 | - | 1 VM | guide |
images-healthcheck |
'Up' says nothing about the service | c2 | - | 1 VM | guide |
buildkit-cache-monte |
Reuse dependencies when the build step runs again | c2 | - | 1 VM | guide |
buildkit-secret-de-build |
Fetch a private dependency without shipping the build secret | c2 | - | 1 VM | guide |
images-multi-architecture |
Publish two architectures and execute both | c2 | - | 1 VM | guide |
epreuve-livrer-une-premiere-image |
Deliver a first image worth publishing | c2 | - | 1 VM | guide |
| Lab (id) | Title | Level | Certif | Runtime | Companion guide |
|---|---|---|---|---|---|
registry-tag-push-pull |
Publish an image that another client can really pull | c2 | - | 1 VM | guide |
registry-tag-contre-digest |
Recover the original release after its tag has moved | c2 | - | 1 VM | guide |
images-epingler-par-digest |
Keep a rebuild on its approved base when a tag moves | c2 | - | 1 VM | guide |
registry-locale-tls |
Serve a trusted registry and pull without disabling TLS checks | c2 | - | 1 VM | guide |
registry-authentification |
Protect a TLS registry with authentication | c2 | - | 1 VM | guide |
registry-droits-minimaux |
Grant minimal registry scopes to a publisher and a separate reader | c2 | - | 2 VMs | guide |
registry-air-gap |
Deliver a complete image without its source registry | c2 | - | 1 VM | guide |
epreuve-livrer-une-image |
Deliver an image a bare client can actually run | c2 | - | 2 VMs | guide |
epreuve-reconstruire-une-image-pour-deux-architectures |
One image, four demands, two architectures | c4 | - | 1 VM | guide |
| Lab (id) | Title | Level | Certif | Runtime | Companion guide |
|---|---|---|---|---|---|
compose-premiere-stack |
Start and recreate a two-service Compose application | c3 | - | 1 VM | guide |
compose-resolution-des-variables |
Resolve Compose variables without mixing environments | c4 | - | 1 VM | guide |
compose-isolation-reseau |
Separate frontend and data networks with Compose | c4 | - | 1 VM | guide |
compose-persistance |
Keep application data across Compose recreation | c4 | - | 1 VM | guide |
compose-tache-de-migration |
Gate application startup on a successful migration | c3 | - | 1 VM | guide |
compose-secrets |
Grant a Compose secret only to its consumer | c4 | - | 1 VM | guide |
compose-sante-et-dependances |
Wait for readiness, not just a running container | c3 | - | 1 VM | guide |
compose-ressources |
Enforce Compose resource budgets on the engine | c3 | - | 1 VM | guide |
compose-plusieurs-environnements |
Deploy isolated development and production Compose projects | c3 | - | 2 VMs | guide |
compose-profils-et-variantes |
Two variants from one base, and no dev mount left in deployment | c3 | - | 1 VM | guide |
epreuve-fiabiliser-une-stack |
The stack that only works on the second try | c3 | - | 1 VM | guide |
| Lab (id) | Title | Level | Certif | Runtime | Companion guide |
|---|---|---|---|---|---|
compose-retour-arriere |
Roll back a release, and refuse the one that would lose data | c3 | - | 1 VM | guide |
compose-correlation-et-saturation |
Correlate business requests and attribute CPU saturation | c3 | - | 1 VM | guide |
donnees-postgres-sauvegarde |
Restore PostgreSQL data on a second VM | c3 | - | 2 VMs | guide |
epreuve-remettre-une-stack-en-service |
Bring a Compose stack back into service | c3 | - | 1 VM | guide |
epreuve-restaurer-une-stack |
Restore a stack on another host, data included | c3 | - | 2 VMs | guide |
| Lab (id) | Title | Level | Certif | Runtime | Companion guide |
|---|---|---|---|---|---|
docker-configurer-le-demon |
Configure persistent Docker log defaults | c4 | - | 1 VM | guide |
docker-contexte-ssh |
Create an SSH Docker context | c4 | - | 2 VMs | guide |
docker-rotation-des-journaux |
Rotate one container log | c4 | - | 1 VM | guide |
securite-groupe-docker |
Remove an unnecessary Docker grant | c4 | - | 1 VM | guide |
securite-socket-lecture-seule |
Remove direct Docker socket access | c4 | - | 1 VM | guide |
securite-socket-proxy |
Restrict a Docker API socket proxy | c4 | - | 1 VM | guide |
securite-rootless |
Start the prepared rootless Docker engine | c4 | - | 1 VM | guide |
securite-capabilities |
Drop all but one Linux capability | c4 | - | 1 VM | guide |
securite-no-new-privileges |
Prevent privilege gain at runtime | c4 | - | 1 VM | guide |
securite-seccomp |
Measure a targeted seccomp syscall denial | c4 | - | 1 VM | guide |
securite-apparmor |
Confine the service with AppArmor | c4 | - | 1 VM | guide |
container-systeme-en-lecture-seule |
Use a read-only container root filesystem | c4 | - | 1 VM | guide |
reseau-pare-feu-cible |
Repair one routed firewall path without dropping protections | c4 | - | 1 VM | guide |
container-limite-de-processus |
Bound the container process count | c4 | - | 1 VM | guide |
secrets-au-runtime |
Keep a runtime token out of Env | c4 | - | 1 VM | guide |
container-memoire-et-oom |
Set a hard memory ceiling | c4 | - | 1 VM | guide |
troubleshoot-disque-plein |
Recover space in a bounded ENOSPC sandbox | c4 | - | 1 VM | guide |
donnees-volume-orphelin |
Recover retained orphan data and remove only disposable data | c4 | - | 1 VM | guide |
epreuve-reprendre-un-hote-docker |
Take over a Docker host nobody has maintained | c4 | - | 1 VM | guide |
| Lab (id) | Title | Level | Certif | Runtime | Companion guide |
|---|---|---|---|---|---|
compose-dans-une-ci |
Run Compose as a reproducible local CI job | c4 | - | 1 VM | guide |
image-scan-de-vulnerabilites |
Gate an image using dated, real offline vulnerability scans | c4 | - | 1 VM | guide |
images-signature-identite |
Verify a container archive signature and its approved signer identity | c3 | - | 1 VM | guide |
capstone-chaine-complete |
Deliver and recover an isolated application end to end | c4 | - | 2 VMs | guide |
epreuve-durcir-sans-casser |
Harden a service without breaking it | c4 | - | 1 VM | guide |
90 labs, table generated by scripts/gen_catalog.py.
It is under construction. ROADMAP.md holds the full backlog, organised by
skill and in waves, COVERAGE.md the matrix of the blog's guides, and
AUDIT.md what the rebuild kept from the former repository and what it threw
away.
validation-labs.json holds each lab's verdict: a lab appears there only once
it has been played in both directions, scoring zero before the work and full
marks after.
- Contributing: see CONTRIBUTING.
- Conduct: Code of Conduct · Security: SECURITY.
- What changed, and when: CHANGELOG.
- What changed before this catalogue: see below.
This catalogue supersedes containers-training, which stays in place: its five hands-on guides remain readable there, and their substance is carried over here as measured labs.
See LICENSE (CC BY 4.0).