docker-compose.yml
Rama main · réplica de lectura del repositorio original.
services:
# ── Garage (S3-compatible blob storage) ──────────────────────────────────────
# Used for file uploads (images, audio, attachments). Same API as AWS S3 / R2.
# Replaces MinIO (EOL — upstream repo archived April 2026). The entrypoint
# self-provisions layout + bucket + key, importing S3_ACCESS_KEY/S3_SECRET_KEY
# so the S3_* env contract on web/worker is unchanged. Host port 9000 is kept
# (Garage's S3 API listens on 3900 internally) so derived public endpoints
# keep working. Override S3_* env to use external storage (e.g. R2) instead.
garage:
image: dxflrs/garage:v2.3.0
container_name: pear-garage
ports:
- "9000:3900" # S3 API (host 9000 preserved from the MinIO era)
environment:
# Secrets read by Garage when absent from garage.toml. Compose-network
# internal (RPC/admin ports are not published); defaults are fine for
# local self-hosting, override in .env for anything exposed.
GARAGE_RPC_SECRET: ${GARAGE_RPC_SECRET:-9dd7325b40c1f0ab926e0a35e6592f9026fda9ec7d5c0d1fa1c4c05dfa79dc44}
GARAGE_ADMIN_TOKEN: ${GARAGE_ADMIN_TOKEN:-pear-garage-admin}
volumes:
- garage-meta:/var/lib/garage/meta
- garage-data:/var/lib/garage/data
- ./server/docker/garage.toml:/etc/garage.toml:ro
restart: unless-stopped
networks:
- pear
# One-shot provisioner: layout + bucket + key import via the admin API.
garage-init:
image: alpine:3.20
container_name: pear-garage-init
depends_on:
- garage
entrypoint: ["/bin/sh", "/garage-init.sh"]
environment:
GARAGE_ADMIN_TOKEN: ${GARAGE_ADMIN_TOKEN:-pear-garage-admin}
S3_BUCKET: ${S3_BUCKET:-pear-attachments}
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-pear-access-key}
S3_SECRET_KEY: ${S3_SECRET_KEY:-pear-secret-key-change-me}
volumes:
- ./server/docker/garage-init.sh:/garage-init.sh:ro
restart: "no"
networks:
- pear
# ── SpacetimeDB ────────────────────────────────────────────────────────────
# Pre-build the module before starting: cd server && spacetime build
spacetimedb:
image: clockworklabs/spacetime:v2.0.3
container_name: pear-spacetimedb
ports:
- "3000:3000"
volumes:
- spacetimedb-data:/stdb/data
# Persist the spacetime CLI identity across container restarts.
# Without this, every restart generates a new identity that isn't the
# database owner, causing a 403 on publish.
- spacetimedb-config:/root/.config/spacetime
# Entrypoint script (starts SpacetimeDB then publishes the module)
- ./server/docker/entrypoint.sh:/entrypoint.sh:ro
# Pre-built module WASM — run `cd server && spacetime build` first
- ./server/spacetimedb/target/wasm32-unknown-unknown/release/server.wasm:/module/server.wasm:ro
entrypoint: ["/bin/sh", "/entrypoint.sh"]
environment:
STDB_DB_NAME: ${SPACETIMEDB_DB_NAME:-pear-dev}
user: root
restart: unless-stopped
networks:
- pear
# ── Next.js web client ──────────────────────────────────────────────────────
web:
# Image pulled from the Forgejo container registry on the remote.
# Set WEB_IMAGE in your .env to the full registry path, e.g.:
# WEB_IMAGE=forgejo.yourdomain.com/kara/pear-web:latest
# Falls back to a local tag for dev builds.
image: ${WEB_IMAGE:-pear-web:latest}
build:
context: .
dockerfile: web/Dockerfile
args:
# NEXT_PUBLIC_* vars are baked into the browser bundle at build time.
# The browser must reach these URLs directly, so use the public-facing
# addresses (Cloudflare Tunnel hostname or exposed host port).
NEXT_PUBLIC_SPACETIMEDB_URI: ${SPACETIMEDB_PUBLIC_URI-ws://localhost:3000}
NEXT_PUBLIC_SPACETIMEDB_DB_NAME: ${SPACETIMEDB_DB_NAME:-pear-dev}
NEXT_PUBLIC_SPACETIMEAUTH_CLIENT_ID: ${SPACETIMEAUTH_CLIENT_ID:-}
NEXT_PUBLIC_SPACETIMEAUTH_AUTHORITY: ${SPACETIMEAUTH_AUTHORITY:-}
# Optional same-origin proxy to SpacetimeDB via Next.js rewrites.
# Set SPACETIMEDB_PROXY_ORIGIN=http://spacetimedb:3000 and leave
# SPACETIMEDB_PUBLIC_URI empty to let the browser use ws(s)://<web-host>.
# The web app proxies /v1/* to the internal SpacetimeDB service.
SPACETIMEDB_PROXY_ORIGIN: ${SPACETIMEDB_PROXY_ORIGIN:-}
container_name: pear-web
ports:
- "3001:3001"
depends_on:
- spacetimedb
- garage
environment:
# S3-compatible blob storage (default: Garage in this stack)
# Override to use external storage (e.g. R2, S3): set S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY.
S3_ENDPOINT: ${S3_ENDPOINT:-http://garage:3900}
# Optional. Browser upload URL (presigned URLs). If unset, derived from request host + :9000 (zero config when app and Garage are on the same host — Garage's S3 API is published on host port 9000). Set only when storage is on another host/port.
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT:-}
S3_BUCKET: ${S3_BUCKET:-pear-attachments}
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-pear-access-key}
S3_SECRET_KEY: ${S3_SECRET_KEY:-pear-secret-key-change-me}
S3_REGION: ${S3_REGION:-us-east-1}
restart: unless-stopped
networks:
- pear
# ── Pear LLM Worker ────────────────────────────────────────────────────────
# Orcha agent: connects to SpacetimeDB, claims "orchestrate" and "llm" tasks,
# calls Anthropic Claude, and submits results back to the coordination layer.
# Set ANTHROPIC_API_KEY in your .env to enable.
# Scale with: docker compose up --scale worker=3
worker:
image: ${WORKER_IMAGE:-pear-worker:latest}
build:
context: .
dockerfile: worker/Dockerfile
container_name: pear-worker
depends_on:
- spacetimedb
environment:
SPACETIMEDB_URI: ws://spacetimedb:3000
SPACETIMEDB_DB_NAME: ${SPACETIMEDB_DB_NAME:-pear-dev}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
SPACETIMEDB_TOKEN: ${SPACETIMEDB_TOKEN:-}
PEAR_MAIL_PRIVATE_KEY_B64: ${PEAR_MAIL_PRIVATE_KEY_B64:-}
PEAR_MAIL_ALLOW_PRIVATE_HOSTS: ${PEAR_MAIL_ALLOW_PRIVATE_HOSTS:-false}
ANTHROPIC_MODEL: ${ANTHROPIC_MODEL:-claude-haiku-4-5-20251001}
ORCHA_AGENT_ID: ${ORCHA_AGENT_ID:-pear-llm-worker}
# Same storage as the web service — the worker fetches image attachments
# to hand to AI providers as base64.
S3_ENDPOINT: ${S3_ENDPOINT:-http://garage:3900}
S3_BUCKET: ${S3_BUCKET:-pear-attachments}
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-pear-access-key}
S3_SECRET_KEY: ${S3_SECRET_KEY:-pear-secret-key-change-me}
S3_REGION: ${S3_REGION:-us-east-1}
restart: unless-stopped
networks:
- pear
# ── Pear MCP Server (optional) ─────────────────────────────────────────────
# Streamable-HTTP MCP endpoint so external AI models (Claude Code, Cursor, …)
# can use Pear as a memory store / notes backend. Clients authenticate with
# an AI user's worker token as a Bearer token (mint one: `pnpm mcp:provision`).
# Opt-in: docker compose --profile mcp up
mcp:
image: ${WORKER_IMAGE:-pear-worker:latest}
build:
context: .
dockerfile: worker/Dockerfile
container_name: pear-mcp
command: ["pnpm", "--filter", "@pear/worker", "mcp:http"]
profiles: ["mcp"]
depends_on:
- spacetimedb
ports:
- "${PEAR_MCP_HTTP_PORT:-3888}:3888"
environment:
SPACETIMEDB_URI: ws://spacetimedb:3000
SPACETIMEDB_DB_NAME: ${SPACETIMEDB_DB_NAME:-pear-dev}
PEAR_MCP_HTTP_HOST: 0.0.0.0
PEAR_MCP_HTTP_PORT: 3888
# Host-header allowlist for DNS-rebinding protection. Add your public
# hostname:port when exposing this beyond localhost.
PEAR_MCP_ALLOWED_HOSTS: ${PEAR_MCP_ALLOWED_HOSTS:-localhost:3888,127.0.0.1:3888}
restart: unless-stopped
networks:
- pear
networks:
pear:
driver: bridge
volumes:
spacetimedb-data:
spacetimedb-config:
garage-meta:
garage-data: