🐳
A Practical Introduction

Docker
from zero
to compose

Containers, images, volumes, networks — and why your ops team will love you for it.

containers images volumes compose
press → or arrow-keys to advance
concept 01

VMs vs Containers

Same isolation goal — very different mechanism.

VIRTUAL MACHINE Physical Hardware Host OS Hypervisor Guest OS App A libs kernel ~2 GB Guest OS App B libs kernel ~2 GB Guest OS App C libs kernel ~2 GB DOCKER CONTAINERS Physical Hardware Host OS Kernel ← shared! processes isolated via namespaces + cgroups Docker Engine (containerd) Container A App A libs only no guest OS ~100 MB Container B App B libs only no guest OS ~100 MB Container C App C libs only no guest OS ~100 MB ⚡ No hypervisor · No boot · Starts in milliseconds
concept 02

Processes run on the host kernel

Containers are just regular Linux processes — wrapped in isolation primitives.

No hypervisor · No guest OS · No boot time $ ps aux (on host) root 1041 nginx: master process postgres 1089 postgres: checkpointer node 1112 node server.js ← real host PIDs — each container thinks it is PID 1 inside its own namespace Each is an isolated process group on the host kernel: nginx PID namespace: 1 net: 172.17.0.2 mnt: /var/www user ns · uts ns postgres PID namespace: 1 net: 172.17.0.3 mnt: /var/lib/pg user ns · uts ns myapp PID namespace: 1 net: 172.17.0.4 mnt: /app user ns · uts ns Linux Kernel ← shared by all containers namespaces · cgroups · seccomp · no hypervisor needed

📦 Namespaces — "what you see"

  • PID — each container thinks it's PID 1
  • NET — own network stack & IP
  • MNT — own filesystem view
  • USER — own user IDs
  • UTS — own hostname

⚖️ cgroups — "how much you get"

Limit CPU, RAM, and I/O per container. The kernel enforces quotas — no runaway process can starve another.

✅ Net result

Full isolation without a second OS. Starts in under a second. Shares the host kernel efficiently and safely.

concept 03

Image vs Container

An image is a blueprint. A container is a running instance of that blueprint.

IMAGE (read-only) Layer 0 · ubuntu:22.04 base Layer 1 · apt install python3 Layer 2 · pip install -r req.txt Layer 3 · COPY app/ /app stacked, content-addressed layers (sha256) docker build → docker push / pull docker run CONTAINERS (running) container A thin r/w layer image layers (shared read-only) PID 1: python app.py stop → r/w lost ⚠ container B thin r/w layer image layers (shared read-only) PID 1: python app.py stop → r/w lost ⚠ Both share the same image layers — writes differ → use volumes for persistence!
concept 04

Run Alpine on Ubuntu, Debian on Fedora…

Images bundle their own userland — only the kernel is borrowed from the host.

Ubuntu 22.04 Host Kernel Linux 6.x ← the only thing shared Docker Engine Alpine 3.19 musl libc · ~5 MB nginx 1.25 busybox + apk Debian Bookworm glibc · ~120 MB python 3.12 GNU coreutils + apt ❌ NOT in the image kernel modules · hardware drivers systemd · the kernel itself → all come from the host Images ship userspace only — kernel is the host's

🧅 What's inside an OS image?

  • Standard C library (glibc or musl)
  • Package manager (apt, apk, dnf…)
  • Core utilities (ls, cat, bash…)
  • App + its runtime (node, python, jre…)
  • Not the kernel

🤔 Why does this work?

Linux has a stable syscall ABI. Every app ultimately calls syscall. The kernel responds. The C library version comes from the image, not the host — Debian glibc runs fine on an Alpine host kernel.

⚠️ One real limit: CPU arch

You cannot run an ARM64 image on an x86-64 host without QEMU emulation. Kernel ABI is stable but architecture-specific.

concept 05

Building with a Dockerfile

A recipe: each instruction bakes a new read-only layer into the image.

# Start from an official base image
FROM python:3.12-slim

# Set working directory inside the container
WORKDIR /app

# Copy dependency list FIRST (layer cache trick!)
COPY requirements.txt .

# Install — cached if req.txt unchanged
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the app code
COPY . .

# Document which port the app listens on
EXPOSE 8000

# Set an environment variable
ENV DEBUG=false

# Default command when container starts
CMD ["python", "-m", "uvicorn", "main:app"]

