Containers: Docker, Compose and Swarm

Every exercise runs your files and commands on a simulated host, then tests it like production: reboots, upgrades, traffic and attacks.

Start here

Your first container

The marketing team runs the spring sale page in a container on the shop's server, but nobody can open it. Make it answer on port 8080 of the server.

  • Beginner
  • Fix
  • About 10 min
Start the exercise
90%

Beginner free

Run, configure and debug containers, keep data through restarts, and have a service come back on its own after a crash or a reboot.

0 of 9 passed

  1. Your first container The marketing team runs the spring sale page in a container on the shop's server, but nobody can open it. Make it answer on port 8080 of the server. ContainerPort publishingContainer lifecycle Fix About 10 min
  2. Image or container? The homepage said "Welcom". A colleague fixed it with docker exec, right inside the running container, and the page looks fine. At 3:00 tonight the server's maintenance recreates every container from its image. Make the fix survive. Image vs containerWritable layerImage tags Fix About 15 min
  3. Your first Dockerfile The orders API has to ship in a container. A first Dockerfile builds fine, but the container stops a second after it starts. Make the API answer on port 3000 of the server. DockerfileCMD and ENTRYPOINTMain process (PID 1) Build About 15 min
  4. Configure with the environment The payments API refuses to start: it crashes with exit code 1. It needs the payment provider's key, and someone suggested writing it in the Dockerfile. Start the API without baking the secret into the image. Environment variablesKeeping secrets out of imagesContainer logs Fix About 15 min
  5. Read the logs, step inside Since this morning's release, the orders service restarts in a loop. Find out why with its logs, look inside the image, and get it to run for an hour without a single restart. Container logsdocker execRestart policies Fix About 15 min
  6. Does the data survive? The orders database runs in a container. At 2:00 tonight, operations upgrade PostgreSQL by replacing that container with one from postgres:16.4. Make sure not a single order is lost. VolumesWritable layerImage vs container Fix About 20 min
  7. Restart on its own Last night the server rebooted for a kernel update at 3:00 and the shop stayed down until someone arrived at 9:00. Make the shop come back by itself, whatever stops it. Restart policiesContainer lifecycleExit codes Fix About 15 min
  8. Two containers that talk The visit counter API keeps its counts in Redis. Both run in containers, but the API can't find "redis" and keeps crashing. Someone suggested publishing Redis on the server... don't. Make the API reach Redis by its name, with Redis invisible from the internet. User-defined networksDefault bridge networkPort publishing Fix About 15 min
  9. Make room The CI server's disk is almost full and builds fail with "no space left on device". Free space without touching the volume ci-history, which keeps two years of build history. Docker disk usageDangling imagesVolumes Fix About 15 min
99%

Intermediate

Build small, fast and safe images, and run a Compose stack that starts in order, stays within its limits and keeps development apart from production.

Unlock with the Intermediate plan
  1. The build cache Every one-line change to the orders API costs four minutes of npm ci in CI. Reorder the Dockerfile so that a code change rebuilds in under 20 seconds. Build cacheImage layersDockerfile Fix Preview
  2. A 1 GB image The catalog service is a small Go program, yet its image weighs 1.1 GB: the whole Go toolchain ships to production. Bring the image under 100 MB with a multi-stage build. Multi-stage buildsBase imagesStatic binaries Fix Preview
  3. The build context Building the accounts service sends 420 MB to Docker every time, the container crashes on the server with "invalid ELF header", and a security scan just found the production .env file inside the image. Fix the build context. Build context.dockerignoreKeeping secrets out of images Fix Preview
  4. Stop running as root A security audit flags the media service: its process runs as root, so a flaw in the app would hand an attacker root inside the container. Run it as an unprivileged user... and keep photo uploads working. Running as non-rootFile permissions in containersVolumes Fix Preview
  5. Started doesn't mean ready On almost every deployment the orders API crashes once: it connects before PostgreSQL accepts connections. depends_on is already in compose.yaml. Make every start of the stack succeed. Startup orderHealthchecksDocker Compose Fix Preview
  6. Your first Compose stack The shop runs a front (nginx), an API, PostgreSQL and Redis. A first compose.yaml starts everything, but it publishes the database and the cache on the server's public address. Only the front may be reachable from the internet, and the front must not even see the database. Docker ComposeCompose networksEXPOSE vs publishing Fix Preview
  7. Resource limits Every afternoon the shop slows to a crawl: the thumbnail worker leaks memory, the server starts swapping and the API's latency explodes. The leak can't be fixed today. Make sure it hurts only the worker. Resource limitsOOM killerSwap Fix Preview
  8. Development and production The team develops with docker compose up: the code is mounted for live reload and the Node.js debugger listens on port 9229. Production runs docker compose -f compose.yaml -f compose.prod.yaml up -d... and gets the same mount and the open debugger. Make production clean without breaking the developers' setup. Compose override filesDev/prod parityBind mounts Fix Preview
