Practice brief
Production Patterns Exercises
The TaskFlow stack has a Dockerfile, a Compose file, a named volume, health checks, and a non-root user. In Module 14 you add the final two production concerns: automatic container restart and memory limits. After this, the stack is ready to run on a server.
Build from scratch
Add Restart Policy and Resource Limits
If the TaskFlow API crashes today, it stays down until someone notices and manually runs docker compose up again. On a server running unsupervised, that could be hours. A restart policy tells Docker to bring the container back automatically. At the same time, a container with no memory limit can consume all available RAM on the host if it has a memory leak, starving other processes. A limit caps the damage. In this exercise you will add both to the Compose file.
Done when
Try it yourself first. Open the guided path if you get blocked.
Step 1 — Add restart policy to both services
Open docker-compose.yml and add restart: unless-stopped to both the db and api service definitions. db: image: postgres:16-alpine restart: unless-stopped ... api: build: . restart: unless-stopped ... unless-stopped means Docker restarts the container whenever it exits unexpectedly — due to a crash, an OOM kill, or a host reboot — unless you explicitly stopped it with docker compose stop. The other common policies are always (restarts even after docker compose stop, which can cause surprise restarts after a deploy) and on-failure (only restarts on non-zero exit codes).
Step 2 — Add memory limits to both services
Add a deploy block with resource limits to each service. This syntax works with docker compose and is also compatible with Docker Swarm. api: build: . restart: unless-stopped deploy: resources: limits: memory: 512M db: image: postgres:16-alpine restart: unless-stopped deploy: resources: limits: memory: 256M 512M means 512 megabytes. If the api process tries to allocate more than this, the kernel sends an OOM signal and Docker kills the container. With restart: unless-stopped in place, it will come back up — but you will also want to investigate the cause of the leak.
Step 3 — Apply the changes
docker compose down
docker compose up -d Restart the stack to apply the new Compose configuration. Compose recreates any service whose definition changed. After startup, run docker compose ps to confirm both services are running.
Step 4 — Prove the restart policy works
docker compose kill api
docker compose ps docker compose kill sends SIGKILL to the api container — it stops immediately without a graceful shutdown. With restart: unless-stopped, Docker detects the unexpected exit and restarts the container within a second or two. Run docker compose ps immediately after the kill and again a few seconds later. You will see the api service transition from exited back to running. The restart count in the Status column increments each time this happens.
Step 5 — Check the memory limit is in effect
docker inspect taskflow-lab-api-1 --format '{{.HostConfig.Memory}}' This prints the memory limit in bytes. 512MB is 536870912 bytes. If the output is 0, the limit was not applied — check that the deploy block is correctly indented in your Compose file. YAML indentation errors are silent; the key just gets ignored. Compose appends -1 to service names, so your container may be named taskflow-lab-api-1.
Break-fix
The Restart Loop That Never Stops
A teammate deployed the TaskFlow stack with restart: always. They then realised the DATABASE_URL had a typo — the wrong password. The API crashes on startup, Docker restarts it immediately, it crashes again, and now the container is in an endless restart loop. Every few seconds it appears briefly in docker ps, then disappears again. docker compose logs api is filling up with the same error. They cannot stop it with docker compose stop because the restart policy keeps overriding the stop. Reproduce this loop, figure out how to break out of it, fix the configuration error, and deploy cleanly.
Reproduce this
docker compose down --volumes You're done when
Show investigative hints
- restart: always restarts the container even after docker compose stop. That is why stop does not break the loop. docker compose down stops and removes the container entirely — removal is not affected by the restart policy.
- Once you have stopped the loop with docker compose down, read the error in docker compose logs api before that session is cleared. The error message will tell you exactly what is wrong with the DATABASE_URL.
- restart: unless-stopped is almost always the better choice for production services. It restarts on crashes but respects explicit stop commands, so you can deploy without fighting the restart policy.
Show diagnosis and fix rationale
restart: always keeps bringing the API back after it crashes, even when an operator is trying to stop it. Break the loop by removing the stack with docker compose down, fix the DATABASE_URL typo, and prefer restart: unless-stopped for long-running services. That policy still restarts crashed containers, but it respects an intentional stop during debugging or maintenance.