Skip to content

Phase 2 · CONTAINERS & CI/CD

Week 4 review + Docker Rescue

Day 28 of 90 ~45 min 0/25 in phase Builds on Day 27

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

Section 1 of 5 · ~3 min

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.

The Docker Rescue walk: see the crash with docker ps, then read docker logs, then find the missing dependency, then declare the whole stack with docker compose up — a lone docker run can't describe a multi-container app. See the crash docker ps Read the logs docker logs Find the dep db + network Declare the stack compose up -d one docker run can't describe a multi-container app — compose can
The rescue walk behind every crash-looping container — the one you run in today's mission.

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 run while 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

Section 2 of 5 · ~2 min

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 crashdocker ps shows the container Restarting, not Up. That status is the tell that it starts, dies, and starts again on a loop rather than exiting once (days 22–23).
  • Read the logsdocker 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 run started 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 a compose.yaml that declares both services, their network and the volume, brought up with docker compose up -d so Docker’s embedded DNS lets the app find db by 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

Section 3 of 5 · ~3 min

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 docker command on a fresh host before the daemon is up, or as a user who isn’t in the docker group:

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

Why: The docker CLI 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 (and enable it for boot), then confirm with docker info. To drop the sudo, 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 docker command 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 the docker group. 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 allocated

Why: -p 8080:80 asks 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 (or ss -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’s ports: 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 -a shows the leftover; the deploy needs a docker 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 named db on 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 --network flags. Declare both services in a compose.yaml; docker compose up -d creates a default network for the project, attaches both containers, and lets the app resolve db by 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

Section 4 of 5 · ~1 min

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

Section 5 of 5 · ~1 min

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 run alone and watch it fail to reach a db; then write a tiny compose.yaml with both services and run docker compose up -d, and watch name resolution just work. Feel the exact gap the mission exploits.
  • 10 min — Run docker compose ls and docker ps side by side and read the difference between a stack (a compose project) and the individual containers it manages; then skim docker compose --help to see up, down, logs and ps in one place.
  • 15 min — Skim the official Compose file reference and map each top-level key — services, networks, volumes — back to the docker run flags 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