Ai docker
install
source · Clone the upstream repo
git clone https://github.com/wpank/ai
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/wpank/ai "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/devops/docker" ~/.claude/skills/wpank-ai-docker && rm -rf "$T"
manifest:
skills/devops/docker/SKILL.mdsource content
Docker
Container optimization, security hardening, multi-stage builds, and production deployment patterns.
Installation
OpenClaw / Moltbot / Clawbot
npx clawhub@latest install docker
NEVER
- NEVER use
tag - Always use specific version tags for reproducibility:latest - NEVER run as root - Create and use non-root users with specific UID/GID
- NEVER store secrets in images - Use Docker secrets, env vars at runtime, or secret managers
- NEVER skip .dockerignore - Always create comprehensive .dockerignore to reduce build context
- NEVER install dev dependencies in production - Use multi-stage builds to separate concerns
- NEVER leave package manager caches - Clean in the same RUN layer (
)npm ci && npm cache clean --force - NEVER use
(v1) - Usedocker-compose
(v2) with plugin syntaxdocker compose
Quick Reference
Multi-Stage Build Pattern
# Stage 1: Dependencies FROM node:20-alpine AS deps WORKDIR /app COPY package*.json ./ RUN npm ci --only=production && npm cache clean --force # Stage 2: Build FROM node:20-alpine AS build WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build && npm prune --production # Stage 3: Production FROM node:20-alpine AS runtime RUN addgroup -g 1001 -S nodejs && adduser -S app -u 1001 WORKDIR /app COPY --from=deps --chown=app:nodejs /app/node_modules ./node_modules COPY --from=build --chown=app:nodejs /app/dist ./dist COPY --from=build --chown=app:nodejs /app/package*.json ./ USER app EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1 CMD ["node", "dist/index.js"]
Security-Hardened Container
FROM node:20-alpine # Create non-root user with specific UID/GID RUN addgroup -g 1001 -S appgroup && \ adduser -S appuser -u 1001 -G appgroup WORKDIR /app # Copy with ownership COPY --chown=appuser:appgroup package*.json ./ RUN npm ci --only=production && npm cache clean --force COPY --chown=appuser:appgroup . . # Switch to non-root user USER 1001 # At runtime: --cap-drop=ALL --read-only --security-opt=no-new-privileges
Production Docker Compose
services: app: build: context: . target: production depends_on: db: condition: service_healthy networks: - frontend - backend healthcheck: test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s deploy: resources: limits: cpus: '0.5' memory: 512M reservations: cpus: '0.25' memory: 256M restart: unless-stopped db: image: postgres:16-alpine environment: POSTGRES_DB_FILE: /run/secrets/db_name POSTGRES_USER_FILE: /run/secrets/db_user POSTGRES_PASSWORD_FILE: /run/secrets/db_password secrets: - db_name - db_user - db_password volumes: - postgres_data:/var/lib/postgresql/data networks: - backend healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s timeout: 5s retries: 5 networks: frontend: driver: bridge backend: driver: bridge internal: true # No external access volumes: postgres_data: secrets: db_name: file: ./secrets/db_name.txt db_user: file: ./secrets/db_user.txt db_password: file: ./secrets/db_password.txt
.dockerignore Template
# Dependencies node_modules .pnpm-store # Build outputs dist build .next out # Development .git .gitignore *.md LICENSE docs/ # IDE .vscode .idea *.swp *.swo # Testing coverage .nyc_output *.test.js *.spec.js __tests__ # Environment .env* !.env.example # Docker Dockerfile* docker-compose* .docker # Misc .DS_Store *.log tmp
Common Patterns
Build Cache Optimization
# Mount build cache for faster rebuilds FROM node:20-alpine AS deps WORKDIR /app COPY package*.json ./ RUN --mount=type=cache,target=/root/.npm \ npm ci --only=production
Build-Time Secrets
# BuildKit secrets (never stored in image layers) FROM alpine RUN --mount=type=secret,id=api_key \ API_KEY=$(cat /run/secrets/api_key) && \ # Use API_KEY for build process echo "Key loaded"
# Build with secret docker build --secret id=api_key,src=./api_key.txt .
Multi-Architecture Builds
# Create builder for multi-arch docker buildx create --name multiarch --use # Build for multiple platforms docker buildx build --platform linux/amd64,linux/arm64 \ -t myapp:latest --push .
Development Override
# docker-compose.override.yml (auto-loaded in dev) services: app: build: target: development volumes: - .:/app - /app/node_modules # Anonymous volume for node_modules environment: - NODE_ENV=development - DEBUG=app:* ports: - "9229:9229" # Debug port command: npm run dev
Framework-Specific Patterns
Next.js Production
FROM node:20-alpine AS deps WORKDIR /app COPY package*.json ./ RUN npm ci FROM node:20-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . ENV NEXT_TELEMETRY_DISABLED=1 RUN npm run build FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001 COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static USER nextjs EXPOSE 3000 ENV PORT=3000 CMD ["node", "server.js"]
Python FastAPI
FROM python:3.12-slim AS base ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 FROM base AS deps WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt FROM base AS runtime WORKDIR /app RUN useradd -m -u 1001 appuser COPY --from=deps /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages COPY --chown=appuser:appuser . . USER appuser EXPOSE 8000 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Go Distroless
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 FROM gcr.io/distroless/static-debian12 COPY --from=builder /app/server /server USER nonroot:nonroot EXPOSE 8080 ENTRYPOINT ["/server"]
Troubleshooting
Image Size Issues
# Analyze layers docker history myapp:latest --no-trunc # Use dive for detailed analysis dive myapp:latest # Check actual size docker images myapp --format "{{.Size}}"
Common causes:
- Dev dependencies in production → Use
npm ci --only=production - Large base images → Use Alpine or distroless
- Uncleaned caches → Clean in same RUN layer
- Unnecessary files → Improve .dockerignore
Build Performance
# Enable BuildKit (faster, better caching) export DOCKER_BUILDKIT=1 # Build with cache export docker build --cache-from myapp:latest -t myapp:new . # Check build cache docker buildx du
Debugging Containers
# Shell into running container docker exec -it <container> sh # Shell into failed container docker run -it --entrypoint sh myapp:latest # Check container logs docker logs -f <container> # Inspect container details docker inspect <container>
Review Checklist
Dockerfile
- Multi-stage build separates build/runtime
- Dependencies copied before source (layer caching)
- Specific base image tags (no
):latest - Non-root user with specific UID/GID
- Package cache cleaned in same RUN layer
- Health check configured
- .dockerignore is comprehensive
Security
- Runs as non-root user
- No secrets in image layers
- Minimal base image (Alpine/distroless)
- Read-only filesystem where possible
- Capabilities dropped at runtime
Docker Compose
- Health checks with
condition: service_healthy - Resource limits defined
- Internal networks for backend services
- Secrets via files, not environment
- Restart policy configured