name: hayva
x-default-logging: &default-logging
  driver: json-file
  options: {max-size: "10m", max-file: "5"}
services:
  nginx:
    image: nginx:1.28.3-alpine
    depends_on:
      core-api: {condition: service_healthy}
      web: {condition: service_healthy}
    ports: ["127.0.0.1:8080:80"]
    volumes: ["./infra/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro"]
    restart: unless-stopped
    logging: *default-logging
    pids_limit: 128
    mem_limit: 256m
    cpus: 0.5
    networks: [edge, app]
  web:
    build: {context: ., dockerfile: web/Dockerfile}
    read_only: true
    tmpfs: ["/tmp"]
    security_opt: ["no-new-privileges:true"]
    cap_drop: ["ALL"]
    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:8080/healthz"]
      interval: 15s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    logging: *default-logging
    pids_limit: 128
    mem_limit: 256m
    cpus: 0.5
    networks: [app]
  core-api:
    build: {context: ., dockerfile: services/core-api/Dockerfile}
    environment:
      APP_ENV: "${APP_ENV}"
      APP_BASE_URL: "${APP_BASE_URL}"
      APP_SECRET_FILE: /run/secrets/app-secret
      OWNER_BOOTSTRAP_TOKEN_FILE: /run/secrets/owner-bootstrap-token
      AI_SERVICE_URL: http://ai-agent:8001
      AI_SERVICE_TOKEN_FILE: /run/secrets/ai-service-token
      COMPUTER_SERVICE_URL: http://computer-agent:8002
      COMPUTER_SERVICE_TOKEN_FILE: /run/secrets/computer-service-token
      COMPUTER_CAPABILITY_PRIVATE_KEY_FILE: /run/secrets/computer-capability-private-key
      COMPUTER_CAPABILITY_KEY_ID: "${COMPUTER_CAPABILITY_KEY_ID:-computer-capability-v1}"
      COMPUTE_PROVIDER: "${COMPUTE_PROVIDER:-unconfigured}"
      COMPUTE_PROVIDER_URL: "${COMPUTE_PROVIDER_URL:-}"
      COMPUTE_PROVIDER_TOKEN_FILE: /run/secrets/compute-provider-token
      COMPUTE_PROVIDER_TIMEOUT_SECONDS: "${COMPUTE_PROVIDER_TIMEOUT_SECONDS:-30}"
      DATA_ENCRYPTION_KEY_FILE: /run/secrets/data-encryption-key
      DATABASE_URL: "${DATABASE_URL}"
      REDIS_URL: "${REDIS_URL}"
    depends_on:
      db-permissions: {condition: service_completed_successfully}
      redis: {condition: service_healthy}
      ai-agent: {condition: service_healthy}
      computer-agent: {condition: service_healthy}
    read_only: true
    volumes:
      - ./secrets/internal/app-secret:/run/secrets/app-secret:ro
      - ./secrets/bootstrap/owner_token:/run/secrets/owner-bootstrap-token:ro
      - ./secrets/internal/ai-service-token:/run/secrets/ai-service-token:ro
      - ./secrets/internal/computer-service-token:/run/secrets/computer-service-token:ro
      - ./secrets/internal/computer-capability-private-key:/run/secrets/computer-capability-private-key:ro
      - ./secrets/internal/compute-provider-token:/run/secrets/compute-provider-token:ro
      - ./secrets/encryption/data-encryption-key:/run/secrets/data-encryption-key:ro
    tmpfs: ["/tmp"]
    security_opt: ["no-new-privileges:true"]
    cap_drop: ["ALL"]
    stop_grace_period: 30s
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health/readiness')"]
      interval: 15s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    logging: *default-logging
    pids_limit: 256
    mem_limit: 1g
    cpus: 1.0
    networks: [app, data]
  computer-agent:
    build: {context: ., dockerfile: services/computer-agent/Dockerfile}
    environment:
      APP_ENV: "${APP_ENV}"
      COMPUTER_SERVICE_TOKEN_FILE: /run/secrets/computer-service-token
      COMPUTER_CAPABILITY_PUBLIC_KEY_FILE: /run/secrets/computer-capability-public-key
      COMPUTER_CAPABILITY_KEY_ID: "${COMPUTER_CAPABILITY_KEY_ID:-computer-capability-v1}"
      BROWSER_PROFILE_ROOT: /data/browser-profiles
      COMPUTER_ARTIFACT_ROOT: /data/computer-artifacts
      BROWSER_HEADLESS: "true"
      BROWSER_MAX_SESSIONS: "4"
    read_only: true
    volumes:
      - ./secrets/internal/computer-service-token:/run/secrets/computer-service-token:ro
      - ./secrets/internal/computer-capability-public-key:/run/secrets/computer-capability-public-key:ro
      - browser-profiles:/data/browser-profiles
      - computer-artifacts:/data/computer-artifacts
    tmpfs: ["/tmp"]
    shm_size: "1gb"
    cap_drop: ["ALL"]
    security_opt:
      - no-new-privileges:true
      - seccomp=./infra/computer/seccomp_profile.json
    pids_limit: 512
    mem_limit: 2g
    cpus: 2.0
    stop_grace_period: 30s
    healthcheck:
      test: ["CMD", "python", "-c", "import os,urllib.request; t=open(os.environ['COMPUTER_SERVICE_TOKEN_FILE']).read().strip(); r=urllib.request.Request('http://127.0.0.1:8002/health',headers={'x-service-token':t}); urllib.request.urlopen(r)"]
      interval: 15s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    logging: *default-logging
    networks: [app, browser-egress]
  ai-agent:
    build: {context: ., dockerfile: services/ai-agent/Dockerfile}
    environment:
      APP_ENV: "${APP_ENV}"
      AI_SERVICE_TOKEN_FILE: /run/secrets/ai-service-token
      OPENAI_API_KEY_FILE: /run/secrets/openai-api-key
      OPENAI_MODEL: "${OPENAI_MODEL}"
      OPENAI_FALLBACK_MODEL: "${OPENAI_FALLBACK_MODEL}"
    read_only: true
    volumes:
      - ./secrets/internal/ai-service-token:/run/secrets/ai-service-token:ro
      - ./secrets/providers/openai-api-key:/run/secrets/openai-api-key:ro
    tmpfs: ["/tmp"]
    cap_drop: ["ALL"]
    security_opt: ["no-new-privileges:true"]
    healthcheck:
      test: ["CMD", "python", "-c", "import os,urllib.request; t=open(os.environ['AI_SERVICE_TOKEN_FILE']).read().strip(); r=urllib.request.Request('http://127.0.0.1:8001/health',headers={'x-service-token':t}); urllib.request.urlopen(r)"]
      interval: 15s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    logging: *default-logging
    pids_limit: 256
    mem_limit: 1g
    cpus: 1.0
    networks: [app, egress]
  migrate:
    build: {context: ., dockerfile: services/core-api/Dockerfile}
    command: ["python", "-m", "alembic", "upgrade", "head"]
    environment:
      DATABASE_URL: "${DATABASE_MIGRATION_URL}"
    depends_on:
      postgres: {condition: service_healthy}
    read_only: true
    tmpfs: ["/tmp"]
    security_opt: ["no-new-privileges:true"]
    cap_drop: ["ALL"]
    pids_limit: 128
    mem_limit: 512m
    cpus: 1.0
    logging: *default-logging
    restart: "no"
    networks: [data]
  db-permissions:
    image: pgvector/pgvector:0.8.6-pg17
    command: ["/bin/sh", "/opt/hayva/apply_runtime_grants.sh"]
    environment:
      POSTGRES_DB: "${POSTGRES_DB}"
      POSTGRES_USER: "${POSTGRES_USER}"
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
      POSTGRES_RUNTIME_USER: "${POSTGRES_RUNTIME_USER}"
      PGPASSWORD: "${POSTGRES_PASSWORD}"
    volumes:
      - ./infra/postgres/apply_runtime_grants.sh:/opt/hayva/apply_runtime_grants.sh:ro
    depends_on:
      migrate: {condition: service_completed_successfully}
    read_only: true
    tmpfs: ["/tmp"]
    cap_drop: ["ALL"]
    security_opt: ["no-new-privileges:true"]
    restart: "no"
    logging: *default-logging
    networks: [data]
  postgres:
    image: pgvector/pgvector:0.8.6-pg17
    environment:
      POSTGRES_DB: "${POSTGRES_DB}"
      POSTGRES_USER: "${POSTGRES_USER}"
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
      POSTGRES_RUNTIME_USER: "${POSTGRES_RUNTIME_USER}"
      POSTGRES_RUNTIME_PASSWORD: "${POSTGRES_RUNTIME_PASSWORD}"
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./infra/postgres/init_runtime_role.sh:/docker-entrypoint-initdb.d/10-runtime-role.sh:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
      interval: 10s
      timeout: 5s
      retries: 10
    restart: unless-stopped
    logging: *default-logging
    pids_limit: 256
    mem_limit: 1g
    cpus: 1.0
    networks: [data]
  redis:
    image: redis:8.10.1-alpine
    command: ["redis-server", "--appendonly", "yes", "--aclfile", "/run/secrets/redis-users.acl"]
    volumes:
      - redis-data:/data
      - ./secrets/redis/users.acl:/run/secrets/redis-users.acl:ro
    healthcheck:
      test: ["CMD", "redis-cli", "--user", "health", "ping"]
      interval: 10s
      timeout: 5s
      retries: 10
    restart: unless-stopped
    logging: *default-logging
    pids_limit: 256
    mem_limit: 512m
    cpus: 0.5
    networks: [data]
networks:
  edge: {}
  app: {internal: true}
  data: {internal: true}
  egress: {}
  browser-egress: {}
volumes:
  postgres-data: {}
  redis-data: {}
  browser-profiles: {}
  computer-artifacts: {}
