services: # ============================================================ # PostgreSQL # ============================================================ postgres: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER:-elydora} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-elydora-secret-change-me} POSTGRES_DB: ${POSTGRES_DB:-elydora} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-elydora} -d ${POSTGRES_DB:-elydora}"] interval: 5s timeout: 5s retries: 10 # ============================================================ # Redis # ============================================================ redis: image: redis:7-alpine restart: unless-stopped command: redis-server --appendonly yes volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 5s retries: 10 # ============================================================ # MinIO (S3-compatible object storage) # ============================================================ minio: image: minio/minio:latest restart: unless-stopped command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER:-elydora} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-elydora-minio-secret-change-me} volumes: - minio_data:/data ports: - "9001:9001" healthcheck: test: ["CMD", "mc", "ready", "local"] interval: 10s timeout: 5s retries: 10 # ============================================================ # MinIO init: creates the evidence bucket # ============================================================ minio-init: image: minio/mc:latest depends_on: minio: condition: service_healthy environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER:-elydora} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-elydora-minio-secret-change-me} MINIO_BUCKET: ${MINIO_BUCKET:-elydora-evidence} entrypoint: > /bin/sh -c " mc alias set local http://minio:9000 $${MINIO_ROOT_USER} $${MINIO_ROOT_PASSWORD}; mc mb --ignore-existing local/$${MINIO_BUCKET}; echo 'MinIO bucket initialized.'; " restart: on-failure # ============================================================ # DB migrate: applies packages/server/migrations # ============================================================ db-migrate: image: postgres:16-alpine depends_on: postgres: condition: service_healthy environment: PGPASSWORD: ${POSTGRES_PASSWORD:-elydora-secret-change-me} volumes: - ./packages/server/migrations:/migrations:ro,z entrypoint: ["sh", "-c"] command: - | set -e PSQL="psql -h postgres -U ${POSTGRES_USER:-elydora} -d ${POSTGRES_DB:-elydora}" for file in /migrations/*.sql; do version=$$(basename "$$file" | cut -d_ -f1 | sed 's/^0*//') applied=$$($$PSQL -tAX -c "SELECT 1 FROM schema_versions WHERE version = $$version" 2>/dev/null || true) if [ "$$applied" = "1" ]; then echo "Skipping $$file (already applied)" continue fi echo "Applying $$file" $$PSQL -v ON_ERROR_STOP=1 -f "$$file" done api: build: context: ./packages/server dockerfile: Dockerfile restart: unless-stopped env_file: .env ports: - "${PORT:-8787}:8787" depends_on: postgres: condition: service_healthy redis: condition: service_healthy minio: condition: service_healthy db-migrate: condition: service_completed_successfully healthcheck: test: ["CMD-SHELL", "wget -qO- http://localhost:8787/v1/health || exit 1"] interval: 10s timeout: 5s retries: 10 # ============================================================ # Worker (queue consumer) # ============================================================ worker: build: context: ./packages/server dockerfile: Dockerfile restart: unless-stopped command: ["node", "dist/worker.js"] env_file: .env depends_on: api: condition: service_healthy # ============================================================ # Scheduler (epoch scheduler) # ============================================================ scheduler: build: context: ./packages/server dockerfile: Dockerfile restart: unless-stopped command: ["node", "dist/scheduler.js"] env_file: .env depends_on: api: condition: service_healthy # ============================================================ # Console (Next.js frontend) # ============================================================ console: build: context: . dockerfile: packages/console/Dockerfile args: NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL:-http://localhost:8787} restart: unless-stopped environment: NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL:-http://localhost:8787} ports: - "3000:3000" depends_on: api: condition: service_healthy volumes: postgres_data: redis_data: minio_data: