Docker
Core Concepts
| Term | What it is |
|---|---|
| Image | Immutable read-only template (filesystem + metadata) built from a Dockerfile |
| Container | A running (or stopped) instance of an image — isolated process with its own filesystem layer |
| Registry | Where images are stored/shared (Docker Hub, GHCR, ECR) |
| Layer | Each Dockerfile instruction adds a cached, reusable filesystem layer |
| Volume | Persistent storage that outlives a container’s lifecycle |
Containers share the host kernel (unlike VMs) — that’s why they start in milliseconds and use far less overhead.
Dockerfile Basics
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
ENV NODE_ENV=production
USER node
CMD ["node", "server.js"]
| Instruction | Purpose |
|---|---|
FROM | base image |
WORKDIR | set cwd for subsequent instructions |
COPY / ADD | copy files in (prefer COPY; ADD also unpacks archives/fetches URLs) |
RUN | execute a command at build time (new layer) |
ENV | set environment variables |
EXPOSE | document the listening port (doesn’t publish it) |
CMD | default command when the container starts (overridable) |
ENTRYPOINT | fixed executable; CMD becomes its default args |
USER | drop root for the running process |
Build And Run
docker build -t myapp:latest . # build image from Dockerfile in cwd
docker build -t myapp:latest -f Dockerfile.prod .
docker run myapp:latest # run, foreground
docker run -d --name web -p 8080:3000 myapp:latest # detached, named, port-mapped
docker run -it myapp:latest sh # interactive shell in a new container
docker run --rm myapp:latest # auto-remove container on exit
docker run -e API_KEY=secret myapp:latest # pass an env var
-p host:container maps ports; -e sets env vars; -d detaches; --rm cleans up automatically — useful for one-off/debug runs.
Image Management
docker images # list local images
docker pull nginx:latest # download an image
docker push myrepo/myapp:1.0 # upload to a registry
docker tag myapp:latest myrepo/myapp:1.0
docker rmi myapp:latest # remove an image
docker image prune # remove dangling (untagged) images
docker image prune -a # remove all unused images
docker history myapp:latest # inspect layer sizes
Container Lifecycle
docker ps # running containers
docker ps -a # all containers, including stopped
docker start <name> # start a stopped container
docker stop <name> # graceful stop (SIGTERM, then SIGKILL after timeout)
docker restart <name> # stop + start
docker rm <name> # remove a stopped container
docker rm -f <name> # force remove (even if running)
docker exec -it <name> sh # shell into a running container
docker logs -f <name> # follow logs
Volumes And Bind Mounts
docker volume create mydata
docker run -v mydata:/app/data myapp # named volume (Docker-managed storage)
docker run -v $(pwd):/app myapp # bind mount (host path, great for local dev)
docker run --mount type=volume,src=mydata,dst=/app/data myapp # explicit long-form syntax
docker volume ls
docker volume rm mydata
Named volumes persist and are portable across containers; bind mounts tie directly to a host path — ideal for live-reloading source code during development.
Networking
docker network ls
docker network create mynet
docker run --network mynet --name api myapp
docker run --network mynet --name web nginx # `web` can reach `api` by container name (DNS)
docker network inspect mynet
By default, containers on the same user-defined network resolve each other by container/service name — no manual IP wiring needed.
Docker Compose
# docker-compose.yml
services:
web:
build: .
ports:
- "8080:3000"
environment:
- NODE_ENV=production
depends_on:
- db
db:
image: postgres:16
volumes:
- dbdata:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=secret
volumes:
dbdata:
docker compose up -d # start all services, detached
docker compose down # stop and remove containers/network
docker compose logs -f web # follow logs for one service
docker compose exec web sh # shell into a running service
docker compose build --no-cache # rebuild ignoring layer cache
Multi Stage Builds
# build stage — has full toolchain, produces artifacts
FROM node:20 AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build
# runtime stage — only ships the built output, smaller final image
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --omit=dev
CMD ["node", "dist/server.js"]
Multi-stage builds keep the final image lean — compilers, dev dependencies, and source maps used only during build never ship to production.
Debugging Containers
docker inspect <name> # full JSON metadata (env, mounts, network, etc.)
docker logs --tail 100 <name> # last 100 log lines
docker stats # live CPU/memory usage per container
docker exec -it <name> sh # poke around inside a running container
docker events # stream real-time Docker daemon events
docker system df # disk usage summary (images, containers, volumes)
docker system prune -a --volumes # reclaim disk space — destructive, review first