feat(skills): add research-summarizer and docker-development agent skills
research-summarizer (product-team/): - Structured research summarization for papers, articles, reports - Slash commands: /research:summarize, /research:compare, /research:cite - Python tools: extract_citations.py (5 citation formats), format_summary.py (6 templates) - References: summary-templates.md, citation-formats.md docker-development (engineering/): - Dockerfile optimization, compose orchestration, container security - Slash commands: /docker:optimize, /docker:compose, /docker:security - Python tools: dockerfile_analyzer.py (15 rules), compose_validator.py (best practices) - References: dockerfile-best-practices.md, compose-patterns.md Both skills include .claude-plugin/plugin.json and follow POWERFUL tier conventions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
282
engineering/docker-development/references/compose-patterns.md
Normal file
282
engineering/docker-development/references/compose-patterns.md
Normal file
@@ -0,0 +1,282 @@
|
||||
# Docker Compose Patterns Reference
|
||||
|
||||
## Production-Ready Patterns
|
||||
|
||||
### Web App + Database + Cache
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "3000:3000"
|
||||
env_file:
|
||||
- .env
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
||||
interval: 30s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
mem_limit: 512m
|
||||
cpus: 1.0
|
||||
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
env_file:
|
||||
- .env.db
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- backend
|
||||
mem_limit: 256m
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
command: redis-server --maxmemory 64mb --maxmemory-policy allkeys-lru
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- backend
|
||||
mem_limit: 128m
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
backend:
|
||||
internal: true
|
||||
```
|
||||
|
||||
### Key Patterns
|
||||
- **Healthchecks on every service** — enables depends_on with condition
|
||||
- **Named volumes** — data persists across container recreation
|
||||
- **Explicit networks** — backend is internal (no external access)
|
||||
- **env_file** — secrets not in compose file
|
||||
- **Resource limits** — prevent runaway containers
|
||||
|
||||
---
|
||||
|
||||
## Development Override Pattern
|
||||
|
||||
### docker-compose.yml (base — production-like)
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
### docker-compose.override.yml (dev — auto-loaded)
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
target: development
|
||||
volumes:
|
||||
- .:/app # Bind mount for hot reload
|
||||
- /app/node_modules # Preserve container node_modules
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- DEBUG=true
|
||||
ports:
|
||||
- "9229:9229" # Debug port
|
||||
restart: "no"
|
||||
```
|
||||
|
||||
### Usage
|
||||
```bash
|
||||
# Development (auto-loads override)
|
||||
docker compose up
|
||||
|
||||
# Production (skip override)
|
||||
docker compose -f docker-compose.yml up -d
|
||||
|
||||
# Explicit profiles
|
||||
docker compose --profile dev up
|
||||
docker compose --profile prod up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Network Isolation Pattern
|
||||
|
||||
```yaml
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
networks:
|
||||
- frontend
|
||||
|
||||
app:
|
||||
build: .
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
networks:
|
||||
- backend
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
networks:
|
||||
- backend
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
# External traffic reaches nginx and app
|
||||
backend:
|
||||
internal: true
|
||||
# DB and Redis only reachable by app
|
||||
```
|
||||
|
||||
### Why This Matters
|
||||
- Database and cache are **not accessible from outside**
|
||||
- Only nginx and app handle external traffic
|
||||
- Lateral movement limited if one container is compromised
|
||||
|
||||
---
|
||||
|
||||
## Worker + Queue Pattern
|
||||
|
||||
```yaml
|
||||
services:
|
||||
api:
|
||||
build:
|
||||
context: .
|
||||
target: runtime
|
||||
command: uvicorn main:app --host 0.0.0.0 --port 8000
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
rabbitmq:
|
||||
condition: service_healthy
|
||||
|
||||
worker:
|
||||
build:
|
||||
context: .
|
||||
target: runtime
|
||||
command: celery -A tasks worker --loglevel=info
|
||||
depends_on:
|
||||
rabbitmq:
|
||||
condition: service_healthy
|
||||
|
||||
scheduler:
|
||||
build:
|
||||
context: .
|
||||
target: runtime
|
||||
command: celery -A tasks beat --loglevel=info
|
||||
depends_on:
|
||||
rabbitmq:
|
||||
condition: service_healthy
|
||||
|
||||
rabbitmq:
|
||||
image: rabbitmq:3.13-management-alpine
|
||||
ports:
|
||||
- "15672:15672" # Management UI (dev only)
|
||||
healthcheck:
|
||||
test: ["CMD", "rabbitmq-diagnostics", "check_running"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Logging Configuration
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
tag: "{{.Name}}/{{.ID}}"
|
||||
```
|
||||
|
||||
### Why
|
||||
- **max-size** prevents disk exhaustion
|
||||
- **max-file** rotates logs automatically
|
||||
- Default Docker logging has NO size limit — production servers can run out of disk
|
||||
|
||||
---
|
||||
|
||||
## Environment Variable Patterns
|
||||
|
||||
### .env.example (committed to repo)
|
||||
```env
|
||||
# Database
|
||||
DATABASE_URL=postgres://user:password@db:5432/appname
|
||||
POSTGRES_USER=user
|
||||
POSTGRES_PASSWORD=changeme
|
||||
POSTGRES_DB=appname
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://redis:6379/0
|
||||
|
||||
# Application
|
||||
SECRET_KEY=changeme-generate-a-real-secret
|
||||
NODE_ENV=production
|
||||
LOG_LEVEL=info
|
||||
|
||||
# External Services (BYOK)
|
||||
# SMTP_HOST=
|
||||
# SMTP_PORT=587
|
||||
# AWS_ACCESS_KEY_ID=
|
||||
# AWS_SECRET_ACCESS_KEY=
|
||||
```
|
||||
|
||||
### Variable Substitution in Compose
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
image: myapp:${APP_VERSION:-latest}
|
||||
environment:
|
||||
- LOG_LEVEL=${LOG_LEVEL:-info}
|
||||
- PORT=${PORT:-3000}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting Checklist
|
||||
|
||||
| Symptom | Likely Cause | Fix |
|
||||
|---------|-------------|-----|
|
||||
| Container exits immediately | CMD/ENTRYPOINT crashes, missing env vars | Check logs: `docker compose logs service` |
|
||||
| Port already in use | Another service or host process on same port | Change host port: `"3001:3000"` |
|
||||
| Volume permissions denied | Container user doesn't own mounted path | Match UID/GID or use named volumes |
|
||||
| Build cache not working | COPY . . invalidates cache early | Reorder: copy deps first, then source |
|
||||
| depends_on doesn't wait | No healthcheck condition | Add `condition: service_healthy` |
|
||||
| Container OOM killed | No memory limit or limit too low | Set appropriate `mem_limit` |
|
||||
| Network connectivity issues | Wrong network or service name | Services communicate by service name within shared network |
|
||||
@@ -0,0 +1,235 @@
|
||||
# Dockerfile Best Practices Reference
|
||||
|
||||
## Layer Optimization
|
||||
|
||||
### The Golden Rule
|
||||
Every `RUN`, `COPY`, and `ADD` instruction creates a new layer. Fewer layers = smaller image.
|
||||
|
||||
### Combine Related Commands
|
||||
```dockerfile
|
||||
# Bad — 3 layers
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y curl git
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Good — 1 layer
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends curl git && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
```
|
||||
|
||||
### Order Layers by Change Frequency
|
||||
```dockerfile
|
||||
# Least-changing layers first
|
||||
COPY package.json package-lock.json ./ # Changes rarely
|
||||
RUN npm ci # Changes when deps change
|
||||
COPY . . # Changes every build
|
||||
RUN npm run build # Changes every build
|
||||
```
|
||||
|
||||
### Use .dockerignore
|
||||
```
|
||||
.git
|
||||
node_modules
|
||||
__pycache__
|
||||
*.pyc
|
||||
.env
|
||||
.env.*
|
||||
dist
|
||||
build
|
||||
*.log
|
||||
.DS_Store
|
||||
.vscode
|
||||
.idea
|
||||
coverage
|
||||
.pytest_cache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Base Image Selection
|
||||
|
||||
### Size Comparison (approximate)
|
||||
|
||||
| Base | Size | Use Case |
|
||||
|------|------|----------|
|
||||
| `scratch` | 0MB | Static binaries (Go, Rust) |
|
||||
| `distroless/static` | 2MB | Static binaries with CA certs |
|
||||
| `alpine` | 7MB | Minimal Linux, shell access |
|
||||
| `distroless/base` | 20MB | Dynamic binaries (C/C++) |
|
||||
| `debian-slim` | 80MB | When you need glibc + apt |
|
||||
| `ubuntu` | 78MB | Full Ubuntu ecosystem |
|
||||
| `python:3.12-slim` | 130MB | Python apps (production) |
|
||||
| `node:20-alpine` | 130MB | Node.js apps |
|
||||
| `golang:1.22` | 800MB | Go build stage only |
|
||||
| `python:3.12` | 900MB | Never use in production |
|
||||
| `node:20` | 1000MB | Never use in production |
|
||||
|
||||
### When to Use Alpine
|
||||
- Small image size matters
|
||||
- No dependency on glibc (musl works)
|
||||
- Willing to handle occasional musl-related issues
|
||||
- Not running Python with C extensions that need glibc
|
||||
|
||||
### When to Use Slim
|
||||
- Need glibc compatibility
|
||||
- Python with compiled C extensions (numpy, pandas)
|
||||
- Fewer musl compatibility issues
|
||||
- Still much smaller than full images
|
||||
|
||||
### When to Use Distroless
|
||||
- Maximum security (no shell, no package manager)
|
||||
- Compiled/static binaries
|
||||
- Don't need debugging access inside container
|
||||
- Production-only (not development)
|
||||
|
||||
---
|
||||
|
||||
## Multi-Stage Builds
|
||||
|
||||
### Why Multi-Stage
|
||||
- Build tools and source code stay out of production image
|
||||
- Final image contains only runtime artifacts
|
||||
- Dramatically reduces image size and attack surface
|
||||
|
||||
### Naming Stages
|
||||
```dockerfile
|
||||
FROM golang:1.22 AS builder # Named stage
|
||||
FROM alpine:3.19 AS runtime # Named stage
|
||||
COPY --from=builder /app /app # Reference by name
|
||||
```
|
||||
|
||||
### Selective Copy
|
||||
```dockerfile
|
||||
# Only copy the built artifact — nothing else
|
||||
COPY --from=builder /app/server /server
|
||||
COPY --from=builder /app/config.yaml /config.yaml
|
||||
# Don't COPY --from=builder /app/ /app/ (copies source code too)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Hardening
|
||||
|
||||
### Run as Non-Root
|
||||
```dockerfile
|
||||
# Create user
|
||||
RUN groupadd -r appgroup && useradd -r -g appgroup -s /sbin/nologin appuser
|
||||
|
||||
# Set ownership
|
||||
COPY --chown=appuser:appgroup . .
|
||||
|
||||
# Switch user (after all root-requiring operations)
|
||||
USER appuser
|
||||
```
|
||||
|
||||
### Secret Management
|
||||
```dockerfile
|
||||
# Bad — secret baked into layer
|
||||
ENV API_KEY=sk-12345
|
||||
|
||||
# Good — BuildKit secret mount (never in layer)
|
||||
RUN --mount=type=secret,id=api_key \
|
||||
export API_KEY=$(cat /run/secrets/api_key) && \
|
||||
./configure --api-key=$API_KEY
|
||||
```
|
||||
|
||||
Build with:
|
||||
```bash
|
||||
docker build --secret id=api_key,src=./api_key.txt .
|
||||
```
|
||||
|
||||
### Read-Only Filesystem
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
services:
|
||||
app:
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp
|
||||
- /var/run
|
||||
```
|
||||
|
||||
### Drop Capabilities
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE # Only if binding to ports < 1024
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Build Performance
|
||||
|
||||
### BuildKit Cache Mounts
|
||||
```dockerfile
|
||||
# Cache pip downloads across builds
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Cache apt downloads
|
||||
RUN --mount=type=cache,target=/var/cache/apt \
|
||||
apt-get update && apt-get install -y curl
|
||||
```
|
||||
|
||||
### Parallel Builds
|
||||
```dockerfile
|
||||
# These stages build in parallel when using BuildKit
|
||||
FROM node:20-alpine AS frontend
|
||||
COPY frontend/ .
|
||||
RUN npm ci && npm run build
|
||||
|
||||
FROM golang:1.22 AS backend
|
||||
COPY backend/ .
|
||||
RUN go build -o server
|
||||
|
||||
FROM alpine:3.19
|
||||
COPY --from=frontend /dist /static
|
||||
COPY --from=backend /server /server
|
||||
```
|
||||
|
||||
### Enable BuildKit
|
||||
```bash
|
||||
export DOCKER_BUILDKIT=1
|
||||
docker build .
|
||||
|
||||
# Or in daemon.json
|
||||
{ "features": { "buildkit": true } }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Health Checks
|
||||
|
||||
### HTTP Service
|
||||
```dockerfile
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:8000/health || exit 1
|
||||
```
|
||||
|
||||
### Without curl (using wget)
|
||||
```dockerfile
|
||||
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:8000/health || exit 1
|
||||
```
|
||||
|
||||
### TCP Check
|
||||
```dockerfile
|
||||
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
|
||||
CMD nc -z localhost 8000 || exit 1
|
||||
```
|
||||
|
||||
### PostgreSQL
|
||||
```dockerfile
|
||||
HEALTHCHECK --interval=10s --timeout=5s --retries=5 \
|
||||
CMD pg_isready -U postgres || exit 1
|
||||
```
|
||||
|
||||
### Redis
|
||||
```dockerfile
|
||||
HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
|
||||
CMD redis-cli ping | grep PONG || exit 1
|
||||
```
|
||||
Reference in New Issue
Block a user