Ctrl K
ComunidadGuías…

docker-compose.workers.yml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Worker services — managed separately from app (no blue-green needed).
# BullMQ queues are durable in Redis; jobs survive worker restart.
#
# Usage: docker compose -f docker-compose.workers.yml up -d

x-worker-image: &worker-image
  image: ${SOTTO_WORKERS_IMAGE:-sotto-workers}:${SOTTO_IMAGE_TAG:-local}
  build:
    context: .
    dockerfile: apps/web/Dockerfile.workers
    args:
      WORKER_BASE_IMAGE: ${SOTTO_WORKER_BASE_IMAGE:-ghcr.io/affromero/sotto-workers-base:node24}
      RUNTIME_UID: '1000'
  # Web and workers share the CLI homes as UID 1000.
  volumes:
    # Workers generate the TTS and episode audio that web serves back over
    # /api/v1/storage, so web and workers must mount the same local store.
    - storage:/app/apps/web/.sotto/storage
    - executions:/app/apps/web/.sotto/runtime
    - cli-credentials:/run/cli-credentials
    - agent-home:/home/sotto/.claude
    - agent-codex-home:/home/sotto/.codex

services:
  workers-heavy:
    <<: *worker-image
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 3g
    env_file:
      - .env
    environment:
      NODE_ENV: production
      DATABASE_URL: ${DATABASE_URL:?Set DATABASE_URL}
      REDIS_URL: ${REDIS_URL:?Set REDIS_URL}
      SOTTO_CREDENTIAL_SYNC_DIR: /run/cli-credentials
      LOCAL_STORAGE_DIR: ${LOCAL_STORAGE_DIR:-/app/apps/web/.sotto/storage}
      SIDEDOOR_EXECUTION_DIR: /app/apps/web/.sotto/runtime/executions
      CLAUDE_HOME: /home/sotto/.claude
      CODEX_HOME: /home/sotto/.codex
      WORKER_PROFILE: heavy
      WORKER_PRESET: ${WORKER_PRESET_HEAVY:-full}
      WORKER_QUEUES: ${WORKER_QUEUES_HEAVY:-}
      WORKER_EXCLUDE_QUEUES: ${WORKER_EXCLUDE_QUEUES_HEAVY:-}
    command: ['node', '--max-old-space-size=2560', '--import', 'tsx', 'src/workers/index.ts']
    networks:
      - sotto-network
    logging:
      driver: json-file
      options:
        max-size: '50m'
        max-file: '5'

  workers-pipeline:
    <<: *worker-image
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 1g
    env_file:
      - .env
    environment:
      NODE_ENV: production
      DATABASE_URL: ${DATABASE_URL:?Set DATABASE_URL}
      REDIS_URL: ${REDIS_URL:?Set REDIS_URL}
      SOTTO_CREDENTIAL_SYNC_DIR: /run/cli-credentials
      LOCAL_STORAGE_DIR: ${LOCAL_STORAGE_DIR:-/app/apps/web/.sotto/storage}
      SIDEDOOR_EXECUTION_DIR: /app/apps/web/.sotto/runtime/executions
      CLAUDE_HOME: /home/sotto/.claude
      CODEX_HOME: /home/sotto/.codex
      WORKER_PROFILE: pipeline
      WORKER_PRESET: ${WORKER_PRESET_PIPELINE:-full}
      WORKER_QUEUES: ${WORKER_QUEUES_PIPELINE:-}
      WORKER_EXCLUDE_QUEUES: ${WORKER_EXCLUDE_QUEUES_PIPELINE:-}
    command: ['node', '--max-old-space-size=768', '--import', 'tsx', 'src/workers/index.ts']
    networks:
      - sotto-network
    logging:
      driver: json-file
      options:
        max-size: '50m'
        max-file: '5'

  workers-light:
    <<: *worker-image
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 768m
    env_file:
      - .env
    environment:
      NODE_ENV: production
      DATABASE_URL: ${DATABASE_URL:?Set DATABASE_URL}
      REDIS_URL: ${REDIS_URL:?Set REDIS_URL}
      SOTTO_CREDENTIAL_SYNC_DIR: /run/cli-credentials
      LOCAL_STORAGE_DIR: ${LOCAL_STORAGE_DIR:-/app/apps/web/.sotto/storage}
      SIDEDOOR_EXECUTION_DIR: /app/apps/web/.sotto/runtime/executions
      CLAUDE_HOME: /home/sotto/.claude
      CODEX_HOME: /home/sotto/.codex
      WORKER_PROFILE: light
      WORKER_PRESET: ${WORKER_PRESET_LIGHT:-full}
      WORKER_QUEUES: ${WORKER_QUEUES_LIGHT:-}
      WORKER_EXCLUDE_QUEUES: ${WORKER_EXCLUDE_QUEUES_LIGHT:-}
    command: ['node', '--max-old-space-size=512', '--import', 'tsx', 'src/workers/index.ts']
    networks:
      - sotto-network
    logging:
      driver: json-file
      options:
        max-size: '20m'
        max-file: '3'

  credential-sync:
    image: alpine:3.22@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: '20m'
        max-file: '3'
    network_mode: none
    read_only: true
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - DAC_READ_SEARCH
    volumes:
      - ${CLAUDE_AUTH_DIR:-/dev/null}:/host-claude:ro
      - ${CODEX_AUTH_DIR:-/dev/null}:/host-codex:ro
      - cli-credentials:/credential-sync
      - ./scripts/agent/sync-cli-credentials.sh:/usr/local/bin/sync-cli-credentials:ro
    command: ['/bin/sh', '/usr/local/bin/sync-cli-credentials']
    healthcheck:
      test: ['CMD', 'test', '-f', '/credential-sync/ready']
      interval: 2s
      timeout: 1s
      retries: 15

networks:
  sotto-network:
    external: true
    name: sotto-network

volumes:
  # Host-wide, shared with every stack: whichever process refreshes the OAuth
  # token first, the rotation is immediately the truth for all the others.
  agent-home:
    name: agent-claude-home
  agent-codex-home:
    name: agent-codex-home
  cli-credentials:
    name: ${SOTTO_STACK:?Set SOTTO_STACK}-cli-credentials
  # Stack-scoped but NOT project-scoped: web runs blue/green under two project
  # names and the workers are a third, so an unnamed volume would give each its
  # own empty store and every local asset would vanish on the next swap.
  storage:
    name: ${SOTTO_STACK:?Set SOTTO_STACK}-storage
  executions:
    name: ${SOTTO_STACK:?Set SOTTO_STACK}-executions