99.9%

Advanced

Ship through a registry, deploy without downtime, keep logs and disks under control, and upgrade stateful services without losing data.

Unlock with the Advanced plan
  1. Ship through the registry Production has always built the shop from source with docker compose up --build, right on the server. The new production server receives no source code any more: only compose.prod.yaml and .env. Build release 1.4.0 here, on the CI server, publish it to the company registry, and make compose.prod.yaml run exactly those images. Image registriesImage tagsBuild once, deploy everywhere Fix Preview
  2. The tag that moved Last week the shop went down at 3:00. The nightly job pulled registry.shop.internal/shop/api:1.4.0 again, and got the back-office instead: a misconfigured pipeline had pushed its image under that very tag. The API running right now is the right one. Make sure the nightly job, and any new server built from these files, can only ever start that exact API. Image digestsImage registriesImage tags Fix Preview
  3. The logs that filled the disk Twice this month the shop stopped taking orders in the middle of the night: PostgreSQL could not write any more because the disk was full. docker system df showed nothing unusual. The culprit is the payment webhooks service, which logs at debug level for an ongoing investigation. Keep those logs, but make sure no container can ever fill the disk with its logs again. Log rotationDocker disk usageContainer logs Fix Preview
  4. Bad Gateway after every release The team ships the API several times a day with docker compose up -d --build. After every release the shop answers 502 Bad Gateway, until someone restarts the nginx container by hand. The API itself is fine: curl from inside the network works. Make releases stop breaking the shop. Proxies and container addressesCompose networksDocker Compose Fix Preview
  5. Killed in the middle of a payout The payout worker sends the sellers' money to the bank in batches. On every deployment, docker compose waits ten seconds, then kills it: some transfers are sent twice, others never, and finance reconciles by hand. The worker already knows how to stop cleanly when it receives SIGTERM. Make every deployment let it finish its batch. Graceful shutdownMain process (PID 1)CMD and ENTRYPOINT Fix Preview
  6. PostgreSQL 16 to 17, every order kept The shop's database must move from PostgreSQL 16 to 17 tonight. The plan in the ticket says: change the tag in compose.yaml and run docker compose up -d. Upgrade it so that every one of the 48,210 orders is still there afterwards, and the shop takes orders again. Upgrading a database containerVolumesdocker exec Fix Preview
  7. The token in every image The billing service installs @shop/invoice-pdf from the company's private npm registry, so its build needs the CI's NPM_TOKEN. It is passed as a build argument, and a scan just found that token in the history of every billing image ever pushed. The token has been rotated; now make sure the next images do not carry the new one, while the build keeps installing the private package. Build secretsKeeping secrets out of imagesImage layers Fix Preview
  8. Lock down the exposed containers The penetration test is back: through a flaw in a dependency, the testers wrote a file into the running API container, and they point out that both internet-facing containers keep Docker's default capabilities. Harden frontend and api: a read-only root filesystem, every capability dropped, and no way to gain new privileges. The shop must stay online. Read-only root filesystemLinux capabilitiesRunning as non-root Fix Preview
99.99%

Expert

Run Docker Swarm across several nodes: services, rolling updates, secrets, overlay networks and node failures.

