name: n3xvault

x-api-security: &api-security
  read_only: true
  security_opt:
    - no-new-privileges:true
  cap_drop:
    - ALL
  tmpfs:
    - /tmp:size=32m,mode=1777

x-web-security: &web-security
  read_only: true
  security_opt:
    - no-new-privileges:true
  cap_drop:
    - ALL
  tmpfs:
    - /tmp:size=32m,mode=1777
    - /app/.next/cache:size=64m,mode=0700,uid=1001,gid=1001

services:
  bootstrap:
    image: alpine:3.22
    restart: "no"
    command:
      - /bin/sh
      - -ec
      - |
        umask 077
        if [ ! -s /run/n3xvault/database_password ]; then
          od -An -N32 -tx1 /dev/urandom | tr -d ' \n' > /run/n3xvault/database_password
        fi
        if [ ! -s /run/n3xvault/bootstrap_token ]; then
          od -An -N32 -tx1 /dev/urandom | tr -d ' \n' > /run/n3xvault/bootstrap_token
        fi
        chmod 0444 /run/n3xvault/database_password /run/n3xvault/bootstrap_token
    volumes:
      - runtime_secrets:/run/n3xvault
    networks:
      - backend
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL

  postgres:
    image: postgres:17-alpine
    restart: unless-stopped
    depends_on:
      bootstrap:
        condition: service_completed_successfully
    environment:
      POSTGRES_DB: n3xvault
      POSTGRES_USER: n3xvault
      POSTGRES_PASSWORD_FILE: /run/n3xvault/database_password
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - runtime_secrets:/run/n3xvault:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U n3xvault -d n3xvault"]
      interval: 5s
      timeout: 5s
      retries: 12
    networks:
      - backend

  api:
    image: ghcr.io/nexitpl/n3xvault-api:alpha
    restart: unless-stopped
    depends_on:
      bootstrap:
        condition: service_completed_successfully
      postgres:
        condition: service_healthy
    environment:
      N3XVAULT_BIND: 0.0.0.0:8080
      N3XVAULT_DATABASE_HOST: postgres
      N3XVAULT_DATABASE_NAME: n3xvault
      N3XVAULT_DATABASE_USER: n3xvault
      N3XVAULT_DATABASE_PASSWORD_FILE: /run/n3xvault/database_password
      N3XVAULT_BOOTSTRAP_TOKEN_FILE: /run/n3xvault/bootstrap_token
      N3XVAULT_PUBLIC_ORIGIN: ${N3XVAULT_PUBLIC_ORIGIN:-http://localhost:8793}
      N3XVAULT_WEBAUTHN_RP_ID: ${N3XVAULT_WEBAUTHN_RP_ID:-localhost}
      N3XVAULT_SESSION_TTL_SECONDS: ${N3XVAULT_SESSION_TTL_SECONDS:-900}
      N3XVAULT_WEBAUTHN_FLOW_TTL_SECONDS: ${N3XVAULT_WEBAUTHN_FLOW_TTL_SECONDS:-300}
      N3XVAULT_DEVICE_PAIRING_TTL_SECONDS: ${N3XVAULT_DEVICE_PAIRING_TTL_SECONDS:-300}
      RUST_LOG: ${N3XVAULT_LOG_LEVEL:-n3xvault_api=info,tower_http=info}
    volumes:
      - runtime_secrets:/run/n3xvault:ro
    healthcheck:
      test: ["CMD", "curl", "--fail", "--silent", "http://127.0.0.1:8080/health/ready"]
      interval: 15s
      timeout: 3s
      retries: 5
      start_period: 15s
    networks:
      - backend
    <<: *api-security

  web:
    image: ghcr.io/nexitpl/n3xvault-web:alpha
    restart: unless-stopped
    depends_on:
      api:
        condition: service_healthy
    environment:
      N3XVAULT_API_INTERNAL_URL: http://api:8080
    ports:
      - "${N3XVAULT_HTTP_BIND:-127.0.0.1}:${N3XVAULT_HTTP_PORT:-8793}:3000"
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000/api/health"]
      interval: 20s
      timeout: 5s
      retries: 5
      start_period: 20s
    networks:
      - frontend
      - backend
    <<: *web-security

networks:
  frontend:
  backend:
    internal: true

volumes:
  postgres_data:
  runtime_secrets:
