Ctrl K
ComunidadGuías…

docker-compose.yml

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

Archivo original ↗Preguntar a KiBot
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: