services: # ============================================ # NEXUS APP SERVICES # ============================================ # Vault Agent for nexus migrations (one-shot, runs before app) vault-agent-migrate: image: hashicorp/vault:1.18 container_name: nexus-vault-agent-migrate network_mode: host user: "0:0" command: > sh -c "rm -f /vault/secrets/.env; vault agent -config=/vault/config/agent-config.hcl & while [ ! -f /vault/secrets/.env ]; do sleep 1; done; echo 'Secrets rendered, exiting'; exit 0" cap_add: - IPC_LOCK volumes: - ./vault/agent-config-migrate.hcl:/vault/config/agent-config.hcl:ro - ./vault/templates:/vault/templates:ro - ./secrets/migrate/role-id:/vault/role-id:ro - ./secrets/migrate/secret-id:/vault/secret-id:ro - ./run/migrate:/vault/secrets environment: - VAULT_ADDR=http://vault.example.local:8200 # Nexus migration runner (one-shot, runs before app) migrate: build: context: . dockerfile: Dockerfile.migrate container_name: nexus-migrate network_mode: host depends_on: vault-agent-migrate: condition: service_completed_successfully volumes: - ./run/migrate:/vault/secrets:ro - ./migrations:/app/migrations:ro working_dir: /app command: - | set -e echo "Loading credentials from Vault..." set -a . /vault/secrets/.env set +a echo "Running migrations..." sqlx migrate run echo "Migrations complete!" # Vault Agent for nexus app runtime (long-running) vault-agent: image: hashicorp/vault:1.18 container_name: nexus-vault-agent restart: unless-stopped network_mode: host pid: host # Share PID namespace to signal nexus on credential refresh user: "0:0" command: ["vault", "agent", "-config=/vault/config/agent-config.hcl"] cap_add: - IPC_LOCK - KILL # Required to send SIGHUP to nexus for credential refresh volumes: - ./vault/agent-config.hcl:/vault/config/agent-config.hcl:ro - ./vault/templates:/vault/templates:ro - ./secrets/app/role-id:/vault/role-id:ro - ./secrets/app/secret-id:/vault/secret-id:ro - ./run/app:/vault/secrets environment: - VAULT_ADDR=http://vault.example.local:8200 healthcheck: test: ["CMD", "test", "-f", "/vault/secrets/.env"] interval: 5s timeout: 3s retries: 30 start_period: 10s # Main nexus application nexus: build: context: . dockerfile: Dockerfile container_name: nexus restart: unless-stopped network_mode: host pid: host # Share PID namespace so vault-agent can signal us depends_on: migrate: condition: service_completed_successfully vault-agent: condition: service_healthy volumes: - ./run/app:/vault/secrets # Not read-only - app writes nexus.pid environment: - RUST_LOG=nexus=info,tower_http=info healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5050/health/ready"] interval: 30s timeout: 10s retries: 3 start_period: 10s logging: driver: "json-file" options: max-size: "10m" max-file: "3" # ============================================ # PGBOUNCER SERVICE (for Kratos) # ============================================ # PgBouncer with integrated Vault Agent # Proxies Kratos DB connections with dynamic Vault credentials pgbouncer: build: context: ./pgbouncer dockerfile: Dockerfile container_name: nexus-pgbouncer restart: unless-stopped network_mode: host cap_add: - IPC_LOCK volumes: - ./vault/agent-config-pgbouncer.hcl:/vault/config/agent-config.hcl:ro - ./vault/templates:/vault/templates:ro - ./secrets/kratos-app/role-id:/vault/role-id:ro - ./secrets/kratos-app/secret-id:/vault/secret-id:ro environment: - VAULT_ADDR=http://vault.example.local:8200 healthcheck: test: ["CMD", "pg_isready", "-h", "127.0.0.1", "-p", "6432", "-U", "kratos"] interval: 5s timeout: 3s retries: 30 start_period: 15s logging: driver: "json-file" options: max-size: "10m" max-file: "3" # ============================================ # KRATOS SERVICES # ============================================ # Vault Agent for Kratos migrations (one-shot) vault-agent-kratos-migrate: image: hashicorp/vault:1.18 container_name: nexus-vault-agent-kratos-migrate network_mode: host user: "0:0" command: > sh -c "rm -f /vault/secrets/.env; vault agent -config=/vault/config/agent-config.hcl & while [ ! -f /vault/secrets/.env ]; do sleep 1; done; echo 'Secrets rendered, exiting'; exit 0" cap_add: - IPC_LOCK volumes: - ./vault/agent-config-kratos-migrate.hcl:/vault/config/agent-config.hcl:ro - ./vault/templates:/vault/templates:ro - ./secrets/kratos-migrate/role-id:/vault/role-id:ro - ./secrets/kratos-migrate/secret-id:/vault/secret-id:ro - ./run/kratos-migrate:/vault/secrets environment: - VAULT_ADDR=http://vault.example.local:8200 # Kratos migration runner (one-shot) kratos-migrate: image: oryd/kratos:v1.1.0 container_name: nexus-kratos-migrate network_mode: host depends_on: vault-agent-kratos-migrate: condition: service_completed_successfully migrate: condition: service_completed_successfully # Nexus migrations create kratos schema volumes: - ./kratos/config:/etc/kratos:ro - ./run/kratos-migrate:/vault/secrets:ro entrypoint: ["/bin/sh", "-c"] command: - | export $(grep -v '^#' /vault/secrets/.env | xargs) exec kratos migrate sql -e --yes # Vault Agent for Kratos runtime (long-running) vault-agent-kratos: image: hashicorp/vault:1.18 container_name: nexus-vault-agent-kratos restart: unless-stopped network_mode: host user: "0:0" command: ["vault", "agent", "-config=/vault/config/agent-config.hcl"] cap_add: - IPC_LOCK volumes: - ./vault/agent-config-kratos.hcl:/vault/config/agent-config.hcl:ro - ./vault/templates:/vault/templates:ro - ./secrets/kratos-app/role-id:/vault/role-id:ro - ./secrets/kratos-app/secret-id:/vault/secret-id:ro - ./run/kratos:/vault/secrets environment: - VAULT_ADDR=http://vault.example.local:8200 healthcheck: test: ["CMD", "test", "-f", "/vault/secrets/.env"] interval: 5s timeout: 3s retries: 30 start_period: 10s # Kratos identity server (long-running) kratos: image: oryd/kratos:v1.1.0 container_name: nexus-kratos restart: unless-stopped network_mode: host depends_on: kratos-migrate: condition: service_completed_successfully vault-agent-kratos: condition: service_healthy pgbouncer: condition: service_healthy volumes: - ./kratos/config:/etc/kratos:ro - ./run/kratos:/vault/secrets:ro entrypoint: ["/bin/sh", "-c"] command: - | export $(grep -v '^#' /vault/secrets/.env | xargs) exec kratos serve --config /etc/kratos/kratos.yml healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:6050/health/alive"] interval: 10s timeout: 5s retries: 5 start_period: 10s logging: driver: "json-file" options: max-size: "10m" max-file: "3" # ============================================ # OATHKEEPER SERVICE # ============================================ # Vault Agent for Oathkeeper (long-running) vault-agent-oathkeeper: image: hashicorp/vault:1.18 container_name: nexus-vault-agent-oathkeeper restart: unless-stopped network_mode: host user: "0:0" command: ["vault", "agent", "-config=/vault/config/agent-config.hcl"] cap_add: - IPC_LOCK volumes: - ./vault/agent-config-oathkeeper.hcl:/vault/config/agent-config.hcl:ro - ./vault/templates:/vault/templates:ro - ./secrets/oathkeeper/role-id:/vault/role-id:ro - ./secrets/oathkeeper/secret-id:/vault/secret-id:ro - ./run/oathkeeper:/vault/secrets environment: - VAULT_ADDR=http://vault.example.local:8200 healthcheck: test: ["CMD", "test", "-f", "/vault/secrets/.env"] interval: 5s timeout: 3s retries: 30 start_period: 10s # Oathkeeper API gateway (stateless, long-running) oathkeeper: build: context: ./oathkeeper dockerfile: Dockerfile container_name: nexus-oathkeeper restart: unless-stopped network_mode: host depends_on: kratos: condition: service_healthy nexus: condition: service_healthy vault-agent-oathkeeper: condition: service_healthy volumes: - ./run/oathkeeper:/vault/secrets:ro healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:7250/health/alive"] interval: 10s timeout: 5s retries: 5 start_period: 10s logging: driver: "json-file" options: max-size: "10m" max-file: "3" # ============================================ # AUTH FRONTEND SERVICE # ============================================ # Auth frontend (account.example.com) auth-frontend: build: context: ./auth-frontend dockerfile: Dockerfile container_name: nexus-auth-frontend restart: unless-stopped network_mode: host environment: - KRATOS_SERVER_URL=http://localhost:6000 - ORIGIN=https://account.example.com - ADMIN_USER_ID=00000000-0000-0000-0000-000000000000 # Replace with your admin user ID depends_on: kratos: condition: service_healthy oathkeeper: condition: service_healthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/"] interval: 30s timeout: 5s retries: 3 start_period: 10s logging: driver: "json-file" options: max-size: "10m" max-file: "3" # ============================================ # MAIN FRONTEND SERVICE # ============================================ # Main frontend (app.example.com) frontend: build: context: ./frontend dockerfile: Dockerfile container_name: nexus-frontend restart: unless-stopped network_mode: host environment: - NODE_ENV=production - ORIGIN=https://app.example.com depends_on: oathkeeper: condition: service_healthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5000/"] interval: 30s timeout: 5s retries: 3 start_period: 10s logging: driver: "json-file" options: max-size: "10m" max-file: "3"