Skip to content
Episode 61 · Works on my laptop

Docker explained

Tuesday, 10:40: release 2.14 passed every test on your laptop, yet one of StayHub's three servers crashes on start and 1 in 3 bookings fail. Same code, three machines, three different setups.

Why software that works on your laptop breaks on a server, and how Docker packs everything into one box that runs the same everywhere.

30 min intermediateContainersVirtual machinesImages vs containersDockerfile & layersRegistries & image tagsVolumes & container networks
  1. Problem

    The booking app works on your laptop and on two servers, and crashes in production with a different Node version and a missing system library.

  2. Why the simple system fails

    Installing dependencies by hand on each machine drifts: different OS packages, runtime versions and config. Every environment is a unique snowflake.

  3. Engineering concept

    Package the app with its runtime and dependencies into an immutable image; run identical, isolated copies (containers) anywhere.

  4. Technology

    Dockerfile → image (layers) → registry → container, plus volumes, networks and Docker Compose.

  5. Trade-offs

    Consistent, fast deploys in exchange for image hygiene, a build pipeline, and the reminder that containers share the host kernel and are not full VMs.

01

The problem

2 min
Tuesday, 10:40. Ten minutes after the 2.14 deploy.
  1. Omar · On-call SRE
    app-3 is crash-looping since 2.14. 1 in 3 booking requests fail. app-1 and app-2 are fine.
  2. You · Engineer at StayHub
    It passed every test on my laptop. Same commit on all three servers.
  3. Lina · Senior engineer, your mentor
    Same code, yes. Guess what's different.
02

The big idea

4 min
Same code, three different servers
The big idea

Don't install on each machine. Build one image and run it everywhere.

Installing Node 22 on app-3 fixes today's difference; the next server drifts some other way. With images, the question "what's installed here?" becomes "which image is running?", which has one exact answer.

03

How it works

5 min
From recipe to running app
  1. 1
    Write the recipe

    A says: start from Node 22, install libvips, install dependencies from the lockfile, copy our code, start with node server.js.

  2. 2
    Build the image once

    A is a frozen, versioned package: code plus runtime and libraries. booking-api:2.14.0, about 380 MB, never changes after it's built.

  3. 3
    Layers

    Each recipe step makes an : base 50 MB, Node 100, dependencies 220, our code 10. A release that changes only code ships about 10 MB.

  4. 4
    Push it to a registry

    A is a shelf for images; every server pulls the exact same version.

  5. 5
    Run containers

    A is the image running. It sees only the image's files (Node 22 everywhere) but shares the host's Linux kernel, so it starts in under a second, unlike a VM.

  6. 6
    Roll back by tag

    Bad release? Run 2.13.2 again: it's still on disk, about 20 s per server.

04

Watch a decision being made

4 min
Worked example

Lina moves the booking API to images

