Docker
Docker is the standard for packaging applications into portable, reproducible containers. It is used for deploying microservices, running local dev environments, and building CI/CD pipelines, and ML model serving is almost always shipped as a container image.
Table of Contents
- What is Docker?
- Core Concepts
- Installation & Setup
- Dockerfile
- Images & Containers
- Volumes & Networking
- Docker Compose
- Multi-Stage Builds
- Docker in CI/CD
- Security Best Practices
- Docker in 2026
- Cheat Sheet
What is Docker?
Docker packages an application and all its dependencies (libraries, runtime, config) into a container: a lightweight, isolated process that runs identically on any machine.
Without Docker: With Docker:
"It works on my machine!" → "It works everywhere."
Docker vs VMs
| Feature | Virtual Machine | Docker Container |
|---|---|---|
| Isolation | Full OS | Process-level |
| Startup time | Minutes | Milliseconds |
| Size | GBs | MBs |
| Overhead | High (hypervisor) | Minimal |
| Portability | Medium | High |
Containers share the host OS kernel: they're not VMs. They use Linux namespaces and cgroups for isolation.
Core Concepts
The Big Picture
Dockerfile → docker build → Image → docker run → Container
↕
Docker Registry
(Docker Hub, ECR, GCR)
| Concept | Description |
|---|---|
| Dockerfile | Blueprint for building an image (text file with instructions) |
| Image | Read-only snapshot of a filesystem + metadata. Immutable. |
| Container | A running instance of an image. Ephemeral by default. |
| Registry | Remote store for images (Docker Hub, AWS ECR, GitHub GHCR) |
| Layer | Each Dockerfile instruction creates a cached, reusable layer |
| Volume | Persistent storage that survives container restarts |
| Network | Virtual network allowing containers to communicate |
Installation & Setup
Linux
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER # run Docker without sudo
newgrp docker
Verify
docker version
docker run hello-world
Docker Desktop (Mac/Windows)
Download from docker.com/products/docker-desktop. Includes Docker Engine + Compose + Kubernetes.
Dockerfile
A Dockerfile is a script of instructions that Docker executes layer by layer to build an image.
Basic Structure
# Base image
FROM python:3.12-slim
# Set working directory
WORKDIR /app
# Copy dependency files first (cache optimization)
COPY requirements.txt .
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Expose port (documentation only, doesn't publish)
EXPOSE 8000
# Default command to run
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Key Instructions
| Instruction | Purpose | Example |
|---|---|---|
FROM |
Base image | FROM python:3.12-slim |
WORKDIR |
Set working directory | WORKDIR /app |
COPY |
Copy files from host | COPY src/ /app/src/ |
ADD |
Like COPY + supports URLs/tar | ADD archive.tar.gz /app/ |
RUN |
Execute command in new layer | RUN apt-get install -y curl |
ENV |
Set environment variable | ENV PORT=8080 |
ARG |
Build-time variable | ARG VERSION=1.0 |
EXPOSE |
Document port | EXPOSE 5432 |
CMD |
Default run command | CMD ["python", "app.py"] |
ENTRYPOINT |
Fixed command (CMD appended) | ENTRYPOINT ["gunicorn"] |
USER |
Switch to non-root user | USER appuser |
HEALTHCHECK |
Container health check | See below |
LABEL |
Metadata | LABEL version="1.0" |
CMD vs ENTRYPOINT
# CMD: overridable default command
CMD ["python", "app.py"]
# docker run myimage python other.py ← overrides CMD
# ENTRYPOINT: fixed executable, CMD becomes default args
ENTRYPOINT ["python"]
CMD ["app.py"]
# docker run myimage other.py ← runs "python other.py"
.dockerignore
Like .gitignore: prevents unnecessary files from being sent to the Docker build context:
.git
__pycache__
*.pyc
.env
node_modules
*.log
.DS_Store
Images & Containers
Building Images
# Build from Dockerfile in current directory
docker build -t myapp:1.0 .
# Build with specific Dockerfile
docker build -f Dockerfile.prod -t myapp:prod .
# Build with build args
docker build --build-arg VERSION=2.0 -t myapp:2.0 .
# Build for specific platform (cross-compile)
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest .
Image Management
# List local images
docker images
docker image ls
# Pull from registry
docker pull python:3.12-slim
docker pull ghcr.io/myorg/myapp:latest
# Push to registry
docker tag myapp:1.0 myrepo/myapp:1.0
docker push myrepo/myapp:1.0
# Inspect image layers
docker history myapp:1.0
docker inspect myapp:1.0
# Remove image
docker rmi myapp:1.0
docker image prune -a # remove all unused images
Running Containers
# Run interactively
docker run -it ubuntu bash
# Run in background (detached)
docker run -d --name my-postgres postgres:16
# Run with environment variables
docker run -d \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=mydb \
--name postgres \
postgres:16
# Run with port mapping (host:container)
docker run -d -p 8080:80 nginx
# Run with volume mount
docker run -d \
-v /host/data:/container/data \
myapp:1.0
# Run with resource limits
docker run -d \
--memory="512m" \
--cpus="1.5" \
myapp:1.0
# Auto-remove when stopped
docker run --rm -it myapp:1.0 bash
Container Management
# List running containers
docker ps
# List all containers (including stopped)
docker ps -a
# Stop / start / restart
docker stop my-app
docker start my-app
docker restart my-app
# Remove container
docker rm my-app
docker rm -f my-app # force remove running container
# View logs
docker logs my-app
docker logs -f my-app # follow (tail -f)
docker logs --tail 100 my-app
# Execute command in running container
docker exec -it my-app bash
docker exec my-app cat /etc/hosts
# Copy files to/from container
docker cp ./config.yaml my-app:/app/config.yaml
docker cp my-app:/app/logs/app.log ./logs/
# Inspect container
docker inspect my-app
# Stats (CPU, memory, network)
docker stats
# Clean up everything stopped
docker container prune
Volumes & Networking
Volumes
Volumes are the preferred way to persist data. They're managed by Docker and survive container restarts/removal.
# Create volume
docker volume create pgdata
# Use volume in container
docker run -d \
-v pgdata:/var/lib/postgresql/data \
postgres:16
# Bind mount (maps host directory)
docker run -v $(pwd)/data:/app/data myapp
# List volumes
docker volume ls
# Inspect volume
docker volume inspect pgdata
# Remove volume
docker volume rm pgdata
docker volume prune # remove all unused
Volumes vs Bind Mounts vs tmpfs:
| Type | Use Case | Managed By |
|---|---|---|
| Volume | Production data persistence | Docker |
| Bind mount | Dev: sync host code into container | Host OS |
| tmpfs | Sensitive temp data (in memory only) | Memory |
Networking
# List networks
docker network ls
# Create network
docker network create my-network
# Connect containers on same network
docker run -d --name db --network my-network postgres:16
docker run -d --name app --network my-network myapp:1.0
# app can reach db via hostname "db"
# Inspect network
docker network inspect my-network
# Connect/disconnect running container
docker network connect my-network my-app
docker network disconnect my-network my-app
Built-in Networks:
| Network | Description |
|---|---|
bridge |
Default. Containers communicate via IP. |
host |
Container shares host network stack. |
none |
No networking. |
| Custom bridge | User-defined. Containers reach each other by name. |
Docker Compose
Docker Compose defines and runs multi-container applications from a single compose.yaml file (the older docker-compose.yml name is still read). Use the Compose v2 plugin, docker compose (space); the standalone Python docker-compose v1 binary is end-of-life.
Example: Full-Stack App
# compose.yaml
# No top-level "version:" key: it is obsolete in the Compose Specification
# and current Compose prints a warning and ignores it.
services:
# PostgreSQL database
db:
image: postgres:16
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: secret
POSTGRES_DB: appdb
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser"]
interval: 10s
timeout: 5s
retries: 5
# Redis cache
redis:
image: redis:7-alpine
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
# Backend API
api:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://appuser:secret@db:5432/appdb
REDIS_URL: redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
volumes:
- ./backend:/app # dev hot-reload
restart: unless-stopped
# Frontend
frontend:
build: ./frontend
ports:
- "3000:3000"
environment:
VITE_API_URL: http://localhost:8000
depends_on:
- api
volumes:
pgdata:
Compose Commands
# Start all services (detached)
docker compose up -d
# Start and rebuild images
docker compose up -d --build
# Stop all services
docker compose down
# Stop and remove volumes
docker compose down -v
# View logs
docker compose logs -f
docker compose logs -f api
# Scale a service
docker compose up -d --scale api=3
# Run one-off command
docker compose run --rm api python manage.py migrate
# Exec into service
docker compose exec api bash
# Status
docker compose ps
# Restart single service
docker compose restart api
# Pull latest images
docker compose pull
Multi-Stage Builds
Multi-stage builds keep production images small by separating build-time dependencies from runtime.
Python Example
# Stage 1: Build
FROM python:3.12 AS builder
# Install dependencies into a virtualenv that is easy to copy as one directory
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Stage 2: Production (lean)
FROM python:3.12-slim AS production
# Copy only the installed packages from builder (no compilers, no build cache)
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
WORKDIR /app
COPY . .
# Create non-root user
RUN useradd -r -s /bin/false appuser
USER appuser
EXPOSE 8000
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Node.js Example
# Stage 1: Dependencies
FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
# Stage 2: Build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 3: Production
FROM node:22-alpine AS production
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package.json .
EXPOSE 3000
CMD ["node", "dist/index.js"]
Docker in CI/CD
GitHub Actions Example
name: Build and Push
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
Security Best Practices
# 1. Use specific image tags, not :latest
FROM python:3.12.3-slim-bookworm # not FROM python:latest
# 2. Run as non-root user
RUN useradd --create-home --shell /bin/bash appuser
USER appuser
# 3. Minimize layers and clean up in same RUN step
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*
# 4. Don't copy secrets into images
# Use --secret or runtime environment variables
# 5. Use COPY over ADD (more explicit)
COPY requirements.txt .
# 6. Set read-only filesystem
# docker run --read-only myapp (with tmpfs for /tmp)
# 7. Scan images for vulnerabilities
# docker scout cves myapp:latest
# trivy image myapp:latest
Secrets Management
# Build-time secret (never stored in image layer)
docker build --secret id=mysecret,src=./secret.txt .
# In Dockerfile - use secret without storing it
RUN --mount=type=secret,id=mysecret \
cat /run/secrets/mysecret | pip install --extra-index-url ...
Docker in 2026
What's Changed
| Feature | Status in 2026 |
|---|---|
| Docker Scout | Built-in vulnerability scanning in Docker Desktop |
| Buildx / BuildKit | Default build backend; multi-platform builds standard |
| Docker Init | docker init generates Dockerfile + Compose automatically |
| Testcontainers | Standard for integration testing with real containers |
| Wasm support | Docker Desktop shipped Wasm workloads as a beta feature, since announced for deprecation; treat it as experimental |
| OCI standard | All major runtimes (containerd, podman) are OCI-compliant |
Alternatives to Know
| Tool | Description |
|---|---|
| Podman | Daemonless, rootless Docker alternative (Red Hat) |
| nerdctl | Docker-compatible CLI for containerd |
| Finch | AWS's open-source container CLI for macOS |
| Buildpacks | Build images from source without Dockerfiles (Cloud Native Buildpacks) |
Cheat Sheet
# === IMAGES ===
docker build -t name:tag . # Build image
docker pull image:tag # Pull image
docker push repo/image:tag # Push image
docker images # List images
docker rmi image:tag # Remove image
docker image prune -a # Remove all unused images
# === CONTAINERS ===
docker run -d -p host:container name # Run detached with port
docker run -it image bash # Interactive shell
docker run --rm image command # Auto-remove on exit
docker ps # Running containers
docker ps -a # All containers
docker stop/start/restart name # Lifecycle
docker rm name # Remove container
docker logs -f name # Tail logs
docker exec -it name bash # Shell into running container
docker stats # Resource usage
# === VOLUMES ===
docker volume create vol # Create volume
docker volume ls # List volumes
docker run -v vol:/path image # Attach volume
docker run -v $(pwd):/path image # Bind mount
# === NETWORKING ===
docker network create net # Create network
docker network ls # List networks
docker run --network net image # Use network
# === COMPOSE ===
docker compose up -d # Start all services
docker compose up -d --build # Start and rebuild
docker compose down # Stop all
docker compose down -v # Stop + remove volumes
docker compose logs -f [service] # Logs
docker compose exec service bash # Shell into service
docker compose ps # Status
# === CLEANUP ===
docker system prune # Remove all unused
docker system prune -a --volumes # Nuclear cleanup
docker container prune # Remove stopped containers
docker volume prune # Remove unused volumes
docker image prune -a # Remove unused images