Phase 2 · CONTAINERS & CI/CD
Week 4 review + Docker Rescue
By the end of today
- Consolidate week 4 into one build, run, connect, persist, compose loop
- Rescue a crash-looping container by moving from docker run to compose
- Explain why a container that ran yesterday crash-loops on a fresh host
The Week-4 muscle: build it, run it, connect it, persist it, compose it
Week 3 left the app on one box — scheduled and versioned, but still tied to that machine’s exact setup. Week 4 cut the last cord: Docker packages your app with its environment so it runs the same on your laptop, a colleague’s, and a fresh production host. Six days built that skill, and today they collapse into one reflex — when a container won’t stay up, you walk it from image to running stack and stop at the first thing that’s missing.
The container chain has three links, and you now own each one:
Package it. Day 22 drew the line between a container and a VM — one shares the host kernel and starts in milliseconds, the other boots a whole guest OS. Day 23 gave you the vocabulary: an image is the read-only recipe, a container is a running instance of it, and a registry like Docker Hub is where images live. Day 24 taught you to write that recipe yourself in a Dockerfile.
Connect it. Day 25 showed how a container talks to the world: -p 8080:80 maps a host port to a container port, and containers on the same user-defined network reach each other by name. Day 26 gave your data somewhere to live past a docker rm — a named volume, mounted so a database survives the container being recreated.
Compose it. Day 27 tied it together: a real app is rarely one container. docker compose up -d reads a compose.yaml that declares every service, its network and its volumes, and brings the whole stack up with one command.
Put those together and you have what every containerized app is underneath: images, wired on a network, with volumes for state, described once in a compose file. Which is exactly why a lone docker run fails in ways compose doesn’t — it can only start one container, and a modern app almost always needs more than one.
Real world: A food truck runs anywhere because it carries its own kitchen — you don’t rebuild a stove at every stop. A container is that truck. But a restaurant that needs a kitchen and a walk-in fridge next door can’t fit both in one truck: park only the kitchen and every order fails the moment it needs the fridge. That’s a web app started alone with
docker runwhile its database truck was never parked — nothing is broken, the meal just can’t be made.
A named example makes it concrete. The declarative move compose teaches — describe the desired stack in a file, run one command, let the tool create and wire everything — is the exact mental model Kubernetes runs on at scale. A compose.yaml and a Kubernetes manifest both say what you want, not how to start it step by step, so learning to read one service block today makes the manifests waiting in Phase 3 read like a longer version of the same idea.
That chain is why today is a mission, not a lecture. Reading about a crash-looping container doesn’t build the reflex — rescuing one does. Docker Rescue hands you an app someone started with a bare docker run, crash-looping on a database that was never there, and the week-4 tools to bring the whole stack up the right way — without touching a line of the app’s source.
Hands-On Lab
Today the lab is the mission. No WSL2, no setup, nothing to paste — Docker Rescue runs entirely in your browser.
This is your Week-4 boss fight, and it closes the container half of Phase 2. A teammate deployed a web app to a fresh host with a single docker run and went home; it’s been crash-looping ever since. The image is fine, the host is fine, and the source code is fine — the container just won’t stay up. Everything you need is something you already learned in days 22–27, so play it as the walk from the diagram:
- See the crash —
docker psshows the containerRestarting, notUp. That status is the tell that it starts, dies, and starts again on a loop rather than exiting once (days 22–23). - Read the logs —
docker logs <container>is where the app tells you why it’s dying. A “could not resolve host db” or “connection refused” line means the process itself is fine, but something it depends on isn’t there (day 23’s containers, the debugging habit you’ll formalize on day 30). - Find the dependency — the app needs a second container, a database, on a shared network, with a volume for its data. One
docker runstarted only the app; nothing ever started the db or wired the two together (days 25–26). - Declare the stack — the fix isn’t a longer
docker run. It’s acompose.yamlthat declares both services, their network and the volume, brought up withdocker compose up -dso Docker’s embedded DNS lets the app finddbby name (day 27).
Type help in the terminal to see the supported commands, and hint if you stall — it nudges without solving. There’s no penalty for poking around; the whole point is to run the crash → logs → dependency → compose walk with your hands until it’s reflex, and to feel why a single docker run can never describe a multi-container app. Get the stack healthy — then, if you want the bragging rights, replay it and try to bring it up in fewer commands.
When the app finally reports healthy, come back and note below which layer tripped you up — that reflection is where the lesson sticks.
Common Errors & Fixes
These are the mistakes that trip people up when they run the week-4 container walk for real on Ubuntu 24.04 — the same links the mission rehearses. Read the error text slowly; parsing it is the skill.
Common error: Running any
dockercommand on a fresh host before the daemon is up, or as a user who isn’t in thedockergroup:Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?Why: The
dockerCLI is only a client — it talks to the Docker daemon over a socket. If the daemon isn’t started, or the current user lacks permission to read/var/run/docker.sock, the client has nothing to connect to. It is not a problem with the image or the command that followed.Fix: Start and enable the service with
sudo systemctl start docker(andenableit for boot), then confirm withdocker info. To drop thesudo, add yourself to the group once —sudo usermod -aG docker $USER— and open a new login shell so the group membership takes effect.How you’d spot it in prod: A CI job or deploy script that fails on its very first
dockercommand with “Cannot connect to the Docker daemon” is almost never a bad image — the runner has no Docker service, or the service user isn’t in thedockergroup. Check the daemon before you touch the pipeline.
Common error: Bringing a container up on a host port something else already holds:
Error response from daemon: driver failed programming external connectivity on endpoint web: Bind for 0.0.0.0:8080 failed: port is already allocatedWhy:
-p 8080:80asks Docker to bind host port 8080. Only one process can listen on a host port at a time, so if a stray earlier container already holds 8080, the new container can’t publish and refuses to start. (A non-Docker process holding the port produces a different message —Bind for 0.0.0.0:8080 failed: address already in use.)Fix: Find the holder with
docker ps(orss -tlnp | grep ':8080'for a non-Docker process), then either stop it or publish on a free host port instead:-p 8081:80. In compose, edit the service’sports:mapping rather than re-typing a run command.How you’d spot it in prod: A deploy that worked yesterday failing with “port is already allocated” usually means the previous container wasn’t cleaned up before the new one started.
docker ps -ashows the leftover; the deploy needs adocker compose down(or--remove-orphans) before it brings the stack back up.
Common error: The app container starts, then crash-loops because it can’t reach a database that was never started alongside it:
Error: getaddrinfo ENOTFOUND db at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:118:26) { errno: -3008, code: 'ENOTFOUND', syscall: 'getaddrinfo', hostname: 'db' }Why: The app was launched alone with
docker run, so there is no container nameddbon a shared user-defined network for Docker’s embedded DNS to resolve. Name-based service discovery only works between containers on the same network — a lone container has no peer to find, so the lookup fails and the process exits, again and again.Fix: Don’t hand-wire it with a pile of
docker run --networkflags. Declare both services in acompose.yaml;docker compose up -dcreates a default network for the project, attaches both containers, and lets the app resolvedbby name — with a named volume so the database keeps its data across restarts.How you’d spot it in prod: An app whose logs show a name-not-found or connection-refused error for a dependency, on an otherwise healthy host, points to a dependency that was never started or never wired onto the same network. Check what the stack should contain — the compose file or manifest — against what’s actually running with
docker ps.
Docker Interview Questions
The “runs on my laptop, crash-loops on the host” walk-through and the image-versus-container basics below are among the most common Phase-2 container screening questions — a calm, layered answer beats a clever one every time. The answer bank renders right after this note. Cover each answer, say your own version out loud first, then compare — recalling before revealing is what makes it stick for interview day.
Go Deeper
Optional extras if you have ~40 more minutes:
- 5 min — Replay Docker Rescue and try to bring the stack up in the optimal command count — speed here is just knowing the crash → logs → dependency → compose walk cold.
- 10 min — On a throwaway box, run a web image with
docker runalone and watch it fail to reach adb; then write a tinycompose.yamlwith both services and rundocker compose up -d, and watch name resolution just work. Feel the exact gap the mission exploits. - 10 min — Run
docker compose lsanddocker psside by side and read the difference between a stack (a compose project) and the individual containers it manages; then skimdocker compose --helpto seeup,down,logsandpsin one place. - 15 min — Skim the official Compose file reference and map each top-level key —
services,networks,volumes— back to thedocker runflags from days 25–26 it replaces. That mapping is the whole point of compose.
Why does a container run on your laptop but crash-loop on a fresh host? Both
Usually it isn't the image — the same image runs both places. It's what the container depends on and can't find. Started alone with docker run, an app has no database container, no shared network and no volume, so it dies the moment it tries to reach a db that was never started. Other classics: a bind-mount path that exists on your laptop but not the server, a missing environment variable or secret, or a host port already taken. My move is docker logs to read why it's dying, then check whether every dependency it assumes — another container, a network, a volume, an env var — actually exists on that host.
When would you reach for docker compose instead of a plain docker run? Product
The moment the app is more than one container, which is almost always. A bare docker run starts a single container; a real app is a web process plus a database, maybe a cache and a queue, that all need to find each other on a network and keep their data in volumes. Compose lets me declare every service, network and volume in one compose.yaml and bring the whole stack up with docker compose up -d — reproducibly, in the right order. I'll still use docker run for a quick one-off, like a throwaway shell in an image. But anything with dependencies or state I want described in a file, not typed as an ever-growing run command.
A container keeps restarting — how do you debug it? Product
I start with docker ps to confirm it's actually restarting rather than exited, then docker logs on it — the application almost always prints why it died right before it does. From there it's usually one of a few things: it can't reach a dependency like a database, a required environment variable is missing, the command in the image exits immediately, or it's out of memory. docker inspect shows the restart policy, the env, the mounts and the networks it's attached to. The key habit is reading the logs first instead of guessing — a restarting container is rarely a broken image, it's usually something in its environment or its dependencies that isn't there.
What's the difference between an image and a container? Both
An image is the read-only template — the filesystem, dependencies and default command, built from a Dockerfile and stored in a registry like Docker Hub. A container is a running instance of that image, with a thin writable layer on top. The relationship is like a class and an object, or a program on disk versus a process: one image can spawn many containers, and each gets its own writable layer, so changes inside a running container don't alter the image. That's also why data written inside a container vanishes when it's removed unless you mount a volume — the writable layer is disposable by design, and the image it came from never changed.
Mark Day 28 complete
Tomorrow you make those images lean and safe — multi-stage builds, smaller layers, and images that don't ship a whole toolchain to prod.
Mission unlocked: Docker Rescue — you have the skills now.
Play (15–20 min)Stuck on today’s lab? Ask in Mission 90 Q&A