name: roomote services: postgres: container_name: roomote-postgres image: pgvector/pgvector:0.8.1-pg17-trixie@sha256:137f044b0efe3d57f39b972b9b53641b1f2045b99d879e298bbf514a25787dcf # Publish datastore ports to loopback only so Postgres, Redis, and MinIO are # never reachable from another host. The production Caddy overlay resets # these to none; use an SSH tunnel or reverse proxy if remote access is # genuinely needed. ports: - '127.0.0.1:15432:5432' volumes: - pg_data:/var/lib/postgresql/data - ./.docker/scripts/postgres:/docker-entrypoint-initdb.d environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password - POSTGRES_DATABASES=roomote_test,roomote_development healthcheck: test: ['CMD-SHELL', 'pg_isready -U postgres -d roomote_development'] interval: 5s timeout: 30s retries: 5 start_period: 120s redis: container_name: roomote-redis image: redis:7-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99 ports: - '127.0.0.1:16379:6379' volumes: - redis_data:/data command: redis-server --appendonly yes minio: container_name: roomote-minio image: ghcr.io/roocodeinc/roomote-minio:RELEASE.2025-10-15T17-29-55Z-f8ecd739@sha256:6e66a6df1e33ad3414c6cc29866bf06fec9d4e3cc7fc3897b9ef01d737d28c98 ports: - '127.0.0.1:19000:9000' - '127.0.0.1:19001:9001' volumes: - minio_data:/data environment: MINIO_ROOT_USER: ${S3_ACCESS_KEY_ID:-roomote} MINIO_ROOT_PASSWORD: ${S3_SECRET_ACCESS_KEY:-roomote-local-artifacts-password} command: server /data --console-address ':9001' minio-init: image: ghcr.io/roocodeinc/roomote-minio:RELEASE.2025-10-15T17-29-55Z-f8ecd739@sha256:6e66a6df1e33ad3414c6cc29866bf06fec9d4e3cc7fc3897b9ef01d737d28c98 restart: 'no' depends_on: minio: condition: service_started environment: MINIO_ROOT_USER: ${S3_ACCESS_KEY_ID:-roomote} MINIO_ROOT_PASSWORD: ${S3_SECRET_ACCESS_KEY:-roomote-local-artifacts-password} S3_BUCKET_ARTIFACTS: ${S3_BUCKET_ARTIFACTS:-roomote-artifacts} entrypoint: ['/bin/sh', '-c'] command: - | until mc alias set roomote http://minio:9000 "$$MINIO_ROOT_USER" "$$MINIO_ROOT_PASSWORD"; do sleep 1 done mc mb --ignore-existing "roomote/$$S3_BUCKET_ARTIFACTS" # Local dev edge mirroring the production Caddy routing: one origin serves # the web app, routes public webhooks directly to the API, and proxies the # reserved /_roomote-api/* prefix to the API. # The dev CLI points the public tunnel at this port so hosted compute # workers can reach the API through the public app URL. caddy-dev: container_name: roomote-caddy-dev image: caddy:2.10-alpine@sha256:4c6e91c6ed0e2fa03efd5b44747b625fec79bc9cd06ac5235a779726618e530d environment: S3_BUCKET_ARTIFACTS: ${S3_BUCKET_ARTIFACTS:-roomote-artifacts} ROOMOTE_PREVIEW_PROXY_PORT: ${ROOMOTE_PREVIEW_PROXY_PORT:-18081} ROOMOTE_PREVIEW_DOMAIN: ${ROOMOTE_PREVIEW_DOMAIN:-roomotepreview.localhost} ports: - '127.0.0.1:18080:18080' volumes: - ./.docker/caddy:/etc/caddy:ro extra_hosts: - 'host.docker.internal:host-gateway' # The Brain (deployment-hosted gbrain), managed alongside the other local # datastores. `pnpm dev` starts this profiled service explicitly; `pnpm # brain:up` remains available to operate it independently. The port is # published to loopback only because the local # api/bullmq processes run on the host rather than in this network; in the # self-host and installer stacks the Brain publishes no ports at all and is # reachable only from the app services. gbrain: profiles: [brain] container_name: roomote-gbrain build: .docker/gbrain restart: unless-stopped depends_on: postgres: condition: service_healthy environment: GBRAIN_DATABASE_URL: postgres://postgres:password@postgres:5432/roomote_development GBRAIN_ADMIN_BOOTSTRAP_TOKEN: ${R_GBRAIN_ADMIN_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://host.docker.internal: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:-} ports: - '127.0.0.1:8935:8931' volumes: - gbrain_data:/data healthcheck: # The image ships bun, not curl/wget. test: [ 'CMD-SHELL', 'bun -e "const r = await fetch(\"http://127.0.0.1:8931/health\"); process.exit(r.ok ? 0 : 1)"', ] interval: 10s timeout: 5s retries: 12 start_period: 60s volumes: pg_data: redis_data: minio_data: gbrain_data: name: roomote_gbrain_data networks: default: name: roomote_default driver: bridge