← All Video Notes

Docker Deep Dive: Containers, Images, and Stacks Explained

"Just Dockerize it" gets thrown around a lot, but the mental model underneath — images, containers, and stacks — is where most beginners actually get stuck. Once it clicks, everything else about Docker gets easier.

Images: The Blueprint

An image is a read-only template: a filesystem snapshot plus instructions for what to run when a container starts from it. You don't run an image directly — you run a container from it.

docker pull nginx:latest
docker images

Images are built in layers, and that's the whole reason Docker is fast. Each instruction in a Dockerfile (FROM, RUN, COPY, ...) creates a new layer, and Docker caches layers that haven't changed. Change one line near the bottom of your Dockerfile and only that layer (and everything after it) needs to rebuild.

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "server.js"]

Notice package*.json is copied and installed before the rest of the source code. That ordering means npm install only re-runs when your dependencies actually change, not on every code edit.

Containers: The Running Instance

A container is a running (or stopped) instance of an image — an isolated process with its own filesystem, network namespace, and resource limits, but sharing the host's kernel.

docker run -d --name my-nginx -p 8080:80 nginx:latest
docker ps
docker logs my-nginx
docker stop my-nginx && docker rm my-nginx

The relationship is one-to-many: one image, unlimited containers. Spin up five containers from the same nginx:latest image and you get five independent, isolated instances — this is the whole reason containers are cheap and disposable compared to VMs.

A container's writable layer disappears when the container is removed. If you need data to survive that, you need a volume:

docker run -d -v my-data:/var/lib/data --name my-app my-image

Stacks: Multiple Containers, One Definition

Almost nothing real runs as a single container. A typical self-hosted app is a web server, a database, and maybe a cache or reverse proxy — each is its own container, wired together. That's a stack, and docker-compose.yml is how you define it declaratively instead of typing five long docker run commands from memory:

services:
  app:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - db
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: example
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:
docker compose up -d
docker compose logs -f
docker compose down

Compose also creates a private network for the stack automatically, so app can reach db by service name — no manual networking required.

Putting It Together

Image → container → stack is the whole progression: you build or pull a blueprint, run one or more instances of it, and wire multiple instances together into something that behaves like a single application. Once that ordering is intuitive, reading someone else's docker-compose.yml stops being intimidating — it's just a list of images, how they run, and how they talk to each other.

If you haven't already, the home lab getting-started guide is a good next stop for where to actually run this stuff.