Docker Intro
Docker packages an application and its OS-level dependencies into an image, then runs it as a container — an isolated process with its own filesystem and network.
Docker — what it is
EXAMPLE
# ===== The model =====
# Image: read-only layered filesystem + metadata (entrypoint, env, ports)
# Container: a process started from an image, with its own namespace + cgroups
# Registry: where images are stored (Docker Hub, GHCR, ECR)
# Dockerfile: declarative recipe to build an image
# ===== Hello, container =====
docker run --rm -it ubuntu:24.04 bash
# --rm remove the container when it exits
# -it interactive + tty
# inside: apt update; exit
# ===== A tiny app image =====
# Dockerfile
# syntax=docker/dockerfile:1
FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
FROM base AS runtime
COPY src ./src
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "src/server.js"]
# Build + run:
docker build -t shop:dev .
docker run --rm -p 3000:3000 --name shop shop:dev
# ===== Layered cache =====
# Each line creates a layer; identical layers are cached.
# Order matters: copy dependency manifests + install BEFORE copying source,
# so a code change doesn't bust the dependency cache.
# ===== Volumes + networks =====
docker volume create pg-data
docker network create app-net
docker run -d --name pg --network app-net -v pg-data:/var/lib/postgresql/data postgres:16
docker run -d --name api --network app-net -e PG_HOST=pg shop:dev
# ===== Compose (multi-container dev) =====
# compose.yaml
services:
api:
build: .
ports: ['3000:3000']
depends_on: [db]
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: dev
volumes: ['db:/var/lib/postgresql/data']
volumes: { db: {} }
docker compose up
docker compose down -v
# ===== When Docker wins =====
# - Reproducible dev environments
# - 'Build once, run anywhere' deploys
# - Multi-service local stacks (compose)
# - Sandboxed CI builds
# ===== When Docker hurts =====
# - Single-binary apps where a container adds little
# - GPU / kernel-driver dependent workloads (need extra config)
# - Very thin Lambda/Edge runtimes where cold-start matters
# ===== Patterns to internalise =====
# - Multi-stage builds: build artifacts in one stage, copy into a tiny final image
# - Non-root user in production images
# - HEALTHCHECK + STOPSIGNAL so orchestrators handle lifecycle cleanly
# - Pin digests in production (image@sha256:...)
# ===== Pitfalls =====
# - Running everything as root inside the container
# - 'latest' tag in production deploys
# - Mounting the whole repo into a container in CI (massive cache invalidation)
# - Forgetting EXPOSE / health probe -> orchestrator can't tell when ready
Why it matters
Docker reduces "works on my machine" to a verifiable artifact. Image as code, layered cache for speed, compose for local stacks, and a tiny runtime image for prod. Once the multi-stage Dockerfile + compose loop is reflex, deploys stop being a guessing game.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
# Containers = lightweight processes with their own filesystem. docker run -it alpine shTry it Yourself »
Exercise
Print the installed Docker version.
docker
Nine characters; starts with --.
Discussion
Loading…