Containerization
Docker
Container platform for building, shipping, and running applications - Dockerfile, Compose, networking, and best practices
Docker is the industry-standard container platform that packages applications and their dependencies into lightweight, portable containers.
| Concept | Description |
|---|
| Image | Read-only template containing application code, runtime, libraries, and configuration |
| Container | Running instance of an image with its own filesystem, networking, and process space |
| Dockerfile | Text file with instructions to build an image |
| Registry | Storage and distribution service for images (Docker Hub, ECR, GCR, GHCR) |
| Volume | Persistent storage mechanism that outlives container lifecycle |
| Network | Virtual network for container-to-container communication |
# Stage 1: Dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
# Stage 2: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN corepack enable && pnpm build
# Stage 3: Production
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 appuser
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
USER appuser
EXPOSE 3000
CMD ["node", "dist/index.js"]
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server
FROM scratch
COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/server"]
# Bad: Cache invalidated on every code change
COPY . .
RUN npm install
# Good: Dependencies cached separately
COPY package.json package-lock.json ./
RUN npm ci --production
COPY . .
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
target: runner
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --maxmemory 256mb
volumes:
- redis-data:/data
ports:
- "6379:6379"
volumes:
pgdata:
redis-data:
| Network Type | Description | Use Case |
|---|
| bridge | Default isolated network | Container-to-container on same host |
| host | Shares host network stack | Performance-critical, port conflicts possible |
| overlay | Multi-host networking | Docker Swarm / multi-node clusters |
| none | No networking | Security-sensitive isolated containers |
# Custom networks in Compose
services:
app:
networks:
- frontend
- backend
db:
networks:
- backend
nginx:
networks:
- frontend
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # No external access
services:
app:
volumes:
# Named volume (managed by Docker)
- app-data:/app/data
# Bind mount (host directory)
- ./config:/app/config:ro
# tmpfs (in-memory, not persisted)
- type: tmpfs
target: /app/tmp
tmpfs:
size: 100m
volumes:
app-data:
driver: local
| Practice | Description |
|---|
| Non-root user | Always run as non-root: USER appuser |
| Read-only filesystem | Use read_only: true in Compose |
| Minimal base images | Use alpine, slim, or distroless |
| No secrets in images | Use runtime env vars or Docker secrets |
| Image scanning | Scan images with Trivy, Snyk, or Docker Scout |
| Pin versions | Use specific image tags, not latest |
.dockerignore | Exclude unnecessary files from build context |
node_modules
.git
.env
*.md
docker-compose*.yml
.github
coverage
.next
dist
# Build
docker build -t myapp:latest .
docker build --target builder -t myapp:debug .
# Run
docker run -d --name myapp -p 3000:3000 --env-file .env myapp:latest
docker run --rm -it myapp:latest /bin/sh
# Inspect
docker logs -f --tail 100 myapp
docker exec -it myapp /bin/sh
docker stats myapp
docker inspect myapp
# Cleanup
docker system prune -a --volumes # Remove all unused data
docker image prune -a # Remove unused images
docker volume prune # Remove unused volumes
# Compose
docker compose up -d
docker compose down -v
docker compose logs -f app
docker compose exec app /bin/sh
docker compose build --no-cache
| Strategy | Impact |
|---|
| Multi-stage builds | 50-90% size reduction |
| Alpine base images | ~5MB vs ~100MB for full images |
| Distroless images | Minimal attack surface, ~2MB base |
.dockerignore | Exclude dev files from build context |
| Minimize layers | Combine RUN commands with && |
| Remove caches | rm -rf /var/cache/apk/* after install |