docker-compose.self-host.yml
Rama develop · réplica de lectura del repositorio original.
name: roomote
# One shared application image ships web, api, controller, bullmq,
# preview-proxy, and the db-migrate one-shot. db-migrate owns the build (it
# runs first); every other service runs the same image with a different
# entrypoint-dispatcher command (see .docker/app/entrypoint.sh).
x-roomote-app-image: &roomote-app-image roomote-app:self-host
x-roomote-env: &roomote-env
NODE_ENV: development
R_APP_ENV: development
ROOMOTE_DOCKER_LOAD_ENV_FILE: 'false'
HOST: 0.0.0.0
DATABASE_URL: postgres://postgres:password@postgres:5432/roomote_development
REDIS_URL: redis://redis:6379
S3_ENDPOINT: ${S3_ENDPOINT:-http://minio:9000}
S3_PRESIGN_ENDPOINT: ${S3_PRESIGN_ENDPOINT:-http://minio:9000}
S3_REGION: ${S3_REGION:-us-east-1}
S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-roomote}
S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-roomote-local-artifacts-password}
S3_BUCKET_ARTIFACTS: ${S3_BUCKET_ARTIFACTS:-roomote-artifacts}
R_APP_URL: ${R_PUBLIC_URL:-http://localhost:13000}
R_INSTANCE_ID: ${R_INSTANCE_ID:-}
R_CURATED_INTEGRATIONS_DISABLED: ${R_CURATED_INTEGRATIONS_DISABLED:-false}
# The Brain: optional provider keys for the shared memory agents consult.
# They power the brain service's embeddings and synthesis; an enabled Brain
# otherwise falls back to the deployment's regular provider key. Admins turn
# Memory on or off in Settings > Memory. Until that toggle has been set, a
# key here also turns Memory on (legacy behavior).
R_BRAIN_OPENROUTER_API_KEY: ${R_BRAIN_OPENROUTER_API_KEY:-}
R_BRAIN_OPENAI_API_KEY: ${R_BRAIN_OPENAI_API_KEY:-}
# Shared secret the Brain presents to /api/brain/inference. REQUIRED to
# enable the Brain: a provider key alone leaves it unable to embed. The
# installer generates one; set any 32+ random characters by hand.
R_BRAIN_GATEWAY_TOKEN: ${R_BRAIN_GATEWAY_TOKEN:-}
# Which models the Brain runs, in the configured provider's own naming.
# The synthesis model is free to change; the embedding pair is create-time.
R_BRAIN_MODEL: ${R_BRAIN_MODEL:-}
R_BRAIN_EMBEDDING_MODEL: ${R_BRAIN_EMBEDDING_MODEL:-}
R_BRAIN_EMBEDDING_DIMENSIONS: ${R_BRAIN_EMBEDDING_DIMENSIONS:-}
R_GBRAIN_URL: ${R_GBRAIN_URL:-http://gbrain:8931}
# Roomote reads the brain's bootstrap token once to register its own
# scoped clients; api and bullmq mount the brain volume read-only.
R_GBRAIN_ADMIN_TOKEN_FILE: /gbrain-data/admin-bootstrap-token
# Written by the Brain on first boot when no token was supplied, so a stack
# started by hand needs only a provider key.
R_BRAIN_GATEWAY_TOKEN_FILE: /gbrain-data/gateway-token
TRPC_URL: http://api:3001
PREVIEW_PROXY_BASE_URL: ${PREVIEW_PROXY_BASE_URL:-http://localhost:18081}
PREVIEW_DOMAINS: ${PREVIEW_DOMAINS:-localhost,127.0.0.1,roomotepreview.localhost}
R_PUBLIC_URL: ${R_PUBLIC_URL:-}
R_ALLOWED_EMAILS: ${R_ALLOWED_EMAILS:-}
R_LICENSE_KEY: ${R_LICENSE_KEY:-}
R_MODEL: ${R_MODEL:-}
# Decision model. Unset, a deployment uses Jev when a TypeSafe key is
# configured and otherwise none. R_JUDGMENT_UPSTREAM_URL points at any
# server that speaks the decision contract, such as the `judgment` profile's
# sidecar below (http://judgment:8080).
R_JUDGMENT_MODEL: ${R_JUDGMENT_MODEL:-}
R_JUDGMENT_UPSTREAM_URL: ${R_JUDGMENT_UPSTREAM_URL:-}
R_JUDGMENT_UPSTREAM_API_KEY: ${R_JUDGMENT_UPSTREAM_API_KEY:-}
R_SMALL_MODEL: ${R_SMALL_MODEL:-}
R_CODE_REVIEW_MODEL: ${R_CODE_REVIEW_MODEL:-}
R_EXPLORE_MODEL: ${R_EXPLORE_MODEL:-}
R_MODEL_ENV_KEYS: ${R_MODEL_ENV_KEYS:-}
CUSTOM_PROVIDER_API_KEY: ${CUSTOM_PROVIDER_API_KEY:-}
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
AI_GATEWAY_API_KEY: ${AI_GATEWAY_API_KEY:-}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}
GEMINI_API_KEY: ${GEMINI_API_KEY:-}
GOOGLE_GENERATIVE_AI_API_KEY: ${GOOGLE_GENERATIVE_AI_API_KEY:-}
AWS_BEARER_TOKEN_BEDROCK: ${AWS_BEARER_TOKEN_BEDROCK:-}
AWS_REGION: ${AWS_REGION:-}
MOONSHOT_API_KEY: ${MOONSHOT_API_KEY:-}
KIMI_API_KEY: ${KIMI_API_KEY:-}
MINIMAX_API_KEY: ${MINIMAX_API_KEY:-}
ZAI_API_KEY: ${ZAI_API_KEY:-}
ZAI_REGION: ${ZAI_REGION:-}
ZAI_CODING_PLAN_API_KEY: ${ZAI_CODING_PLAN_API_KEY:-}
ZAI_CODING_PLAN_REGION: ${ZAI_CODING_PLAN_REGION:-}
OPENCODE_API_KEY: ${OPENCODE_API_KEY:-}
OPENCODE_GO_API_KEY: ${OPENCODE_GO_API_KEY:-}
BASETEN_API_KEY: ${BASETEN_API_KEY:-}
TOGETHER_API_KEY: ${TOGETHER_API_KEY:-}
GITHUB_TOKEN: ${GITHUB_TOKEN:-}
R_GITHUB_APP_ID: ${R_GITHUB_APP_ID:-}
R_GITHUB_APP_PRIVATE_KEY: ${R_GITHUB_APP_PRIVATE_KEY:-}
R_GITHUB_CLIENT_ID: ${R_GITHUB_CLIENT_ID:-}
R_GITHUB_CLIENT_SECRET: ${R_GITHUB_CLIENT_SECRET:-}
R_GITHUB_WEBHOOK_SECRET: ${R_GITHUB_WEBHOOK_SECRET:-}
SLACK_APP_ID: ${SLACK_APP_ID:-}
R_SLACK_CLIENT_ID: ${R_SLACK_CLIENT_ID:-}
R_SLACK_CLIENT_SECRET: ${R_SLACK_CLIENT_SECRET:-}
R_SLACK_SIGNING_SECRET: ${R_SLACK_SIGNING_SECRET:-}
R_TELEGRAM_BOT_TOKEN: ${R_TELEGRAM_BOT_TOKEN:-}
R_TELEGRAM_WEBHOOK_SECRET: ${R_TELEGRAM_WEBHOOK_SECRET:-}
R_DISCORD_BOT_TOKEN: ${R_DISCORD_BOT_TOKEN:-}
R_DISCORD_GATEWAY_SECRET: ${R_DISCORD_GATEWAY_SECRET:-}
R_LINEAR_CLIENT_ID: ${R_LINEAR_CLIENT_ID:-}
R_LINEAR_CLIENT_SECRET: ${R_LINEAR_CLIENT_SECRET:-}
R_LINEAR_WEBHOOK_SECRET: ${R_LINEAR_WEBHOOK_SECRET:-}
R_GITHUB_APP_SLUG: ${R_GITHUB_APP_SLUG:-}
R_GITHUB_ADDITIONAL_APP_SLUGS: ${R_GITHUB_ADDITIONAL_APP_SLUGS:-}
SETUP_TOKEN: ${SETUP_TOKEN:-}
DEFAULT_COMPUTE_PROVIDER: ${DEFAULT_COMPUTE_PROVIDER:-docker}
BL_API_KEY: ${BL_API_KEY:-}
BL_WORKSPACE: ${BL_WORKSPACE:-}
BLAXEL_IMAGE: ${BLAXEL_IMAGE:-}
BLAXEL_REGION: ${BLAXEL_REGION:-}
BLAXEL_STANDBY_MAX_COUNT: ${BLAXEL_STANDBY_MAX_COUNT:-25}
BLAXEL_STANDBY_MAX_AGE_HOURS: ${BLAXEL_STANDBY_MAX_AGE_HOURS:-168}
BOX_API_KEY: ${BOX_API_KEY:-}
BOX_API_BASE_URL: ${BOX_API_BASE_URL:-}
BOX_MACHINE_TYPE: ${BOX_MACHINE_TYPE:-}
BOX_TIMEOUT_MS: ${BOX_TIMEOUT_MS:-}
BOX_STANDBY_MAX_COUNT: ${BOX_STANDBY_MAX_COUNT:-25}
BOX_STANDBY_MAX_AGE_HOURS: ${BOX_STANDBY_MAX_AGE_HOURS:-168}
AZURE_SUBSCRIPTION_ID: ${AZURE_SUBSCRIPTION_ID:-}
AZURE_RESOURCE_GROUP: ${AZURE_RESOURCE_GROUP:-}
AZURE_SANDBOX_GROUP: ${AZURE_SANDBOX_GROUP:-}
AZURE_SANDBOX_REGION: ${AZURE_SANDBOX_REGION:-}
AZURE_SANDBOX_DISK_IMAGE: ${AZURE_SANDBOX_DISK_IMAGE:-}
AZURE_CLIENT_ID: ${AZURE_CLIENT_ID:-}
AZURE_TENANT_ID: ${AZURE_TENANT_ID:-}
AZURE_CLIENT_SECRET: ${AZURE_CLIENT_SECRET:-}
AZURE_SANDBOX_REGISTRY_USERNAME: ${AZURE_SANDBOX_REGISTRY_USERNAME:-}
AZURE_SANDBOX_REGISTRY_TOKEN: ${AZURE_SANDBOX_REGISTRY_TOKEN:-}
AZURE_SANDBOX_SIZE: ${AZURE_SANDBOX_SIZE:-}
AZURE_SANDBOX_EGRESS_INSPECTION: ${AZURE_SANDBOX_EGRESS_INSPECTION:-}
AZURE_HTTP_DEBUG: ${AZURE_HTTP_DEBUG:-}
DOCKER_STANDBY_MAX_COUNT: ${DOCKER_STANDBY_MAX_COUNT:-10}
DOCKER_STANDBY_MAX_AGE_HOURS: ${DOCKER_STANDBY_MAX_AGE_HOURS:-24}
# Declarative environment provisioning: point ROOMOTE_ENVIRONMENTS_DIR at a
# directory of environment YAML files mounted into the api container (for
# example `- ./environments:/roomote/environments:ro` on the api service),
# or put inline multi-document YAML in ROOMOTE_ENVIRONMENTS_YAML. Only the
# api service reads these; see SELF_HOSTING.md.
ROOMOTE_ENVIRONMENTS_DIR: ${ROOMOTE_ENVIRONMENTS_DIR:-}
ROOMOTE_ENVIRONMENTS_YAML: ${ROOMOTE_ENVIRONMENTS_YAML:-}
x-roomote-service: &roomote-service
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
minio-init:
condition: service_completed_successfully
db-migrate:
condition: service_completed_successfully
environment:
<<: *roomote-env
services:
db-migrate:
restart: 'no'
container_name: roomote-db-migrate
image: *roomote-app-image
build:
context: .
dockerfile: .docker/app/Dockerfile
args:
APP_ENV: development
RELEASE_VERSION: ${RELEASE_VERSION:-self-host-local}
environment:
<<: *roomote-env
depends_on:
postgres:
condition: service_healthy
command: ['db-migrate']
web:
<<: *roomote-service
container_name: roomote-web
image: *roomote-app-image
command: ['web']
# Publish app ports to loopback only. Standalone self-host is meant to sit
# behind a reverse proxy on the same host (or the production Caddy overlay,
# which resets these to none); binding to all interfaces would expose the
# web app, API, queue dashboard, and preview proxy directly.
ports:
- '127.0.0.1:13000:3000'
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
minio-init:
condition: service_completed_successfully
db-migrate:
condition: service_completed_successfully
api:
condition: service_started
environment:
<<: *roomote-env
NODE_ENV: production
api:
<<: *roomote-service
container_name: roomote-api
image: *roomote-app-image
command: ['api']
# Reachable from task worker containers (worker -> API over TRPC_URL).
networks: [default, worker]
environment:
<<: *roomote-env
PORT: 3001
volumes:
- gbrain_data:/gbrain-data:ro
ports:
- '127.0.0.1:13001:3001'
controller:
<<: *roomote-service
container_name: roomote-controller
image: *roomote-app-image
command: ['controller']
environment:
<<: *roomote-env
SANDBOX_OPENROUTER_API_KEY: ${SANDBOX_OPENROUTER_API_KEY:-}
# Spawns and reaches task worker containers on the worker network.
networks: [default, worker]
stop_grace_period: 7m
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
minio-init:
condition: service_completed_successfully
db-migrate:
condition: service_completed_successfully
api:
condition: service_started
healthcheck:
test:
[
'CMD-SHELL',
'curl -fsS --max-time 5 http://api:3001/health/controller >/dev/null',
]
interval: 5s
timeout: 6s
retries: 12
start_period: 20s
bullmq:
<<: *roomote-service
container_name: roomote-bullmq
image: *roomote-app-image
command: ['bullmq']
environment:
<<: *roomote-env
PORT: 3002
volumes:
- gbrain_data:/gbrain-data:ro
ports:
- '127.0.0.1:13002:3002'
healthcheck:
test:
[
'CMD-SHELL',
'curl -fsS --max-time 5 http://127.0.0.1:3002/admin/health >/dev/null',
]
interval: 5s
timeout: 6s
retries: 12
start_period: 20s
preview-proxy:
<<: *roomote-service
container_name: roomote-preview-proxy
image: *roomote-app-image
command: ['preview-proxy']
# Proxies preview traffic into task worker containers.
networks: [default, worker]
environment:
<<: *roomote-env
PORT: 8081
ports:
- '127.0.0.1:18081:8081'
# Brain (deployment-hosted gbrain). Opt-in via
# COMPOSE_PROFILES=brain; deployments that never enable the
# profile see no infrastructure change. Internal-only: no published
# ports, reachable solely by the API service, which fronts it at
# /api/mcp/gbrain with the read-only agent credential. Wire static tokens
# into api/bullmq (operator-pinned), or the normal path: api/bullmq already
# point R_GBRAIN_URL at http://gbrain:8931 and read the admin bootstrap token
# from the shared volume, so Roomote registers its own scoped clients via
# the admin API once Memory is enabled in Settings > Memory. Set
# GBRAIN_ADMIN_BOOTSTRAP_TOKEN below (>= 32 chars of [A-Za-z0-9_-]) to pin
# the token; if left unset, the entrypoint generates one at
# /data/admin-bootstrap-token on the volume.
gbrain:
profiles: [brain]
container_name: roomote-gbrain
build: .docker/gbrain
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
networks: [default]
environment:
GBRAIN_DATABASE_URL: postgres://postgres:password@postgres:5432/roomote_development
GBRAIN_ADMIN_BOOTSTRAP_TOKEN: ${GBRAIN_ADMIN_BOOTSTRAP_TOKEN:-}
# The Brain holds no provider key. It calls back into this deployment,
# which injects whichever provider key an admin configured in Settings,
# so rotating or switching that key needs no restart here. Setting a
# provider key below instead makes the Brain call the provider directly.
OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://api:3001/api/brain/inference}
OPENAI_API_KEY: ${R_BRAIN_GATEWAY_TOKEN:-}
# Create-time, and it has to be the container that receives these: the
# embedding model and its width are decided when the Brain is created,
# and the gateway must never substitute a different one afterwards.
# Set both before the first boot, or neither. The synthesis model is
# the opposite (R_BRAIN_MODEL, applied by the gateway per request).
GBRAIN_EMBEDDING_MODEL: ${R_BRAIN_EMBEDDING_MODEL:-}
GBRAIN_EMBEDDING_DIMENSIONS: ${R_BRAIN_EMBEDDING_DIMENSIONS:-}
volumes:
- gbrain_data:/data
# Optional CPU decision model (judgment sidecar): the Memory check, reply
# addressing, and Roomote's other typed decisions without a Jev key or a
# GPU. Enable with COMPOSE_PROFILES=judgment and point Roomote at it with
# R_JUDGMENT_UPSTREAM_URL=http://judgment:8080. It serves
# roomote/roomote-judgment-gliner unless JUDGMENT_MODEL names another
# GLiNER 2.5 checkpoint. The memory and CPU caps bound what inference can
# take from the host.
judgment:
profiles: [judgment]
container_name: roomote-judgment
build: .docker/judgment
restart: unless-stopped
networks: [default]
environment:
JUDGMENT_MODEL: ${JUDGMENT_MODEL:-roomote/roomote-judgment-gliner}
HF_TOKEN: ${JUDGMENT_HF_TOKEN:-}
JUDGMENT_API_KEY: ${R_JUDGMENT_UPSTREAM_API_KEY:-}
JUDGMENT_THREADS: ${JUDGMENT_CPUS:-4}
mem_limit: ${JUDGMENT_MEMORY_LIMIT:-4g}
cpus: ${JUDGMENT_CPUS:-4}
volumes:
- judgment_models:/models
networks:
# The controller uses this network to discover the API and optional preview
# proxy. Each task gets a separate bridge network; only the API and an
# enabled preview proxy are attached to it, so task sandboxes never share a
# network with one another or with Postgres, Redis, or MinIO. The default
# network is implicit (roomote_default) and carries the datastores.
worker:
name: roomote_worker
driver: bridge
volumes:
gbrain_data:
name: roomote_gbrain_data
judgment_models:
name: roomote_judgment_models