version: '3.8' services: # PostgreSQL Database postgres: image: postgres:17-alpine container_name: blog_postgres restart: unless-stopped environment: POSTGRES_USER: ${DB_USER:-blog} POSTGRES_PASSWORD: ${DB_PASSWORD:-changeme} POSTGRES_DB: ${DB_NAME:-blog_prod} volumes: - postgres_data:/var/lib/postgresql/data ports: - "127.0.0.1:5432:5432" # Only expose to localhost healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-blog}"] interval: 10s timeout: 5s retries: 5 networks: - blog_network # Redis Cache redis: image: redis:7-alpine container_name: blog_redis restart: unless-stopped command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-changeme} volumes: - redis_data:/data ports: - "127.0.0.1:6379:6379" # Only expose to localhost healthcheck: test: ["CMD", "redis-cli", "--raw", "incr", "ping"] interval: 10s timeout: 5s retries: 5 networks: - blog_network # NestJS Backend API api: build: context: .. dockerfile: deploy/Dockerfile target: backend-prod container_name: blog_api restart: unless-stopped depends_on: postgres: condition: service_healthy redis: condition: service_healthy environment: NODE_ENV: production PORT: 3000 DATABASE_URL: postgresql://${DB_USER:-blog}:${DB_PASSWORD:-changeme}@postgres:5432/${DB_NAME:-blog_prod} REDIS_URL: redis://:${REDIS_PASSWORD:-changeme}@redis:6379 API_PORT: 3000 LOG_LEVEL: ${LOG_LEVEL:-info} expose: - "3000" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s networks: - blog_network volumes: - /var/log/blog:/app/logs # Optional: mount logs to host # Next.js Frontend frontend: build: context: .. dockerfile: deploy/Dockerfile target: frontend-prod container_name: blog_frontend restart: unless-stopped environment: NODE_ENV: production NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost/api} expose: - "3001" healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3001"] interval: 30s timeout: 10s retries: 3 start_period: 40s networks: - blog_network # Nginx Reverse Proxy nginx: image: nginx:alpine container_name: blog_nginx restart: unless-stopped depends_on: - api - frontend ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./ssl:/etc/nginx/ssl:ro - nginx_cache:/var/cache/nginx networks: - blog_network healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"] interval: 30s timeout: 10s retries: 3 networks: blog_network: driver: bridge volumes: postgres_data: driver: local redis_data: driver: local nginx_cache: driver: local