Make all three servers run exactly the same thing, and make rollback fast.

  1. 1List what the app needs

    Node 22, libvips for sharp, the npm dependencies, the start command, port 3000.

    Why: Whatever isn't on this list is what drifts between servers.

  2. 2Write the recipe

    Turn the list into a Dockerfile, reviewed in git next to the code.

    dockerfile
    FROM node:22-slim
    WORKDIR /app
    RUN apt-get update && apt-get install -y libvips \
     && rm -rf /var/lib/apt/lists/*
    COPY package.json package-lock.json ./
    RUN npm ci --omit=dev
    COPY . .
    USER node
    CMD ["node", "server.js"]

    Why: The environment becomes versioned like code.

  3. 3Build once, tag with a version

    CI builds registry.stayhub.dev/booking-api:2.14.0 on merge and pushes it.

    Why: The exact bytes that passed the tests ship; the tag says what's running.

  4. 4Run the same tag everywhere, prove the rollback

    Each server runs the image with secrets passed at start (--env-file). Then run 2.13.2 once: about 20 s.

    Why: Servers only need Docker now. A rollback you haven't tried is a hope.

The whole thing in one line: Everything the app needs went into one versioned image, built once and run as the same tag everywhere: drift gone, rollback = run the previous tag.
05

In real life

3 min
Two well-known teams, two choices

Google: containers for almost everything

  • Has publicly said for years that nearly everything it runs is in containers
  • Its cluster system (Borg) inspired Kubernetes
  • Thousands of services deployed constantly

Worth it when many services share many machines.

Stack Overflow: a few hand-tuned servers

  • Has publicly described running its main site on a small number of big servers for years
  • A small team knew every machine well
  • Fewer moving parts, careful setup

Worth it with a tiny, stable, well-known fleet.

06

What goes wrong

3 min
Baking secrets into the image

Any layer containing a secret is readable by anyone who can pull.

Instead

.env* in .dockerignore; inject secrets at runtime; --mount=type=secret for build steps.

Deploying `:latest`

A moving label: servers can run different code, and you can't tell what to roll back to.

Instead

Immutable version or commit tags.

Storing data inside the container

The writable layer dies with the container.

Instead

Volumes, or managed services and object storage.

07

Trade-offs

3 min
ContainerVirtual machine
IsolationProcess-level, shared kernelOwn kernel, stronger boundary
Start timeMilliseconds to secondsTens of seconds to minutes
SizeMBs to hundreds of MBsGBs (full OS)
Typical usePackaging and running appsHosting containers; isolating tenants
Tuesday, 14:00. The outage is over.
  1. Karim · CTO
    Containers mean a pipeline, a registry and a new tool for 22 people. Why not a script that installs Node 22 and libvips everywhere?
  2. Lina · Senior engineer, your mentor
    A script runs once. Servers keep changing after it.
  3. Karim · CTO
    We have three servers. I can check three servers.
08

Your job as tech lead

4 min

Claude Code can write the code for this. You still own the decisions, and you have to know what good looks like to accept or reject its work.

Decisions you own
  • The exact base image (Node 22, pinned). An agent will happily pick node:latest.
  • How images are named: booking-api:2.14.0 or the commit SHA, never only latest.
  • What never goes in an image: .env files, keys, passwords. Secrets arrive at start.
Tell Claude Code
Containerise the booking API so release 2.14 runs identically on app-1, app-2 and app-3. Write a Dockerfile from an official Node 22 image pinned to an exact version, with libvips for sharp. Install from the lockfile only. Add a .dockerignore excluding .env files, node_modules and test data. Config and secrets come from environment variables at runtime. Tag booking-api:2.14.0. Don't change app code or the email worker. Build, run it, call the health endpoint and a photo resize, then show the image size and layers.
Check before you accept
  • The base names an exact Node 22 version.
  • docker history: about 380 MB, no .env or secrets in any layer.
  • You ran the container and resized a photo: the exact thing that crashed app-3.
Red flags
  • It copies .env into the image, even with a later rm.
  • It fixes one server by installing libraries by hand instead of in the image.
  • It says the image works without running a container from it.
09

Check yourself

2 min
Q1/3Reasoning
Each Docker container runs its own operating-system kernel, just like a virtual machine.
How confident are you?

Next episode: the search team rushed their own image out. It weighs 1.4 GB, and every one-line fix takes six minutes to build.

Finished?

Mark it complete to update your skill map.

Go deeper (optional)You don't need this to understand the lesson. Open it when a project needs the details: Technical deep dive, Interactive simulation, Visual diagram, Analogy, Check your understanding, Practice ladder, Explain it yourself.
10

Technical deep dive

Build once, run anywhere, roll back fast

Here's the Lina writes for the booking API. Each instruction becomes an ; the order matters, and the next episode is all about why.

dockerfile
FROM node:22-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends libvips \
 && rm -rf /var/lib/apt/lists/*
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
USER node
EXPOSE 3000
CMD ["node", "server.js"]
Runtime and system library in the image, dependencies from the lockfile, our code last, a non-root user.
Worked example

Lina moves the booking API to images

Release 2.14 crashes on app-3 because it has Node 20 and no libvips. Make all three servers run exactly the same thing, and make rollback fast.

  1. 1List what the app needs

    Write down everything outside our code: Node 22, libvips for sharp, the npm dependencies, the start command, the port (3000).

    Why: Whatever isn't in this list is what drifts between servers. It all has to go into the image.

  2. 2Write the recipe

    Turn the list into a Dockerfile: FROM node:22-slim, install libvips, npm ci from the lockfile, COPY the code, USER node, CMD.

    Why: The Dockerfile lives in git next to the code, so the environment is reviewed and versioned like code.

  3. 3Build once, tag with a version

    CI runs the build on every merge and tags the result.

    bash
    docker build -t registry.stayhub.dev/booking-api:2.14.0 .
    docker push registry.stayhub.dev/booking-api:2.14.0

    Why: Building once means the exact bytes that passed the tests are the ones that ship. A version tag tells you what's running.

  4. 4Run the same tag everywhere

    On app-1, app-2 and app-3: pull booking-api:2.14.0 and start it with docker run, passing secrets at start time.

    bash
    docker run -d --name api -p 3000:3000 \
      --env-file /etc/stayhub/prod.env \
      registry.stayhub.dev/booking-api:2.14.0

    Why: Each server now only needs Docker. Its own Node version no longer matters.

  5. 5Prove the rollback

    Stop the container and run booking-api:2.13.2 instead. Time it: about 20 s per server, because the old image is still on disk.

    Why: A rollback you haven't tried is a hope. Rehearsing it once makes the next bad release a non-event.

The whole thing in one line: Lina moved everything the app needs into one versioned image, built it once in CI and ran that exact tag on all three servers, so drift disappeared and rollback became "run the previous tag".
ContainerVirtual machine
IsolationProcess-level (namespaces, cgroups), shared kernelHardware-level, own kernel via hypervisor
Start timeMilliseconds to secondsTens of seconds to minutes
SizeMBs to hundreds of MBsGBs (full OS)
DensityDozens to hundreds per hostA handful per host
Security boundaryWeaker; kernel exploits affect all containersStronger
Typical usePackaging and running appsRunning containers on, isolating tenants, other OSes
In the cloud you usually run containers inside VMs: StayHub's app-1..3 are VMs running containers.
11

Interactive simulation

12

Visual diagram

Click a component

From source code to running containers. Follow one release of the booking app.

13

Analogy

  • Shipping containersThe packing listDockerThe Dockerfile
  • Shipping containersA sealed, labelled steel boxDockerAn image
  • Shipping containersThe box opened and in useDockerA running container
  • Shipping containersThe port warehouseDockerA registry
  • Shipping containersCranes don't care what's insideDockerAny server can run any image

Where the analogy breaks: a real container is opened once. One image can be "opened" as many running containers as you like, at the same time, and the image itself never changes.

14

Check your understanding

Q1/4Recall
Match each Docker concept to what it is:
Dockerfile
Image
Container
Registry
Volume
How confident are you?
15

Practice ladder

Step 1 · Trace

Trace a release to app-3

Scenario Warm-up

StayHub now ships images. The facts:

  • app-3 has Node 20 installed on its disk and Docker installed.
  • booking-api:2.15.0 was built from FROM node:22-slim and includes libvips.
  • app-3 already has booking-api:2.14.0 (380 MB) on disk. 2.15.0 changes only our code (the 10 MB layer).
  • Omar runs: docker pull registry.stayhub.dev/booking-api:2.15.0 then docker run … booking-api:2.15.0.

Trace what happens: how much is downloaded, which Node version the app runs with, and what Omar types if 2.15.0 turns out to be broken.

Step 2 · Complete

Finish Lina's Dockerfile for the email worker

Code Practice

The email worker is a Node 22 app started with node worker.js. It renders PDF invoices, which needs the system package fonts-dejavu. Lina's Dockerfile has three gaps:

FROM ___1___
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends fonts-dejavu \
 && rm -rf /var/lib/apt/lists/*
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
___2___
USER node
___3___

Fill in the three gaps and say in one line why each matters.

Step 3 · Solve

"Still works on my machine"

Debugging Practice

A week after the move to images, three new problems appear. For each, give the cause and the fix.

  1. You build booking-api:2.16.0 on your Apple Silicon laptop and push it. On app-1 (x86-64) the container exits instantly: exec /usr/local/bin/node: exec format error.
  2. Karim notices app-2 runs booking-api:latest and app-3 runs booking-api:latest, but they behave differently.
  3. A teammate fixed a typo by running docker exec into the container on app-1 and editing a file. After the next deploy, the typo is back.
Step 4 · Decide

Containers for the reporting server?

Decision Stretch

StayHub has one more server: reports-1, a VM that runs a nightly Python script building revenue reports for hotel owners. The facts:

  • It changes about twice a year.
  • It needs Python 3.12 and a PDF library with system fonts.
  • It was set up by an engineer who left; nobody has written down how.
  • The VM must be rebuilt next month because its OS reaches end of life.

Karim: "It's one boring server. Do we bother putting it in a container?"

Write a short decision in this form: "We chose X over Y because…, accepting the risk of…".

16

Explain it yourself

Explain to a teammate who has never used Docker what problem it solves and how an image becomes running containers in production.

0 words