Unlock with the Expert plan
  1. Only the manager has the image Last month the shop moved from one server to a three-node Docker Swarm. docker service ls says shop_api runs 3/3 and the shop answers. Tomorrow at 10:00, manager-1 is drained for its kernel patch, as RUNBOOK.md says: its tasks have to move to the workers. Make sure the shop survives that maintenance window. Services and tasksSwarm modeImage registries Fix Preview
  2. Every release drops requests The shop runs on Docker Swarm, with two replicas of the API, and still every release breaks the checkout for a minute or so. Today the CI ships 1.5.0, which takes about 25 seconds to start, then 1.5.1 later on. Make releases invisible to customers: not a single failed request while the new version rolls out. Rolling updatesHealthchecksOverlay networks and the routing mesh Fix Preview
  3. The release that looked fine Releases no longer drop requests: healthchecks and start-first did it. But last month 1.4.2 converged, everyone went home, and its tasks crashed every forty seconds all night (ops/postmortem-1.4.2.md). Today at 0:30 the CI ships 1.6.0. Whatever 1.6.0 turns out to be, the shop must end up on a version that works, with no one typing a command. Automatic rollbackRolling updatesHealthchecks Fix Preview
  4. The password in docker service inspect The security audit is clear (security/audit-2026-10.md): the database password sits in stack.yaml, in docker service inspect and in the environment of every container. Get it out of all of them. The password itself does not change today, and the shop must keep serving its 48 210 orders. Swarm secretsEnvironment variablesKeeping secrets out of images Fix Preview
  5. The database that moved Last Tuesday on staging, a worker rebooted and all the orders vanished, then new ones appeared out of nowhere (ops/staging-incident.md). Production runs the same stack.yaml, with 48 210 real orders. A node will fail one day: when it happens, the shop may be unavailable while the node is down (database replication is next quarter's project), but it must never lose an order. Placement constraintsVolumesServices and tasks Fix Preview
  6. Two managers, no majority Last month the team promoted manager-2 so that the cluster would survive the loss of a manager. Monitoring now says manager-1's power supply is about to fail, and the replacement only arrives tomorrow. When manager-1 dies, the cluster must go on managing itself: a task that stops must still be replaced. Get the cluster ready. Managers and quorumSwarm modeServices and tasks Fix Preview
99.999%

Architect

Design a container platform: supply-chain security, multi-architecture images, capacity planning and recovery drills.

Unlock with the Architect plan
  1. The poisoned mirror The push account of our image mirror leaked (security/advisory.md). Until it is rotated at 03:00, anyone can overwrite registry.shop.internal/mirror/node:20-alpine or mirror/postgres:16 with a backdoored copy that works perfectly. Tonight the nightly job rebuilds and redeploys on the mirror's latest images. Make sure it can only ever use the images the shop runs on right now, and that it still succeeds. Image digestsBase imagesImage registries Fix Preview
  2. The idle arm64 nodes To cut costs, two of the three workers are now arm64 machines (ops/capacity.md). On paper, the cluster easily holds the six API replicas Black Friday needs. Yet the API only ever runs on worker-1. At 0:30 the CI scales shop_api to 6: make sure all six replicas run, without overloading any node. Multi-platform imagesPlacement constraintsResource limits Fix Preview
  3. Capacity for the campaign Next week's campaign doubles the API and the search replicas (ops/capacity-report.md). The business wants a promise: losing any one worker during the campaign must neither slow the shop down nor remove a replica, and the search index must never be swapped out. Plan the capacity and make the cluster place its tasks accordingly. Capacity planningResource limitsSwap Fix Preview
  4. The recovery drill Twice a year, a drill rebuilds the shop from scratch in another region by running recovery/restore.sh on a brand-new cluster, with nobody at the keyboard. The runbook was written for the old single-server Compose setup and never tested since. Make the drill pass: within 15 minutes of starting, the new cluster serves the shop with every order of last night's backup. Disaster recovery drillsSwarm secretsPlacement constraints Fix Preview