version: "3.9" services: postgres: image: postgres:16-alpine environment: POSTGRES_DB: ${POSTGRES_DB:-data_cleaning} POSTGRES_USER: ${POSTGRES_USER:-admin} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} PGOPTIONS: "-c log_min_messages=WARNING -c shared_buffers=256MB" volumes: - postgres_data:/var/lib/postgresql/data restart: unless-stopped networks: - database_tier healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5 start_period: 30s deploy: resources: limits: memory: 2GB reservations: memory: 512MB logging: driver: json-file options: max-size: "10m" max-file: "3" redis: image: redis:7-alpine command: redis-server --maxmemory 512mb --maxmemory-policy allkeys-lru restart: unless-stopped networks: - backend_tier healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 3 deploy: resources: limits: memory: 512MB logging: driver: json-file options: max-size: "10m" max-file: "3" backend: image: ghcr.io/${REGISTRY_OWNER}/data-cleaning-openenv-backend:latest pull_policy: always environment: DATABASE_URL: ${DATABASE_URL:-postgresql://admin:password@postgres:5432/data_cleaning} REDIS_URL: ${REDIS_URL:-redis://redis:6379/0} JWT_SECRET: ${JWT_SECRET:-change-me-in-production} ENVIRONMENT: ${ENVIRONMENT:-production} LOG_LEVEL: ${LOG_LEVEL:-INFO} PYTHONUNBUFFERED: 1 POSTGRES_USER: ${POSTGRES_USER:-admin} POSTGRES_DB: ${POSTGRES_DB:-data_cleaning} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} depends_on: postgres: condition: service_healthy redis: condition: service_healthy volumes: - uploads_data:/app/data/uploads restart: unless-stopped networks: - backend_tier - frontend_tier - database_tier healthcheck: test: ["CMD-SHELL", "curl -f http://localhost:8000/api/health || exit 1"] interval: 30s timeout: 5s retries: 3 start_period: 30s read_only: true tmpfs: - /tmp deploy: resources: limits: memory: 1GB cpus: "1" reservations: memory: 256MB logging: driver: json-file options: max-size: "10m" max-file: "5" security_opt: - no-new-privileges:true celery: image: ghcr.io/${REGISTRY_OWNER}/data-cleaning-openenv-backend:latest pull_policy: always command: celery -A backend.tasks worker --loglevel=info --concurrency=2 environment: DATABASE_URL: ${DATABASE_URL:-postgresql://admin:password@postgres:5432/data_cleaning} REDIS_URL: ${REDIS_URL:-redis://redis:6379/0} ENVIRONMENT: ${ENVIRONMENT:-production} LOG_LEVEL: ${LOG_LEVEL:-INFO} PYTHONUNBUFFERED: 1 depends_on: postgres: condition: service_healthy redis: condition: service_healthy backend: condition: service_started restart: unless-stopped networks: - backend_tier - database_tier read_only: true tmpfs: - /tmp deploy: resources: limits: memory: 512MB cpus: "0.5" reservations: memory: 128MB logging: driver: json-file options: max-size: "10m" max-file: "5" security_opt: - no-new-privileges:true frontend: image: ghcr.io/${REGISTRY_OWNER}/data-cleaning-openenv-frontend:latest pull_policy: always environment: VITE_API_BASE_URL: ${VITE_API_BASE_URL:-/api} depends_on: backend: condition: service_started restart: unless-stopped networks: - frontend_tier healthcheck: test: ["CMD-SHELL", "wget --quiet --tries=1 --spider http://localhost:3000 || exit 1"] interval: 30s timeout: 5s retries: 3 start_period: 20s deploy: resources: limits: memory: 512MB cpus: "0.5" reservations: memory: 128MB logging: driver: json-file options: max-size: "10m" max-file: "3" read_only: true tmpfs: - /tmp security_opt: - no-new-privileges:true volumes: postgres_data: driver: local uploads_data: driver: local networks: frontend_tier: internal: false backend_tier: internal: true database_tier: internal: true