mirror of
https://github.com/duthaho/claudekit.git
synced 2026-09-03 16:50:51 +03:00
feat: adding new skills, including testing patterns and methodologies, along with bundled resources for better usability.
This commit is contained in:
@@ -1,70 +1,216 @@
|
||||
---
|
||||
name: docker
|
||||
description: >
|
||||
Use this skill whenever containerizing applications, writing Dockerfiles, configuring Docker Compose, or optimizing container images. Trigger on keywords like Docker, Dockerfile, container, docker-compose, multi-stage build, image, or container registry. Also applies when setting up local development environments with containers, debugging container networking, or preparing applications for container-based deployment in CI/CD pipelines.
|
||||
---
|
||||
|
||||
# Docker
|
||||
|
||||
## Description
|
||||
|
||||
Docker containerization including Dockerfiles, compose, and best practices.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Containerizing applications
|
||||
- Local development environments
|
||||
- CI/CD pipelines
|
||||
|
||||
## When NOT to Use
|
||||
|
||||
- Serverless-only deployments where containers are not part of the architecture (e.g., pure AWS Lambda, Cloudflare Workers)
|
||||
- Local development without containers where native tooling is preferred
|
||||
- Simple scripts or utilities that do not need isolation or reproducible environments
|
||||
|
||||
---
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### Multi-stage Dockerfile (Node.js)
|
||||
### 1. Multi-Stage Builds
|
||||
|
||||
Multi-stage builds separate build-time dependencies from the runtime image, producing
|
||||
smaller, more secure containers.
|
||||
|
||||
#### Python (builder + slim runtime)
|
||||
|
||||
```dockerfile
|
||||
# Build stage
|
||||
FROM node:20-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
# ---- Build stage ----
|
||||
FROM python:3.12-slim AS builder
|
||||
|
||||
# Production stage
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
EXPOSE 3000
|
||||
CMD ["node", "dist/index.js"]
|
||||
```
|
||||
WORKDIR /build
|
||||
|
||||
### Python Dockerfile
|
||||
# Install build-only dependencies (gcc, etc.) needed by some wheels
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends gcc libpq-dev && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
```dockerfile
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
|
||||
|
||||
# ---- Runtime stage ----
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies first (caching)
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
# Copy only the installed packages from the builder
|
||||
COPY --from=builder /install /usr/local
|
||||
|
||||
COPY . .
|
||||
# Copy application code
|
||||
COPY src/ ./src/
|
||||
COPY main.py .
|
||||
|
||||
# Run as non-root
|
||||
RUN addgroup --system app && adduser --system --ingroup app app
|
||||
USER app
|
||||
|
||||
EXPOSE 8000
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
||||
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
|
||||
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
#### Node.js (build + nginx/alpine)
|
||||
|
||||
```dockerfile
|
||||
# ---- Build stage ----
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies first for layer caching
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN corepack enable && pnpm install --frozen-lockfile
|
||||
|
||||
# Copy source and build
|
||||
COPY tsconfig.json ./
|
||||
COPY src/ ./src/
|
||||
COPY public/ ./public/
|
||||
RUN pnpm build
|
||||
|
||||
# ---- Runtime stage (static site served by nginx) ----
|
||||
FROM nginx:1.27-alpine
|
||||
|
||||
# Copy custom nginx config
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Copy built assets from builder
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
|
||||
# Run as non-root
|
||||
RUN chown -R nginx:nginx /usr/share/nginx/html && \
|
||||
chown -R nginx:nginx /var/cache/nginx && \
|
||||
chown -R nginx:nginx /var/log/nginx && \
|
||||
touch /var/run/nginx.pid && \
|
||||
chown -R nginx:nginx /var/run/nginx.pid
|
||||
USER nginx
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/ || exit 1
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
```
|
||||
|
||||
#### Node.js (API server with alpine runtime)
|
||||
|
||||
```dockerfile
|
||||
# ---- Build stage ----
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN corepack enable && pnpm install --frozen-lockfile
|
||||
|
||||
COPY tsconfig.json ./
|
||||
COPY src/ ./src/
|
||||
RUN pnpm build
|
||||
|
||||
# Prune dev dependencies for a lighter production node_modules
|
||||
RUN pnpm prune --prod
|
||||
|
||||
# ---- Runtime stage ----
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder /app/package.json ./
|
||||
|
||||
RUN addgroup -S app && adduser -S app -G app
|
||||
USER app
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
|
||||
|
||||
CMD ["node", "dist/index.js"]
|
||||
```
|
||||
|
||||
#### Go (build + scratch)
|
||||
|
||||
```dockerfile
|
||||
# ---- Build stage ----
|
||||
FROM golang:1.22-alpine AS builder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Download dependencies first for caching
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
# Copy source and build a static binary
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server
|
||||
|
||||
# ---- Runtime stage (scratch = empty image) ----
|
||||
FROM scratch
|
||||
|
||||
# Copy CA certificates for HTTPS calls
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
|
||||
# Copy the static binary
|
||||
COPY --from=builder /app/server /server
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["/server"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Docker Compose for Development
|
||||
|
||||
A full-featured Compose file with services, volumes, networks, healthchecks, and
|
||||
environment variable management.
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: builder # Use builder stage for dev with hot-reload
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://user:pass@db:5432/app
|
||||
NODE_ENV: development
|
||||
DATABASE_URL: postgresql://user:pass@db:5432/app
|
||||
REDIS_URL: redis://redis:6379
|
||||
env_file:
|
||||
- .env.local # Local overrides (gitignored)
|
||||
volumes:
|
||||
- .:/app # Bind-mount source for hot-reload
|
||||
- /app/node_modules # Anonymous volume to preserve node_modules
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
networks:
|
||||
- backend
|
||||
restart: unless-stopped
|
||||
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
@@ -72,23 +218,442 @@ services:
|
||||
POSTGRES_USER: user
|
||||
POSTGRES_PASSWORD: pass
|
||||
POSTGRES_DB: app
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U user -d app"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
networks:
|
||||
- backend
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
networks:
|
||||
- backend
|
||||
|
||||
worker:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.worker
|
||||
environment:
|
||||
DATABASE_URL: postgresql://user:pass@db:5432/app
|
||||
REDIS_URL: redis://redis:6379
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
networks:
|
||||
- backend
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
|
||||
networks:
|
||||
backend:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Layer Caching
|
||||
|
||||
Docker caches each layer. If a layer has not changed, every layer after it is also
|
||||
cached. Order instructions from least-frequently-changed to most-frequently-changed.
|
||||
|
||||
#### Optimal instruction order
|
||||
|
||||
```dockerfile
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 1. System dependencies (rarely change)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends curl && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 2. Dependency manifests (change when adding packages)
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# 3. Application code (changes most often)
|
||||
COPY . .
|
||||
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]
|
||||
```
|
||||
|
||||
#### .dockerignore patterns
|
||||
|
||||
Always include a `.dockerignore` to keep the build context small and avoid leaking
|
||||
secrets into layers.
|
||||
|
||||
```
|
||||
# Version control
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Dependencies (rebuilt inside container)
|
||||
node_modules
|
||||
__pycache__
|
||||
*.pyc
|
||||
.venv
|
||||
venv
|
||||
|
||||
# Build output
|
||||
dist
|
||||
build
|
||||
*.egg-info
|
||||
|
||||
# IDE and editor files
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Environment and secrets
|
||||
.env
|
||||
.env.*
|
||||
*.pem
|
||||
*.key
|
||||
|
||||
# Docker files (not needed in context)
|
||||
Dockerfile*
|
||||
docker-compose*
|
||||
.dockerignore
|
||||
|
||||
# Documentation and misc
|
||||
README.md
|
||||
CHANGELOG.md
|
||||
LICENSE
|
||||
docs/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Health Checks
|
||||
|
||||
Health checks let Docker (and orchestrators like Compose/Swarm/K8s) know when a
|
||||
container is actually ready to serve traffic.
|
||||
|
||||
#### HTTP health check with curl
|
||||
|
||||
```dockerfile
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD curl -f http://localhost:8000/health || exit 1
|
||||
```
|
||||
|
||||
#### HTTP health check with wget (alpine images without curl)
|
||||
|
||||
```dockerfile
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
|
||||
```
|
||||
|
||||
#### TCP port check (for non-HTTP services)
|
||||
|
||||
```dockerfile
|
||||
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
||||
CMD nc -z localhost 5432 || exit 1
|
||||
```
|
||||
|
||||
#### Python-native check (no extra binaries needed)
|
||||
|
||||
```dockerfile
|
||||
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
||||
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
|
||||
```
|
||||
|
||||
**Parameter reference:**
|
||||
|
||||
| Parameter | Description | Default |
|
||||
|------------------|--------------------------------------------------|---------|
|
||||
| `--interval` | Time between checks | 30s |
|
||||
| `--timeout` | Max time for a single check | 30s |
|
||||
| `--start-period` | Grace period before checks count as failures | 0s |
|
||||
| `--retries` | Consecutive failures before marking unhealthy | 3 |
|
||||
|
||||
---
|
||||
|
||||
### 5. Security Hardening
|
||||
|
||||
#### Run as non-root user
|
||||
|
||||
```dockerfile
|
||||
# Debian/Ubuntu based images
|
||||
RUN addgroup --system app && adduser --system --ingroup app app
|
||||
USER app
|
||||
|
||||
# Alpine based images
|
||||
RUN addgroup -S app && adduser -S app -G app
|
||||
USER app
|
||||
```
|
||||
|
||||
#### Use minimal base images
|
||||
|
||||
| Base Image | Size | Use Case |
|
||||
|--------------------|---------|---------------------------------------|
|
||||
| `alpine` | ~5 MB | General minimal base |
|
||||
| `*-slim` | ~50 MB | Debian-based with fewer packages |
|
||||
| `distroless` | ~20 MB | Google's no-shell, no-package-manager |
|
||||
| `scratch` | 0 MB | Static binaries only (Go, Rust) |
|
||||
|
||||
```dockerfile
|
||||
# Distroless for Python
|
||||
FROM gcr.io/distroless/python3-debian12
|
||||
COPY --from=builder /app /app
|
||||
CMD ["main.py"]
|
||||
```
|
||||
|
||||
#### Never put secrets in image layers
|
||||
|
||||
```dockerfile
|
||||
# BAD - secret is baked into image history
|
||||
COPY .env /app/.env
|
||||
RUN echo "API_KEY=secret123" >> /app/.env
|
||||
|
||||
# GOOD - pass secrets at runtime
|
||||
CMD ["python", "main.py"]
|
||||
# docker run -e API_KEY=secret123 myapp
|
||||
# or docker run --env-file .env myapp
|
||||
```
|
||||
|
||||
#### Multi-stage to exclude build tools
|
||||
|
||||
Build tools (compilers, package managers, source code) stay in the builder stage
|
||||
and never reach the runtime image. This reduces attack surface and image size.
|
||||
|
||||
```dockerfile
|
||||
FROM node:20-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN corepack enable && pnpm install --frozen-lockfile
|
||||
COPY . .
|
||||
RUN pnpm build && pnpm prune --prod
|
||||
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
# Only the built output and production deps are copied
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
USER node
|
||||
CMD ["node", "dist/index.js"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. Environment Configuration
|
||||
|
||||
#### ARG vs ENV
|
||||
|
||||
| Directive | Available at | Persists in image | Use for |
|
||||
|-----------|-------------|-------------------|-----------------------------|
|
||||
| `ARG` | Build time | No | Build-time variables |
|
||||
| `ENV` | Build + run | Yes | Runtime configuration |
|
||||
|
||||
```dockerfile
|
||||
# ARG - only available during build
|
||||
ARG NODE_ENV=production
|
||||
ARG BUILD_VERSION=unknown
|
||||
|
||||
# ENV - available at build and runtime
|
||||
ENV NODE_ENV=${NODE_ENV}
|
||||
ENV APP_VERSION=${BUILD_VERSION}
|
||||
|
||||
# Build with: docker build --build-arg BUILD_VERSION=1.2.3 .
|
||||
```
|
||||
|
||||
#### .env files with Compose
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
# Single .env file
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
# Multiple files (later files override earlier ones)
|
||||
env_file:
|
||||
- .env.defaults
|
||||
- .env.local
|
||||
|
||||
# Inline environment variables (override env_file)
|
||||
environment:
|
||||
LOG_LEVEL: debug
|
||||
DEBUG: "true"
|
||||
```
|
||||
|
||||
#### Secrets management with Docker Compose
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
secrets:
|
||||
- db_password
|
||||
- api_key
|
||||
environment:
|
||||
DB_PASSWORD_FILE: /run/secrets/db_password
|
||||
|
||||
secrets:
|
||||
db_password:
|
||||
file: ./secrets/db_password.txt
|
||||
api_key:
|
||||
environment: API_KEY # Read from host environment
|
||||
```
|
||||
|
||||
Inside the container, secrets are mounted at `/run/secrets/<name>` as files.
|
||||
|
||||
---
|
||||
|
||||
### 7. Networking
|
||||
|
||||
#### Bridge networks for service isolation
|
||||
|
||||
```yaml
|
||||
services:
|
||||
frontend:
|
||||
build: ./frontend
|
||||
ports:
|
||||
- "3000:3000"
|
||||
networks:
|
||||
- frontend-net
|
||||
- backend-net # Can reach the API
|
||||
|
||||
api:
|
||||
build: ./api
|
||||
ports:
|
||||
- "8000:8000"
|
||||
networks:
|
||||
- backend-net # Reachable by frontend and workers
|
||||
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
networks:
|
||||
- backend-net # Only reachable by api and workers
|
||||
# No ports exposed to host
|
||||
|
||||
worker:
|
||||
build: ./worker
|
||||
networks:
|
||||
- backend-net
|
||||
|
||||
networks:
|
||||
frontend-net:
|
||||
driver: bridge
|
||||
backend-net:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
#### Service discovery
|
||||
|
||||
Within a Docker Compose network, services reach each other by **service name**
|
||||
as the hostname.
|
||||
|
||||
```python
|
||||
# In the api service, connect to db using its service name
|
||||
DATABASE_URL = "postgresql://user:pass@db:5432/app"
|
||||
|
||||
# In the frontend service, call the api by service name
|
||||
API_URL = "http://api:8000"
|
||||
```
|
||||
|
||||
#### Exposing ports
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
ports:
|
||||
- "3000:3000" # host:container, binds to 0.0.0.0
|
||||
- "127.0.0.1:3000:3000" # bind to localhost only (more secure)
|
||||
expose:
|
||||
- "3000" # expose to other containers only, not host
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Use multi-stage builds
|
||||
2. Order commands for cache efficiency
|
||||
3. Use .dockerignore
|
||||
4. Run as non-root user
|
||||
5. Use specific image tags
|
||||
1. **Use multi-stage builds** -- Separate build dependencies from the runtime
|
||||
image. The final image should contain only what is needed to run the
|
||||
application.
|
||||
|
||||
2. **Pin image tags** -- Use `node:20.11-alpine` or a digest instead of
|
||||
`node:latest` or `node:20`. Floating tags lead to unpredictable builds.
|
||||
|
||||
3. **Order instructions for cache efficiency** -- Copy dependency manifests and
|
||||
install dependencies before copying application code. This ensures that code
|
||||
changes do not invalidate the dependency layer cache.
|
||||
|
||||
4. **Use .dockerignore** -- Exclude `.git`, `node_modules`, `__pycache__`, `.env`
|
||||
files, and anything not needed inside the container to keep the build context
|
||||
small and avoid leaking secrets.
|
||||
|
||||
5. **Run as non-root** -- Add a `USER` instruction to run the process as an
|
||||
unprivileged user. Never run production containers as root.
|
||||
|
||||
6. **Combine RUN commands** -- Merge related `RUN` instructions with `&&` to
|
||||
reduce layers and always clean up apt/apk caches in the same layer that
|
||||
installs packages.
|
||||
|
||||
7. **Use COPY instead of ADD** -- `COPY` is explicit and predictable. `ADD` has
|
||||
implicit behaviors (tar extraction, URL fetching) that can surprise you.
|
||||
|
||||
8. **Set explicit HEALTHCHECK** -- Define health checks in the Dockerfile so
|
||||
orchestrators know when the container is ready. This prevents routing traffic
|
||||
to containers that are still starting up.
|
||||
|
||||
---
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- **Large images**: Use slim/alpine bases
|
||||
- **Cache busting**: Order COPY commands properly
|
||||
- **Root user**: Add USER instruction
|
||||
1. **Bloated images** -- Using full base images like `python:3.12` instead of
|
||||
`python:3.12-slim` adds hundreds of megabytes. Always prefer slim or alpine
|
||||
variants. Use multi-stage builds to exclude build tools.
|
||||
|
||||
2. **Cache invalidation by COPY order** -- Placing `COPY . .` before
|
||||
`RUN pip install` means every code change reinstalls all dependencies. Always
|
||||
copy the dependency manifest first, install, then copy the rest of the code.
|
||||
|
||||
3. **Running as root** -- Forgetting the `USER` instruction means the container
|
||||
process runs as root. If the application is compromised, the attacker has full
|
||||
control of the container filesystem.
|
||||
|
||||
4. **Secrets baked into layers** -- Using `COPY .env .` or `ARG` for secrets
|
||||
embeds them in the image layer history. Anyone with access to the image can
|
||||
extract them with `docker history`. Pass secrets at runtime via environment
|
||||
variables or Docker secrets.
|
||||
|
||||
5. **Missing .dockerignore** -- Without a `.dockerignore`, the entire directory
|
||||
(including `.git`, `node_modules`, `.env` files) is sent as build context.
|
||||
This slows builds, increases image size, and risks leaking credentials.
|
||||
|
||||
6. **Ignoring healthchecks in Compose** -- Using `depends_on` without
|
||||
`condition: service_healthy` means the dependent service starts as soon as
|
||||
the database container starts, not when the database is actually ready to
|
||||
accept connections. Always pair `depends_on` with healthchecks.
|
||||
|
||||
---
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `devops/github-actions` - CI/CD workflows for building and deploying Docker containers
|
||||
- `security/owasp` - Security best practices for container hardening and vulnerability scanning
|
||||
- `patterns/logging` — Container logging and log aggregation
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
# Dockerfile Best Practices Reference
|
||||
|
||||
Quick reference for writing efficient, secure, and maintainable Dockerfiles.
|
||||
|
||||
## Layer Ordering for Cache Optimization
|
||||
|
||||
Order instructions from least-frequently-changed to most-frequently-changed:
|
||||
|
||||
```dockerfile
|
||||
# 1. Base image (changes: rarely)
|
||||
FROM node:22-slim
|
||||
|
||||
# 2. System dependencies (changes: rarely)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 3. App dependency manifest (changes: sometimes)
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
|
||||
# 4. Install dependencies (changes: sometimes, cached if manifests unchanged)
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# 5. Copy source code (changes: frequently)
|
||||
COPY . .
|
||||
|
||||
# 6. Build step (changes: frequently)
|
||||
RUN pnpm build
|
||||
|
||||
# 7. Runtime config (changes: rarely, but placed last for clarity)
|
||||
EXPOSE 3000
|
||||
CMD ["node", "dist/server.js"]
|
||||
```
|
||||
|
||||
**Key rule**: If a layer changes, all subsequent layers are rebuilt. Separate dependency installation from source code copying.
|
||||
|
||||
## Multi-Stage Builds
|
||||
|
||||
Reduce final image size by separating build and runtime stages.
|
||||
|
||||
```
|
||||
+-------------------+ +-------------------+
|
||||
| Build Stage | | Runtime Stage |
|
||||
| | | |
|
||||
| - Full toolchain | ---> | - Minimal base |
|
||||
| - Dev deps | | - Only artifacts |
|
||||
| - Source code | | - No build tools |
|
||||
| - Build output | | - No source code |
|
||||
+-------------------+ +-------------------+
|
||||
~800 MB ~80 MB
|
||||
```
|
||||
|
||||
**Benefits**: Smaller images, faster deploys, reduced attack surface, no build tools in production.
|
||||
|
||||
## Base Image Selection
|
||||
|
||||
| Image | Size | Use Case | Security | Package Manager |
|
||||
|-------|------|----------|----------|-----------------|
|
||||
| **alpine** | ~5 MB | Small images, CLI tools | Good (small surface) | apk |
|
||||
| **slim** (Debian) | ~80 MB | Most apps (Python, Node) | Good | apt |
|
||||
| **distroless** | ~20 MB | Production, no shell needed | Excellent (no shell) | None |
|
||||
| **scratch** | 0 MB | Static Go/Rust binaries | Excellent (nothing) | None |
|
||||
| **full** (Debian) | ~300 MB | Build stages, debugging | Fair (large surface) | apt |
|
||||
|
||||
### Recommendations by Language
|
||||
|
||||
| Language | Build Stage | Runtime Stage |
|
||||
|----------|-------------|---------------|
|
||||
| **Python** | `python:3.12-slim` | `python:3.12-slim` or `distroless/python3` |
|
||||
| **Node.js** | `node:22-slim` | `node:22-slim` or `distroless/nodejs22` |
|
||||
| **Go** | `golang:1.23` | `scratch` or `distroless/static` |
|
||||
| **Rust** | `rust:1.83` | `scratch` or `distroless/cc` |
|
||||
| **Java** | `eclipse-temurin:21-jdk` | `eclipse-temurin:21-jre-alpine` |
|
||||
|
||||
## Instruction Best Practices
|
||||
|
||||
### RUN: Combine and Clean Up
|
||||
|
||||
```dockerfile
|
||||
# BAD: Multiple layers, leftover cache
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y curl
|
||||
RUN apt-get install -y git
|
||||
|
||||
# GOOD: Single layer, cache cleaned
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
```
|
||||
|
||||
### COPY: Be Specific
|
||||
|
||||
```dockerfile
|
||||
# BAD: Copies everything, including .git, node_modules, etc.
|
||||
COPY . .
|
||||
|
||||
# GOOD: Copy only what's needed (use .dockerignore too)
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
COPY src/ ./src/
|
||||
COPY tsconfig.json ./
|
||||
```
|
||||
|
||||
### .dockerignore Essentials
|
||||
|
||||
```
|
||||
.git
|
||||
node_modules
|
||||
__pycache__
|
||||
.env
|
||||
*.log
|
||||
dist
|
||||
.venv
|
||||
.pytest_cache
|
||||
coverage
|
||||
.DS_Store
|
||||
```
|
||||
|
||||
### USER: Don't Run as Root
|
||||
|
||||
```dockerfile
|
||||
# Create non-root user
|
||||
RUN groupadd -r appuser && useradd -r -g appuser -s /bin/false appuser
|
||||
USER appuser
|
||||
```
|
||||
|
||||
### HEALTHCHECK
|
||||
|
||||
```dockerfile
|
||||
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
|
||||
CMD curl -f http://localhost:3000/health || exit 1
|
||||
```
|
||||
|
||||
### ARG vs ENV
|
||||
|
||||
| Directive | Available at | Persists in image | Use for |
|
||||
|-----------|-------------|-------------------|---------|
|
||||
| `ARG` | Build time only | No | Build-time toggles, versions |
|
||||
| `ENV` | Build + runtime | Yes | App configuration |
|
||||
|
||||
```dockerfile
|
||||
ARG PYTHON_VERSION=3.12
|
||||
FROM python:${PYTHON_VERSION}-slim
|
||||
|
||||
ENV APP_ENV=production
|
||||
ENV PORT=8000
|
||||
```
|
||||
|
||||
## Security Checklist
|
||||
|
||||
| Practice | Command/Example |
|
||||
|----------|----------------|
|
||||
| Pin base image digests | `FROM node:22-slim@sha256:abc123...` |
|
||||
| Run as non-root | `USER appuser` |
|
||||
| No secrets in layers | Use `--mount=type=secret` or build args |
|
||||
| Scan for vulnerabilities | `docker scout cves`, `trivy image` |
|
||||
| Read-only filesystem | `docker run --read-only` |
|
||||
| Drop capabilities | `docker run --cap-drop ALL` |
|
||||
| Use `.dockerignore` | Exclude `.env`, `.git`, credentials |
|
||||
| Minimal base image | Use slim/distroless/scratch |
|
||||
|
||||
### Secrets at Build Time (BuildKit)
|
||||
|
||||
```dockerfile
|
||||
# Mount a secret file without baking it into a layer
|
||||
RUN --mount=type=secret,id=npm_token \
|
||||
NPM_TOKEN=$(cat /run/secrets/npm_token) \
|
||||
npm install
|
||||
|
||||
# Build command:
|
||||
# docker build --secret id=npm_token,src=.npmrc .
|
||||
```
|
||||
|
||||
## Image Size Reduction Checklist
|
||||
|
||||
1. Use multi-stage builds
|
||||
2. Choose slim/alpine/distroless base
|
||||
3. Combine RUN commands
|
||||
4. Remove package manager caches (`rm -rf /var/lib/apt/lists/*`)
|
||||
5. Use `.dockerignore`
|
||||
6. Don't install dev dependencies in runtime stage
|
||||
7. Remove unnecessary files after build
|
||||
8. Use `--no-install-recommends` with apt
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
| Pitfall | Impact | Fix |
|
||||
|---------|--------|-----|
|
||||
| `COPY . .` before `npm install` | No dependency caching | Copy lockfile first, install, then copy source |
|
||||
| Using `latest` tag | Non-reproducible builds | Pin specific version tags or digests |
|
||||
| Secrets in `ENV` or `COPY` | Leaked in image layers | Use BuildKit secrets mount |
|
||||
| Running as root | Security vulnerability | Add `USER` directive |
|
||||
| No `.dockerignore` | Bloated context, slow builds | Add and maintain `.dockerignore` |
|
||||
| Installing build tools in final stage | Bloated image | Use multi-stage; build in first stage |
|
||||
| Not using `--frozen-lockfile` | Non-deterministic installs | Always use lockfile flags |
|
||||
@@ -0,0 +1,93 @@
|
||||
# =============================================================================
|
||||
# Multi-Stage Node.js Dockerfile
|
||||
# Usage:
|
||||
# docker build -t myapp .
|
||||
# docker run -p 3000:3000 myapp
|
||||
# =============================================================================
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Stage 1: Install dependencies
|
||||
# ---------------------------------------------------------------------------
|
||||
FROM node:22-slim AS deps
|
||||
|
||||
# Enable corepack for pnpm support.
|
||||
RUN corepack enable
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy only package manifests first for dependency layer caching.
|
||||
# Dependencies are only reinstalled when these files change.
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
|
||||
# Install production and dev dependencies (dev deps needed for build step).
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Stage 2: Build the application
|
||||
# ---------------------------------------------------------------------------
|
||||
FROM node:22-slim AS builder
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy dependencies from the deps stage.
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY --from=deps /app/package.json /app/pnpm-lock.yaml ./
|
||||
|
||||
# Copy source code and config files needed for the build.
|
||||
COPY tsconfig.json ./
|
||||
COPY src/ ./src/
|
||||
# COPY public/ ./public/ # Uncomment for Next.js or static assets
|
||||
|
||||
# Build the application.
|
||||
RUN pnpm build
|
||||
|
||||
# Remove dev dependencies after build to reduce size.
|
||||
RUN pnpm prune --prod
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Stage 3: Production runtime
|
||||
# ---------------------------------------------------------------------------
|
||||
FROM node:22-slim AS runtime
|
||||
|
||||
# Run as non-root for security.
|
||||
RUN groupadd -r appuser && useradd -r -g appuser -d /app -s /bin/false appuser
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Set production environment.
|
||||
ENV NODE_ENV=production \
|
||||
PORT=3000
|
||||
|
||||
# Copy only production artifacts from the builder stage.
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/package.json ./
|
||||
|
||||
# For Next.js standalone output, use instead:
|
||||
# COPY --from=builder /app/.next/standalone ./
|
||||
# COPY --from=builder /app/.next/static ./.next/static
|
||||
# COPY --from=builder /app/public ./public
|
||||
|
||||
# Switch to non-root user.
|
||||
USER appuser
|
||||
|
||||
# Expose the application port.
|
||||
EXPOSE 3000
|
||||
|
||||
# Health check -- adjust the endpoint to match your app.
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD node -e "fetch('http://localhost:3000/health').then(r => { if (!r.ok) process.exit(1) })" || exit 1
|
||||
|
||||
# Run the application.
|
||||
CMD ["node", "dist/server.js"]
|
||||
|
||||
# For Next.js standalone:
|
||||
# CMD ["node", "server.js"]
|
||||
|
||||
# For NestJS:
|
||||
# CMD ["node", "dist/main.js"]
|
||||
|
||||
# For Express with ts-node (dev only, not recommended for production):
|
||||
# CMD ["npx", "ts-node", "src/server.ts"]
|
||||
@@ -0,0 +1,78 @@
|
||||
# =============================================================================
|
||||
# Multi-Stage Python Dockerfile
|
||||
# Usage:
|
||||
# docker build -t myapp .
|
||||
# docker run -p 8000:8000 myapp
|
||||
# =============================================================================
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Stage 1: Build dependencies
|
||||
# ---------------------------------------------------------------------------
|
||||
# Use slim for building - it has gcc and headers available via apt.
|
||||
FROM python:3.12-slim AS builder
|
||||
|
||||
# Prevent Python from writing .pyc files and enable unbuffered output.
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1
|
||||
|
||||
# Install build-time system dependencies (if any compiled packages need them).
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install Python dependencies into a virtual environment.
|
||||
# Copying requirements first enables Docker layer caching --
|
||||
# dependencies are only reinstalled when requirements.txt changes.
|
||||
COPY requirements.txt .
|
||||
RUN python -m venv /app/.venv \
|
||||
&& /app/.venv/bin/pip install --no-cache-dir --upgrade pip \
|
||||
&& /app/.venv/bin/pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Stage 2: Runtime
|
||||
# ---------------------------------------------------------------------------
|
||||
FROM python:3.12-slim AS runtime
|
||||
|
||||
# Prevent .pyc files and enable unbuffered output for logging.
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
# Install only runtime system dependencies (no build tools).
|
||||
# Add packages here if your app needs them at runtime (e.g., libpq for psycopg).
|
||||
# RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
# libpq5 \
|
||||
# && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create a non-root user for security.
|
||||
RUN groupadd -r appuser && useradd -r -g appuser -d /app -s /bin/false appuser
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the virtual environment from the builder stage.
|
||||
COPY --from=builder /app/.venv /app/.venv
|
||||
|
||||
# Copy application source code.
|
||||
COPY src/ ./src/
|
||||
|
||||
# Switch to non-root user.
|
||||
USER appuser
|
||||
|
||||
# Expose the application port.
|
||||
EXPOSE 8000
|
||||
|
||||
# Health check -- adjust the endpoint to match your app.
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
|
||||
|
||||
# Run the application.
|
||||
# For FastAPI/Uvicorn:
|
||||
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
|
||||
# For Django/Gunicorn:
|
||||
# CMD ["gunicorn", "src.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "4"]
|
||||
|
||||
# For a plain script:
|
||||
# CMD ["python", "-m", "src.main"]
|
||||
@@ -0,0 +1,100 @@
|
||||
# =============================================================================
|
||||
# Development Docker Compose
|
||||
# Usage:
|
||||
# docker compose -f docker-compose.dev.yaml up
|
||||
# docker compose -f docker-compose.dev.yaml down -v # remove volumes too
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# Application
|
||||
# ---------------------------------------------------------------------------
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: builder # Use the build stage for dev (includes dev deps)
|
||||
ports:
|
||||
- "${APP_PORT:-3000}:3000"
|
||||
environment:
|
||||
NODE_ENV: development
|
||||
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/app_dev
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
volumes:
|
||||
# Mount source code for hot-reload. Exclude node_modules.
|
||||
- .:/app
|
||||
- /app/node_modules
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
# Override CMD for development (hot-reload).
|
||||
command: ["pnpm", "dev"]
|
||||
# For Python:
|
||||
# command: ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "3000", "--reload"]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PostgreSQL
|
||||
# ---------------------------------------------------------------------------
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
ports:
|
||||
- "${POSTGRES_PORT:-5432}:5432"
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: app_dev
|
||||
volumes:
|
||||
# Persist data across restarts.
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
# Run init scripts on first start.
|
||||
# - ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Redis
|
||||
# ---------------------------------------------------------------------------
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "${REDIS_PORT:-6379}:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
# Persist data to disk every 60 seconds if at least 1 key changed.
|
||||
command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Optional: pgAdmin (database GUI)
|
||||
# ---------------------------------------------------------------------------
|
||||
# pgadmin:
|
||||
# image: dpage/pgadmin4:latest
|
||||
# ports:
|
||||
# - "5050:80"
|
||||
# environment:
|
||||
# PGADMIN_DEFAULT_EMAIL: admin@local.dev
|
||||
# PGADMIN_DEFAULT_PASSWORD: admin
|
||||
# depends_on:
|
||||
# - postgres
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Optional: Mailpit (email testing)
|
||||
# ---------------------------------------------------------------------------
|
||||
# mailpit:
|
||||
# image: axllent/mailpit:latest
|
||||
# ports:
|
||||
# - "8025:8025" # Web UI
|
||||
# - "1025:1025" # SMTP
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
Reference in New Issue
Block a user