# Docker Infrastructure Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Set up complete Docker-based development environment with PostgreSQL, Redis, Traefik, Authentik, FastAPI backend, and React frontend. **Architecture:** Multi-service Docker Compose setup with Traefik as reverse proxy, PostgreSQL for data, Redis for caching, Authentik for auth, FastAPI backend, and Vite React frontend. All services include health checks and persistent volumes. **Tech Stack:** Docker 24.0+, Docker Compose 2.20+, Make, Python 3.11+, Node.js 20+ --- ## File Structure ``` / ├── docker-compose.yml # All services orchestration ├── .env.example # Required environment variables ├── Makefile # Common commands ├── apps/ │ ├── api/ │ │ ├── Dockerfile # Multi-stage Python build │ │ └── pyproject.toml # Python dependencies (placeholder) │ └── web/ │ ├── Dockerfile # Multi-stage Node build │ └── package.json # Node dependencies (placeholder) └── data/ └── repos/ # Git repository storage volume ``` --- ## Task 1: Create Project Directory Structure **Files:** - Create: `apps/api/` - Create: `apps/web/` - Create: `data/repos/` - [ ] **Step 1: Create directory structure** ```bash mkdir -p apps/api apps/web data/repos ``` - [ ] **Step 2: Create placeholder files for Docker build context** Create `apps/api/pyproject.toml`: ```toml [project] name = "headquarter-api" version = "0.1.0" description = "Headquarter platform API" requires-python = ">=3.11" dependencies = [ "fastapi>=0.104.0", "uvicorn[standard]>=0.24.0", "sqlalchemy>=2.0.0", "asyncpg>=0.29.0", "alembic>=1.12.0", "pydantic>=2.5.0", "pydantic-settings>=2.1.0", "python-jose[cryptography]>=3.3.0", "python-multipart>=0.0.6", "httpx>=0.25.0", "structlog>=23.2.0", "cryptography>=41.0.0", ] [project.optional-dependencies] dev = [ "pytest>=7.4.0", "pytest-asyncio>=0.21.0", "mypy>=1.7.0", "ruff>=0.1.0", "httpx>=0.25.0", ] ``` Create `apps/web/package.json`: ```json { "name": "headquarter-web", "version": "0.1.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", "typecheck": "tsc --noEmit", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0" }, "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.20.0", "axios": "^1.6.0", "tailwindcss": "^3.3.0" }, "devDependencies": { "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", "@vitejs/plugin-react": "^4.2.0", "typescript": "^5.3.0", "vite": "^5.0.0", "eslint": "^8.55.0", "@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/parser": "^6.14.0", "autoprefixer": "^10.4.16", "postcss": "^8.4.32" } } ``` - [ ] **Step 3: Commit** ```bash git add apps/ data/ git commit -m "chore: create project directory structure" ``` --- ## Task 2: Create API Dockerfile **Files:** - Create: `apps/api/Dockerfile` - [ ] **Step 1: Write multi-stage API Dockerfile** ```dockerfile # Build stage FROM python:3.11-slim as builder WORKDIR /build # Install build dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ gcc \ libpq-dev \ && rm -rf /var/lib/apt/lists/* # Install Python dependencies COPY pyproject.toml . RUN pip install --no-cache-dir --user -e ".[dev]" # Production stage FROM python:3.11-slim # Create non-root user RUN groupadd -r appgroup && useradd -r -g appgroup appuser WORKDIR /app # Install runtime dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ libpq5 \ git \ && rm -rf /var/lib/apt/lists/* # Copy dependencies from builder COPY --from=builder /root/.local /home/appuser/.local ENV PATH=/home/appuser/.local/bin:$PATH # Copy application code COPY --chown=appuser:appgroup . . # Create directories for repo storage RUN mkdir -p /data/repos && chown -R appuser:appgroup /data/repos # Switch to non-root user USER appuser # Expose port EXPOSE 8000 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1 # Run the application CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] ``` - [ ] **Step 2: Commit** ```bash git add apps/api/Dockerfile git commit -m "feat: add multi-stage API Dockerfile" ``` --- ## Task 3: Create Web Frontend Dockerfile **Files:** - Create: `apps/web/Dockerfile` - Create: `apps/web/nginx.conf` - [ ] **Step 1: Write multi-stage Web Dockerfile** ```dockerfile # Build stage FROM node:20-alpine as builder WORKDIR /build # Copy package files COPY package.json package-lock.json* ./ # Install dependencies RUN npm ci # Copy source code COPY . . # Build production bundle RUN npm run build # Production stage FROM nginx:alpine # Create non-root user RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001 # Copy custom nginx config COPY nginx.conf /etc/nginx/conf.d/default.conf # Copy built assets from builder COPY --from=builder --chown=nextjs:nodejs /build/dist /usr/share/nginx/html # Create required directories RUN mkdir -p /var/cache/nginx /var/run && \ chown -R nextjs:nodejs /var/cache/nginx /var/run /usr/share/nginx/html # Switch to non-root user USER nextjs # Expose port EXPOSE 80 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1 # Start nginx CMD ["nginx", "-g", "daemon off;"] ``` - [ ] **Step 2: Write nginx configuration** ```nginx server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; # Enable gzip compression gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css application/json application/javascript text/xml application/xml; # Handle client-side routing location / { try_files $uri $uri/ /index.html; } # Cache static assets location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; } # Health check endpoint location /health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } } ``` - [ ] **Step 3: Commit** ```bash git add apps/web/Dockerfile apps/web/nginx.conf git commit -m "feat: add multi-stage web frontend Dockerfile with nginx" ``` --- ## Task 4: Create Docker Compose Configuration **Files:** - Create: `docker-compose.yml` - [ ] **Step 1: Write Docker Compose file** ```yaml version: '3.8' services: # PostgreSQL Database postgres: image: postgres:15-alpine container_name: hq-postgres environment: POSTGRES_USER: ${POSTGRES_USER:-headquarter} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-headquarter} POSTGRES_DB: ${POSTGRES_DB:-headquarter} volumes: - postgres_data:/var/lib/postgresql/data - ./init-scripts:/docker-entrypoint-initdb.d:ro ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-headquarter} -d ${POSTGRES_DB:-headquarter}"] interval: 10s timeout: 5s retries: 5 start_period: 10s networks: - backend restart: unless-stopped # Redis Cache redis: image: redis:7-alpine container_name: hq-redis command: redis-server --appendonly yes volumes: - redis_data:/data ports: - "6379:6379" healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 start_period: 5s networks: - backend restart: unless-stopped # Traefik Reverse Proxy traefik: image: traefik:v3.0 container_name: hq-traefik command: - "--api.dashboard=true" - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--ping=true" ports: - "80:80" - "443:443" - "8080:8080" # Dashboard volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./traefik:/etc/traefik:ro healthcheck: test: ["CMD", "traefik", "healthcheck"] interval: 10s timeout: 5s retries: 3 start_period: 5s networks: - frontend - backend restart: unless-stopped labels: - "traefik.enable=true" - "traefik.http.routers.traefik.rule=Host(`traefik.hq.local`)" - "traefik.http.routers.traefik.service=api@internal" - "traefik.http.routers.traefik.entrypoints=web" # Authentik - Authentication Server authentik-server: image: ghcr.io/goauthentik/server:2024.2 container_name: hq-authentik command: server environment: AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-change-me-in-production} AUTHENTIK_REDIS__HOST: redis AUTHENTIK_POSTGRESQL__HOST: postgres AUTHENTIK_POSTGRESQL__NAME: ${POSTGRES_DB:-headquarter} AUTHENTIK_POSTGRESQL__USER: ${POSTGRES_USER:-headquarter} AUTHENTIK_POSTGRESQL__PASSWORD: ${POSTGRES_PASSWORD:-headquarter} volumes: - ./authentik/media:/media - ./authentik/custom-templates:/templates ports: - "9000:9000" - "9443:9443" depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - backend - frontend restart: unless-stopped labels: - "traefik.enable=true" - "traefik.http.routers.authentik.rule=Host(`auth.hq.local`)" - "traefik.http.routers.authentik.entrypoints=web" - "traefik.http.services.authentik.loadbalancer.server.port=9000" # API Service api: build: context: ./apps/api dockerfile: Dockerfile container_name: hq-api environment: DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-headquarter}:${POSTGRES_PASSWORD:-headquarter}@postgres:5432/${POSTGRES_DB:-headquarter} REDIS_URL: redis://redis:6379/0 AUTHENTIK_URL: http://authentik-server:9000 AUTHENTIK_CLIENT_ID: ${AUTHENTIK_CLIENT_ID} AUTHENTIK_CLIENT_SECRET: ${AUTHENTIK_CLIENT_SECRET} JWT_SECRET: ${JWT_SECRET:-change-me-in-production} REPO_BASE_PATH: /data/repos volumes: - repo_data:/data/repos depends_on: postgres: condition: service_healthy redis: condition: service_healthy authentik-server: condition: service_started networks: - backend - frontend restart: unless-stopped labels: - "traefik.enable=true" - "traefik.http.routers.api.rule=Host(`api.hq.local`)" - "traefik.http.routers.api.entrypoints=web" - "traefik.http.services.api.loadbalancer.server.port=8000" # Web Frontend web: build: context: ./apps/web dockerfile: Dockerfile container_name: hq-web environment: VITE_API_URL: http://api.hq.local VITE_AUTH_URL: http://auth.hq.local depends_on: - api networks: - frontend restart: unless-stopped labels: - "traefik.enable=true" - "traefik.http.routers.web.rule=Host(`hq.local`) || Host(`www.hq.local`)" - "traefik.http.routers.web.entrypoints=web" - "traefik.http.services.web.loadbalancer.server.port=80" volumes: postgres_data: redis_data: repo_data: networks: frontend: driver: bridge backend: driver: bridge ``` - [ ] **Step 2: Commit** ```bash git add docker-compose.yml git commit -m "feat: add Docker Compose with all services" ``` --- ## Task 5: Create Environment Configuration Template **Files:** - Create: `.env.example` - [ ] **Step 1: Write environment template** ```bash # Database Configuration POSTGRES_USER=headquarter POSTGRES_PASSWORD=change-me-in-production POSTGRES_DB=headquarter # Redis Configuration REDIS_URL=redis://redis:6379/0 # Authentik Configuration AUTHENTIK_SECRET_KEY=change-me-in-production AUTHENTIK_CLIENT_ID=your-authentik-client-id AUTHENTIK_CLIENT_SECRET=your-authentik-client-secret AUTHENTIK_URL=http://auth.hq.local # JWT Configuration JWT_SECRET=change-me-in-production JWT_ALGORITHM=HS256 JWT_EXPIRATION_HOURS=24 # Application Configuration APP_ENV=development DEBUG=true LOG_LEVEL=info REPO_BASE_PATH=/data/repos # Frontend Configuration VITE_API_URL=http://api.hq.local VITE_AUTH_URL=http://auth.hq.local # Docker Configuration COMPOSE_PROJECT_NAME=headquarter DOCKER_NETWORK=headquarter_default ``` - [ ] **Step 2: Commit** ```bash git add .env.example git commit -m "docs: add environment configuration template" ``` --- ## Task 6: Create Makefile **Files:** - Create: `Makefile` - [ ] **Step 1: Write Makefile** ```makefile .PHONY: help up down logs migrate test lint clean build # Default target help: @echo "Headquarter Development Commands" @echo "================================" @echo "make up - Start all services" @echo "make down - Stop all services" @echo "make logs - View service logs" @echo "make migrate - Run database migrations" @echo "make test - Run test suites" @echo "make lint - Run linting" @echo "make build - Build all Docker images" @echo "make clean - Remove containers and volumes" @echo "make shell - Open shell in API container" # Start services up: docker compose up -d @echo "Services starting..." @echo "API: http://api.hq.local" @echo "Web: http://hq.local" @echo "Auth: http://auth.hq.local" @echo "Traefik: http://traefik.hq.local:8080" # Stop services down: docker compose down # View logs logs: docker compose logs -f # View specific service logs logs-api: docker compose logs -f api logs-web: docker compose logs -f web logs-db: docker compose logs -f postgres # Run database migrations migrate: docker compose exec api alembic upgrade head # Create new migration migration: docker compose exec api alembic revision --autogenerate -m "$(message)" # Run tests test: docker compose exec api pytest -v # Run linting lint: docker compose exec api ruff check . docker compose exec api mypy . cd apps/web && npm run lint # Type checking typecheck: docker compose exec api mypy . cd apps/web && npm run typecheck # Build all images build: docker compose build # Build specific service build-api: docker compose build api build-web: docker compose build web # Clean up clean: docker compose down -v --remove-orphans docker system prune -f # Open shell in API container shell: docker compose exec api /bin/sh # Database shell db-shell: docker compose exec postgres psql -U $(POSTGRES_USER) -d $(POSTGRES_DB) # Health check health: @echo "Checking service health..." @docker compose ps @docker compose exec api wget -qO- http://localhost:8000/health || echo "API health check failed" ``` - [ ] **Step 2: Commit** ```bash git add Makefile git commit -m "feat: add Makefile with common development commands" ``` --- ## Task 7: Validate Docker Configuration **Files:** - Test: `docker-compose.yml` - [ ] **Step 1: Validate Docker Compose syntax** ```bash docker compose config ``` Expected: Valid YAML output with all services configured - [ ] **Step 2: Test build** ```bash make build ``` Expected: Both API and web images build successfully (may warn about missing source files - that's OK) - [ ] **Step 3: Test start/stop** ```bash make up sleep 10 make down ``` Expected: Services start (Postgres and Redis should be healthy), then stop cleanly - [ ] **Step 4: Commit** ```bash git add -A git commit -m "test: validate Docker infrastructure" ``` --- ## Verification Run these checks to verify everything works: 1. **Syntax validation:** ```bash docker compose config > /dev/null && echo "Valid" ``` 2. **Health checks:** ```bash make up docker compose ps ``` All services should show "healthy" or "running" 3. **Makefile commands:** ```bash make help # Shows usage make build # Builds images make up # Starts services make logs # Shows logs make down # Stops services ``` ## Quality Gates - [ ] `docker compose config` validates without errors - [ ] All services have health checks defined - [ ] API Dockerfile uses multi-stage build with non-root user - [ ] Web Dockerfile uses multi-stage build with non-root user - [ ] Makefile includes all required commands (up, down, logs, migrate, test, lint) - [ ] .env.example documents all required variables - [ ] Persistent volume for `/data/repos`