Docker

The tool that made containers practical: how Docker packages, distributes, and runs containers with a simple developer workflow.

BeginnerInfrastructureChapter: Infrastructure10 min read

What Docker Is

Docker is the tool that made containers mainstream. Before Docker (2013), Linux containers existed but required deep kernel knowledge to set up. Docker wrapped them in a simple CLI and an image format that anyone could use.

The core Docker workflow is three steps: build an image, push it to a registry, run it anywhere.


The Dockerfile

A Dockerfile is a plain text recipe that describes how to build an image. Docker reads it top to bottom, executing each instruction and saving the result as a new layer.

Image layers built from a Dockerfile Layer 1: FROM node:20-alpine (base OS + Node runtime — pulled from registry, cached) Layer 2: COPY package.json + RUN npm ci (dependencies — cached until package.json changes) Layer 3: COPY . . (your app code — rebuilt on every code change) Writable layer: runtime writes (logs, temp files) — discarded when container stops build order

The order of instructions matters for caching. Copying package.json before the rest of your source files means the npm ci layer gets cached and skipped on every rebuild where only your code changed. This keeps builds fast.


Images and Registries

An image is just a stack of layers plus some metadata (what command to run, what port to expose). Once built, you tag it and push it to a registry — a storage server for images.

Docker Hub is the public default. Companies typically run a private registry (AWS ECR, Google Artifact Registry, or a self-hosted Harbor) so images stay internal.

When a server pulls an image, it only downloads the layers it doesn't already have. If the base Node image is already cached locally, only your application layers transfer.


Running Containers

docker run takes an image and starts a container from it. Key options engineers use daily:

  • -d — run detached (in the background)
  • -p 3000:3000 — map port 3000 on the host to port 3000 inside the container
  • -e DATABASE_URL=... — inject environment variables
  • --name my-api — give the container a name instead of the random default
  • -v /data:/app/data — mount a host directory into the container (for persistent data)

A container runs until its main process exits. If the process crashes, the container stops. Docker has a --restart always flag that will restart it automatically, but in production you'd normally hand this responsibility to an orchestrator like Kubernetes.


Docker Compose

For local development with multiple services (an API, a database, a cache), Docker Compose lets you define them all in a single docker-compose.yml and start everything with one command: docker compose up. Each service gets its own container and they share a private network, so they can reach each other by name.


Further Reading

Prerequisites

Code Examples