Key instructions

  • FROM — base image to start from
  • WORKDIR — cd (creates dir if absent)
  • COPY — host → image filesystem
  • RUN — execute command, bake into layer
  • ENV — set environment variable
  • EXPOSE — document a port (doesn't open it!)
  • CMD — default start command

🏗️ Build & run

# Build and tag
docker build -t myapp:1.0 .

# Run, mapping host :8080 → container :8000
docker run -p 8080:8000 myapp:1.0

💡 Layer caching tip

Put rarely-changing steps first. COPY requirements.txt before COPY . . so slow installs only re-run when the list changes.

concept 06

Volumes — Persistent Data

Container filesystems are ephemeral. Volumes live outside the container lifecycle.

NAMED VOLUME postgres container /var/lib/postgresql ← mount point 💀 delete container… data survives! ✅ pgdata named volume managed by Docker daemon survives container deletion ✅ docker volume create pgdata docker run -v pgdata:/var/lib/postgresql/data postgres or declare in docker-compose.yml → volumes: pgdata: HOST BIND MOUNT dev container /app (inside) ← mount point changes appear live 🔥 perfect for dev 📁 ./src host directory docker run -v $(pwd)/src:/app myimage --mount type=bind,source=./src,target=/app → edits on host instantly visible inside container
concept 07

Ports & Private Networks

Containers have their own network stack. Traefik handles HTTPS — the apps inside speak plain HTTP.

🌐 Internet HTTPS only 🔒 :443 encrypted Host machine 192.168.1.10 :443 open :5432 ❌ -p 443:443 → host:container no -p = unreachable outside postgres never exposed ✅ plain HTTP :80 inside network apps don't need to handle TLS ✅ → traefik Private Docker Network (mynet) 172.20.0.0/16 · containers find each other by name 🔀 traefik 172.20.0.2 :443 published 🔒 TLS termination here decrypts HTTPS → HTTP 🔑 cert.pem + key.pem routes by Host header 📦 myapp 172.20.0.3 :8000 internal HTTP only web application no TLS needed — traefik handles it 🐘 postgres 172.20.0.4 · :5432 not published ✅ HTTP SQL
concept 08

docker compose

Define your whole stack in one YAML file. One command to start everything.

# docker-compose.yml
services:

  traefik:
    image: traefik:v3
    command:
      - "--providers.docker=true"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.websecure.http.tls=true"
    ports:
      - "443:443"     # only public port (HTTPS)
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
      - "./certs:/certs:ro"  # cert.pem + key.pem
    networks: [mynet]

  app:
    build: .
    labels:
      - "traefik.http.routers.app.rule=Host(`app.example.com`)"
      - "traefik.http.routers.app.entrypoints=websecure"
    networks: [mynet]
    depends_on: [db]
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/mydb

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks: [mynet]   # no ports: → stays private

volumes:
  pgdata:

networks:
  mynet:

🚀 Three commands to know

# Start everything (detached)
docker compose up -d

# Stream logs from all services
docker compose logs -f

# Stop & remove containers + network
docker compose down

🔒 How HTTPS works here

  • Traefik listens on :443 — the only public port
  • Holds the TLS cert & key in ./certs/
  • Decrypts HTTPS, forwards plain HTTP to app
  • App never needs to handle TLS itself

🐘 Postgres stays private

No ports: on db. App reaches it as db:5432 on the internal network. Internet never touches it.

concept 08b

Compose Stack Diagram

Traefik is the single HTTPS gateway — apps inside talk plain HTTP to each other.

🌐 Browser 🔒 HTTPS :443 encrypted DOCKER COMPOSE STACK 🔀 traefik image: traefik:v3 🔒 port 443 published TLS termination HTTPS → HTTP inside 🔑 cert.pem key.pem reads docker labels routes by Host header HTTP 📦 app build: . no public port label: router rule env: DATABASE_URL plain HTTP :8000 internally no TLS needed ✅ SQL 🐘 db (postgres) :5432 internal · NO public port ✅ 💾 pgdata volume named vol · survives restarts 📁 ./certs (bind) cert.pem · key.pem host bind-mount → traefik ALL SERVICES ON: mynet (172.20.0.0/16)
concept 09

Pets vs Cattle

The philosophy shift that makes containers so transformative for operations.

🐕

Pets

  • Each server has a name (prod-web-01)
  • Hand-configured unique snowflakes
  • When sick — nurse it back to health
  • SSH in, patch, tweak, pray
  • Hard to reproduce, hard to scale
  • 3am outage? You're paging someone

Traditional VMs / bare-metal ops

🐄

Cattle

  • Containers are numbered (app_1, app_2…)
  • Identical, built from the same image
  • When sick — kill and replace
  • docker compose restart app
  • Scale with --scale app=10
  • Reproducible, immutable, declarative

Docker / Kubernetes / modern ops

🔑

Why it matters

  • No config drift between environments
  • "Works on my machine" → ship the machine
  • Rollback = pull the old image tag
  • CI/CD pipelines just work™
  • Autoscaling becomes trivial
  • On-call becomes boring (good!)

Immutable infrastructure wins

recap

You now know Docker

Everything needed to containerise a real app today.

🔵 Containers

  • Run on host kernel via namespaces + cgroups
  • Not VMs — no guest OS, instant start
  • Image = blueprint; Container = instance
  • Ephemeral writable layer → use volumes!

🟢 Images & Build

  • Layered, content-addressed filesystem
  • Bundle their own userland, not the kernel
  • FROM → RUN → COPY → EXPOSE → CMD
  • docker build → docker push/pull

🟠 Storage & Networking

  • Named volumes: managed, persistent data
  • Bind mounts: host dir ↔ container (dev)
  • -p host:container exposes ports
  • Private network: containers talk by name

🐋 Compose

One YAML, one command: docker compose up -d. Traefik handles HTTPS on :443, holds the cert & key, decrypts traffic and forwards plain HTTP internally. Postgres stays private.

🐄 Pets vs Cattle

Treat containers as disposable identical units. Kill & replace instead of nurse & patch. Immutable infrastructure = fewer 3am pages.

Next steps: docker.com/get-started · play-with-docker.com · docs.docker.com/compose