docker-compose.workers.yml
Rama main · réplica de lectura del repositorio original.
# 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