nexus/docker-compose.yml
2026-01-26 11:58:04 -05:00

424 lines
11 KiB
YAML

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"