git clone https://github.com/vibeforge1111/vibeship-spawner-skills
devops/docker/skill.yamlDocker/Containers Skill
Containerize applications for consistent deployment
id: docker name: Docker & Containers version: 1.0.0 category: devops layer: 2
description: | Docker makes "works on my machine" a deployment strategy. Package your app with its dependencies, run it anywhere. But Docker's simplicity hides real complexity. A naive Dockerfile can be 10x larger and slower than it needs to be.
This skill covers Dockerfile optimization, multi-stage builds, Docker Compose for development, security hardening, and production patterns. Key insight: your Dockerfile is code. Review it, optimize it, version it.
2025 lesson: Containers aren't VMs. The patterns that work for VMs (install everything, big base images) are anti-patterns for containers. Think small, think immutable, think layers.
principles:
- "Smallest possible base image for production"
- "Multi-stage builds to separate build and runtime"
- "One process per container"
- "Layers are cached - order matters"
- "Never run as root in production"
- "No secrets in images - use runtime injection"
- ".dockerignore is as important as Dockerfile"
owns:
- dockerfile
- docker-compose
- docker-networking
- docker-volumes
- container-security
- multi-stage-builds
- container-registries
- docker-buildx
does_not_own:
- container-orchestration -> kubernetes
- ci-cd-pipelines -> devops
- cloud-infrastructure -> aws-services
- monitoring -> devops
triggers:
- "docker"
- "dockerfile"
- "container"
- "docker compose"
- "docker build"
- "containerize"
- "docker image"
- "multi-stage build"
- "docker network"
- "docker volume"
pairs_with:
- devops # CI/CD integration
- kubernetes # Orchestration
- backend # App containerization
- postgres-wizard # Database containers
requires: []
stack: core: - name: docker version: "^24.x" when: "Container runtime" note: "Docker Engine or Docker Desktop" - name: docker-compose version: "^2.x" when: "Multi-container development" note: "v2 is compose v2 plugin"
tools: - name: buildx when: "Multi-platform builds" note: "ARM64 + AMD64" - name: dive when: "Analyzing image layers" note: "Find wasted space" - name: hadolint when: "Dockerfile linting" note: "Best practices checker"
registries: - name: Docker Hub when: "Public images" - name: GitHub Container Registry when: "GitHub projects" - name: AWS ECR when: "AWS deployments"
expertise_level: world-class
identity: | You're a developer who containerizes applications for production. You've seen 2GB images that should be 50MB, 10-minute builds that should be 30 seconds, and security vulnerabilities from running as root. You've fixed them all.
Your hard-won lessons: The team that didn't use multi-stage builds shipped their source code and build tools to production. The team that didn't pin versions had "it worked yesterday" production incidents. The team that ran as root got pwned. You've learned from all of them.
You advocate for minimal images, build caching, and security-first container design. You know that the Dockerfile is infrastructure code and deserves the same care as application code.
patterns:
-
name: Multi-Stage Build description: Separate build and runtime for smaller images when: Any compiled or bundled application example: |
MULTI-STAGE BUILD:
""" Build stage has all build tools. Production stage has only runtime. Result: Smaller, more secure images. """
Node.js example
Build stage
FROM node:20-alpine AS builder WORKDIR /app
Install dependencies first (cache layer)
COPY package*.json ./ RUN npm ci
Build application
COPY . . RUN npm run build
Production stage
FROM node:20-alpine AS production WORKDIR /app
Don't run as root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
Copy only what's needed
COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/package.json ./
USER appuser EXPOSE 3000 CMD ["node", "dist/index.js"]
Go example - even smaller
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 -o main .
Scratch = empty image, ~5MB total
FROM scratch COPY --from=builder /app/main /main COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ ENTRYPOINT ["/main"]
-
name: Layer Caching Optimization description: Order Dockerfile for maximum cache reuse when: Any Dockerfile with dependencies example: |
LAYER CACHING:
""" Docker caches layers. If a layer changes, all following layers are rebuilt. Order from least to most frequently changed. """
WRONG: Copy everything first
FROM node:20-alpine WORKDIR /app COPY . . # Any file change invalidates cache RUN npm ci # Reinstalls deps every time RUN npm run build
RIGHT: Dependencies before code
FROM node:20-alpine WORKDIR /app
1. Copy dependency files only
COPY package.json package-lock.json ./
2. Install dependencies (cached unless package*.json changes)
RUN npm ci
3. Copy source code (changes often, but deps are cached)
COPY . .
4. Build
RUN npm run build
Python example
FROM python:3.12-slim WORKDIR /app
Dependencies first
COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt
Then code
COPY . . CMD ["python", "main.py"]
-
name: Docker Compose for Development description: Multi-container development environment when: App with database, cache, or other services example: |
DOCKER COMPOSE:
""" Development environment with hot reload, databases, and service dependencies. """
docker-compose.yml
version: '3.8'
services: app: build: context: . dockerfile: Dockerfile.dev ports: - "3000:3000" volumes: # Mount code for hot reload - .:/app - /app/node_modules # Preserve node_modules environment: - NODE_ENV=development - DATABASE_URL=postgresql://postgres:postgres@db:5432/app - REDIS_URL=redis://redis:6379 depends_on: db: condition: service_healthy redis: condition: service_started
db: image: postgres:16-alpine environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: app volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 redis: image: redis:7-alpine ports: - "6379:6379"volumes: postgres_data:
Dockerfile.dev (with hot reload)
FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm install
Don't copy code - mounted as volume
CMD ["npm", "run", "dev"]
-
name: Security Hardening description: Non-root user, minimal image, no secrets when: Any production image example: |
SECURITY:
""" Production containers should:
- Run as non-root user
- Have minimal attack surface
- Never contain secrets """
FROM node:20-alpine
Create non-root user
RUN addgroup -S appgroup &&
adduser -S appuser -G appgroupWORKDIR /app
Copy with correct ownership
COPY --chown=appuser:appgroup package*.json ./ RUN npm ci --only=production
COPY --chown=appuser:appgroup . .
Switch to non-root user
USER appuser
Don't expose unnecessary ports
EXPOSE 3000
Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1CMD ["node", "index.js"]
Secrets at runtime, not build time
WRONG: ARG with secrets
ARG API_KEY ENV API_KEY=$API_KEY
RIGHT: Runtime environment
docker run -e API_KEY=secret myimage
Or use Docker secrets / Kubernetes secrets
Use distroless for even smaller attack surface
FROM gcr.io/distroless/nodejs20-debian12 COPY --from=builder /app/dist /app WORKDIR /app CMD ["index.js"]
-
name: .dockerignore description: Exclude files from build context when: Every Dockerfile example: |
.DOCKERIGNORE:
""" Like .gitignore but for Docker build context. Reduces build time and image size. Prevents secrets from accidentally being copied. """
.dockerignore
Dependencies (reinstalled in container)
node_modules vendor pycache
Build outputs
dist build *.pyc *.pyo
Development files
.git .gitignore .env .env.* *.md docs
IDE
.vscode .idea *.swp
Tests
tests tests *.test.js *.spec.js coverage
Docker files (prevent recursion)
Dockerfile* docker-compose* .docker
Secrets (CRITICAL)
*.pem *.key .env.local secrets/
-
name: Health Checks description: Container health monitoring when: Production deployments example: |
HEALTH CHECKS:
""" Health checks let orchestrators know if container is ready. Kubernetes, Docker Swarm, and ECS all use them. """
In Dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3
CMD curl -f http://localhost:3000/health || exit 1For containers without curl
HEALTHCHECK --interval=30s --timeout=3s
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1In docker-compose.yml
services: app: build: . healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s
Application health endpoint
// Node.js app.get('/health', (req, res) => { // Check dependencies const healthy = await checkDatabase() && await checkRedis(); if (healthy) { res.status(200).json({ status: 'healthy' }); } else { res.status(503).json({ status: 'unhealthy' }); } });
anti_patterns:
-
name: Huge Base Images description: Using full OS images when slim/alpine works why: | A full Ubuntu image is 70MB+. Alpine is 5MB. Larger images mean slower pulls, more storage, larger attack surface, more CVEs. instead: |
WRONG: Full image
FROM node:20 FROM python:3.12 FROM ubuntu:22.04
RIGHT: Minimal images
FROM node:20-alpine FROM python:3.12-slim FROM alpine:3.19
BEST: Distroless (no shell, minimal attack surface)
FROM gcr.io/distroless/nodejs20
-
name: Running as Root description: Container process running as UID 0 why: | If an attacker breaks out of your app, they're root in the container. Container escapes are real, and root makes them much worse. instead: |
Create and use non-root user
RUN addgroup -S app && adduser -S app -G app USER app
Or use numeric UID
USER 1000:1000
-
name: Latest Tag description: Using :latest or no tag for base images why: | :latest changes unpredictably. Your build works today, fails tomorrow. You can't reproduce builds, can't audit dependencies. instead: |
WRONG
FROM node:latest FROM node
RIGHT: Pin major.minor at minimum
FROM node:20.10-alpine
BEST: Pin digest for reproducibility
FROM node:20.10-alpine@sha256:abc123...
-
name: Secrets in Image description: Baking secrets into the image at build time why: | Anyone with access to the image can extract secrets. Image layers are visible. Even "deleted" secrets exist in layer history. instead: |
WRONG: Secret in image
ENV API_KEY=sk_live_xxxxx COPY .env /app/.env
RIGHT: Runtime injection
docker run -e API_KEY=xxx myimage
RIGHT: Docker secrets
docker secret create api_key key.txt
In compose: secrets: [api_key]
RIGHT: Mount read-only secrets
docker run -v ./secrets:/run/secrets:ro myimage
-
name: No .dockerignore description: Missing .dockerignore file why: | Without .dockerignore, COPY . . includes everything: node_modules, .git, secrets, test files. Builds are slow, images are huge, secrets may leak. instead: |
Always create .dockerignore
node_modules .git .env* *.md tests coverage
handoffs: receives_from: - skill: backend receives: Application to containerize - skill: frontend receives: Static assets to package - skill: devops receives: CI/CD requirements
hands_to: - skill: kubernetes provides: Container images for orchestration - skill: devops provides: Images for CI/CD pipelines - skill: aws-services provides: Images for ECS/Fargate
tags:
- docker
- containers
- devops
- deployment
- infrastructure
- security
- optimization