Ctrl K
ComunidadGuías…

docker-compose.selfhost.yml

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

Archivo original ↗Preguntar a KiBot
# Self-hosting Docker Compose — starts PostgreSQL, backend, and frontend.
#
# Services bind to 127.0.0.1 only. For cross-machine or public access, front
# them with a reverse proxy (Caddy / nginx / Cloudflare Tunnel) that terminates
# TLS and forwards to 127.0.0.1:8080 (backend) and 127.0.0.1:3000 (frontend).
# Do NOT change these bindings to 0.0.0.0 — Docker bypasses host firewalls
# (UFW/iptables) by default, so the raw ports would be exposed to the internet
# while any deployment credentials remain weak. See:
#   apps/docs/content/docs/self-host-quickstart.mdx
#
# Usage:
#   cp .env.example .env
#   # Edit .env — generate JWT_SECRET with `openssl rand -hex 32` (required)
#   docker compose -f docker-compose.selfhost.yml up -d
#
# Frontend: http://localhost:${FRONTEND_PORT:-3000}
# Backend:  http://localhost:${BACKEND_PORT:-${API_PORT:-${SERVER_PORT:-${PORT:-8080}}}}
#
# The published values above are HOST ports; the containers always listen on
# 8080 / 3000 internally, so changing them never needs a rebuild. PORT is the
# variable to edit; BACKEND_PORT, API_PORT and SERVER_PORT are optional aliases
# that override it in that order. Keep this alias order identical to Makefile
# and scripts/local-env.sh. The web dev fallback intentionally omits PORT
# because Next uses that variable for its own frontend listener.
#
# Note that *which source* wins differs per entry point — Compose lets the
# calling environment outrank this file, while make lets the included env file
# outrank the environment. Nothing re-derives the published port from the inputs
# any more: `make selfhost` (via scripts/selfhost-wait.sh) and both installers
# read it back with `docker compose port`, so the health check and the printed
# URL always match what was actually published.

name: multica

services:
  postgres:
    image: pgvector/pgvector:pg17
    environment:
      POSTGRES_DB: ${POSTGRES_DB:-multica}
      POSTGRES_USER: ${POSTGRES_USER:-multica}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-multica}
    volumes:
      - pgdata:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "pg_isready -U ${POSTGRES_USER:-multica} -d ${POSTGRES_DB:-multica}",
        ]
      interval: 5s
      timeout: 5s
      retries: 5

  backend:
    image: ${MULTICA_BACKEND_IMAGE:-ghcr.io/multica-ai/multica-backend}:${MULTICA_IMAGE_TAG:-latest}
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "127.0.0.1:${BACKEND_PORT:-${API_PORT:-${SERVER_PORT:-${PORT:-8080}}}}:8080"
    volumes:
      - backend_uploads:/app/data/uploads
    environment:
      DATABASE_URL: postgres://${POSTGRES_USER:-multica}:${POSTGRES_PASSWORD:-multica}@postgres:5432/${POSTGRES_DB:-multica}?sslmode=disable
      DATABASE_SEARCH_WORK_MEM_MB: ${DATABASE_SEARCH_WORK_MEM_MB:-64}
      DATABASE_REPLICA_URL: ${DATABASE_REPLICA_URL:-}
      DATABASE_REPLICA_MAX_CONNS: ${DATABASE_REPLICA_MAX_CONNS:-}
      DATABASE_REPLICA_MIN_CONNS: ${DATABASE_REPLICA_MIN_CONNS:-}
      REDIS_URL: ${REDIS_URL:-}
      REDIS_CLUSTER_MODE: ${REDIS_CLUSTER_MODE:-false}
      PORT: "8080"
      MAINTENANCE_PORT: ${MAINTENANCE_PORT:-}
      METRICS_ADDR: ${METRICS_ADDR:-}
      JWT_SECRET: ${JWT_SECRET:?JWT_SECRET must be set to a strong random value — generate one with 'openssl rand -hex 32'}
      FRONTEND_ORIGIN: ${FRONTEND_ORIGIN:-http://localhost:3000}
      CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-}
      RESEND_API_KEY: ${RESEND_API_KEY:-}
      RESEND_FROM_EMAIL: ${RESEND_FROM_EMAIL:-noreply@multica.ai}
      SMTP_HOST: ${SMTP_HOST:-}
      SMTP_PORT: ${SMTP_PORT:-25}
      SMTP_USERNAME: ${SMTP_USERNAME:-}
      SMTP_PASSWORD: ${SMTP_PASSWORD:-}
      SMTP_FROM_EMAIL: ${SMTP_FROM_EMAIL:-}
      SMTP_TLS: ${SMTP_TLS:-}
      SMTP_TLS_INSECURE: ${SMTP_TLS_INSECURE:-false}
      SMTP_EHLO_NAME: ${SMTP_EHLO_NAME:-}
      GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
      GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
      GOOGLE_REDIRECT_URI: ${GOOGLE_REDIRECT_URI:-http://localhost:3000/auth/callback}
      S3_BUCKET: ${S3_BUCKET:-}
      S3_REGION: ${S3_REGION:-us-west-2}
      AWS_ENDPOINT_URL: ${AWS_ENDPOINT_URL:-}
      S3_USE_PATH_STYLE: ${S3_USE_PATH_STYLE:-}
      AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-}
      AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-}
      ATTACHMENT_DOWNLOAD_MODE: ${ATTACHMENT_DOWNLOAD_MODE:-auto}
      ATTACHMENT_DOWNLOAD_URL_TTL: ${ATTACHMENT_DOWNLOAD_URL_TTL:-30m}
      MULTICA_MAX_PREVIEW_SIZE: ${MULTICA_MAX_PREVIEW_SIZE:-2MiB}
      CLOUDFRONT_DOMAIN: ${CLOUDFRONT_DOMAIN:-}
      CLOUDFRONT_KEY_PAIR_ID: ${CLOUDFRONT_KEY_PAIR_ID:-}
      CLOUDFRONT_PRIVATE_KEY: ${CLOUDFRONT_PRIVATE_KEY:-}
      COOKIE_DOMAIN: ${COOKIE_DOMAIN:-}
      APP_ENV: ${APP_ENV:-production}
      MULTICA_DEV_VERIFICATION_CODE: ${MULTICA_DEV_VERIFICATION_CODE:-}
      MULTICA_APP_URL: ${MULTICA_APP_URL:-http://localhost:3000}
      MULTICA_DATABASE_STARTUP_TIMEOUT: ${MULTICA_DATABASE_STARTUP_TIMEOUT:-3m}
      MULTICA_DATABASE_CONNECT_TIMEOUT: ${MULTICA_DATABASE_CONNECT_TIMEOUT:-5s}
      MULTICA_SHUTDOWN_HOLD_DURATION: ${MULTICA_SHUTDOWN_HOLD_DURATION:-}
      MULTICA_RUNTIME_RECONNECT_GRACE: ${MULTICA_RUNTIME_RECONNECT_GRACE:-}
      ALLOW_SIGNUP: ${ALLOW_SIGNUP:-true}
      ALLOWED_EMAILS: ${ALLOWED_EMAILS:-}
      ALLOWED_EMAIL_DOMAINS: ${ALLOWED_EMAIL_DOMAINS:-}
      DISABLE_WORKSPACE_CREATION: ${DISABLE_WORKSPACE_CREATION:-}
      WORKSPACE_CREATION_ALLOWED_EMAILS: ${WORKSPACE_CREATION_ALLOWED_EMAILS:-}
      DO_NOT_TRACK: ${DO_NOT_TRACK:-}
      # Managed Cloud stays off for self-hosting. All managed Cloud clients use
      # this single URL when it is configured.
      MULTICA_CLOUD_URL: ${MULTICA_CLOUD_URL:-}
      GITHUB_APP_SLUG: ${GITHUB_APP_SLUG:-}
      GITHUB_WEBHOOK_SECRET: ${GITHUB_WEBHOOK_SECRET:-}
      # App API credentials. The pull_request webhook is what mirrors a PR
      # onto a card; these are what let the server authenticate as the App
      # and pull the CI / mergeability snapshot that enriches it, plus
      # Settings -> Repositories -> Choose from GitHub. Left unset,
      # ghsnapshot.NewClientFromEnv returns a nil client and the enrichment
      # degrades off silently - no error and no log line to notice.
      # GITHUB_APP_PRIVATE_KEY is a PEM block: double-quote it in .env so
      # its newlines survive into the container.
      GITHUB_APP_ID: ${GITHUB_APP_ID:-}
      GITHUB_APP_PRIVATE_KEY: ${GITHUB_APP_PRIVATE_KEY:-}
      # Public URL the API is reachable at from the open internet, no
      # trailing slash. Used to mint absolute webhook URLs for autopilot
      # webhook triggers. Leave unset behind a same-origin reverse proxy
      # (e.g. plain localhost dev); the frontend will compose the URL
      # from window.origin + webhook_path in that case. Headers are
      # intentionally NOT used to derive this value, to avoid Host /
      # X-Forwarded-Host spoofing on misconfigured proxies.
      MULTICA_PUBLIC_URL: ${MULTICA_PUBLIC_URL:-}
      # Surface HTML must use a second, cookie-free browser origin routed to
      # this backend. Both settings are optional only in the sense that leaving
      # either empty disables plugin surfaces rather than weakening isolation.
      MULTICA_PLUGIN_SECRET_KEY: ${MULTICA_PLUGIN_SECRET_KEY:-}
      MULTICA_PLUGIN_SURFACE_ORIGIN: ${MULTICA_PLUGIN_SURFACE_ORIGIN:-}
      MULTICA_PLUGIN_API_URL: ${MULTICA_PLUGIN_API_URL:-}
      MULTICA_PLUGIN_DIR: ${MULTICA_PLUGIN_DIR:-}
      # Optional API URL shown in self-host daemon setup commands. Set this
      # when the daemon reaches the API through a different URL than the one
      # used for public webhook URLs; otherwise MULTICA_PUBLIC_URL is used.
      MULTICA_DAEMON_SERVER_URL: ${MULTICA_DAEMON_SERVER_URL:-}
      # Comma-separated CIDRs whose source IP is allowed to set
      # X-Forwarded-For / X-Real-IP for the webhook per-IP rate limiter.
      # Empty default = headers ignored, RemoteAddr used. Set e.g.
      # "127.0.0.1/32" when running behind a same-host reverse proxy.
      MULTICA_TRUSTED_PROXIES: ${MULTICA_TRUSTED_PROXIES:-}
      # Server-side LLM layer, backing internal helpers such as chat title
      # generation. Both the API key and the base URL empty = layer disabled,
      # and callers fall back silently rather than failing. MULTICA_LLM_MAX_RETRIES
      # is the retry budget: unset = 2, 0 = disabled, 1-5 = an exact ceiling.
      # Anything else fails the boot instead of being silently corrected.
      # MULTICA_LLM_DISABLE_THINKING asks gateways that honor the field to
      # turn model reasoning off; see .env.example for the contract.
      MULTICA_LLM_API_KEY: ${MULTICA_LLM_API_KEY:-}
      MULTICA_LLM_BASE_URL: ${MULTICA_LLM_BASE_URL:-}
      MULTICA_LLM_DEFAULT_MODEL: ${MULTICA_LLM_DEFAULT_MODEL:-}
      MULTICA_LLM_MAX_RETRIES: ${MULTICA_LLM_MAX_RETRIES:-}
      MULTICA_LLM_DISABLE_THINKING: ${MULTICA_LLM_DISABLE_THINKING:-}
      # Lark / Feishu bot integration. MULTICA_LARK_SECRET_KEY is the
      # opt-in: unset = integration disabled. Mainland 飞书 and international
      # Lark are auto-detected per installation and served side by side, so
      # the two base-URL knobs should normally stay EMPTY. They are optional
      # deployment-wide overrides that force every installation onto one host
      # (proxy / mock / single-cloud staging). Upgrading from a setup that
      # used https://open.larksuite.com here? The server relabels existing
      # installs to region=lark on first boot, then you can clear them.
      # See docs/lark-bot-integration.
      MULTICA_LARK_SECRET_KEY: ${MULTICA_LARK_SECRET_KEY:-}
      MULTICA_LARK_HTTP_BASE_URL: ${MULTICA_LARK_HTTP_BASE_URL:-}
      MULTICA_LARK_CALLBACK_BASE_URL: ${MULTICA_LARK_CALLBACK_BASE_URL:-}
      # Slack bot integration. MULTICA_SLACK_SECRET_KEY is the opt-in: unset =
      # integration disabled. It decrypts the per-installation bot/app tokens,
      # which are brought by each workspace via OAuth/BYO and stored encrypted
      # in the database, so this single deployment-wide key is all the operator
      # needs to set here.
      MULTICA_SLACK_SECRET_KEY: ${MULTICA_SLACK_SECRET_KEY:-}
      # DingTalk bot integration. MULTICA_DINGTALK_SECRET_KEY is the opt-in:
      # unset = integration disabled. It encrypts each installation's AppSecret
      # at rest, so this single deployment-wide key is all the operator needs
      # to set here.
      MULTICA_DINGTALK_SECRET_KEY: ${MULTICA_DINGTALK_SECRET_KEY:-}
      # Self-hosted Git provider integration (Forgejo / Gitea / GitLab). This
      # is a self-host-only feature, so the compose file turns it on by default;
      # the managed cloud leaves it unset (off). It still needs a valid
      # MULTICA_VCS_SECRET_KEY below to actually work.
      MULTICA_VCS_INTEGRATION_ENABLED: ${MULTICA_VCS_INTEGRATION_ENABLED:-true}
      # VCS integration at-rest encryption key for token-based providers
      MULTICA_VCS_SECRET_KEY: ${MULTICA_VCS_SECRET_KEY:-}
      # Community theme store (Settings → Appearance → Browse). On by default;
      # "off" hides it and stops every registry request. The registry URL
      # points it at another registry's index.json.
      MULTICA_THEME_STORE: ${MULTICA_THEME_STORE:-}
      MULTICA_THEME_REGISTRY_URL: ${MULTICA_THEME_REGISTRY_URL:-}
      # WeCom smart-bot integration. MULTICA_WECOM_SECRET_KEY is the
      # opt-in: unset = integration disabled. It encrypts each installation's
      # smart-bot secret at rest. The server needs it to unseal a bot's
      # subscribe credentials at connection time, and to seal the secret when
      # an installation is created from the WeCom settings tab.
      MULTICA_WECOM_SECRET_KEY: ${MULTICA_WECOM_SECRET_KEY:-}
      # Comma-separated ranges the inbound media fetcher may dial even though
      # they look reserved. Empty (the default) keeps the SSRF guard as strict
      # as it ships; set it only behind a fake-IP proxy whose pool would
      # otherwise get every attachment refused. See .env.example.
      MULTICA_WECOM_MEDIA_ALLOW_CIDRS: ${MULTICA_WECOM_MEDIA_ALLOW_CIDRS:-}
      # The WeCom-issued caller id the scan-code install flow sends as
      # ?source=. Empty (the default) uses the built-in "multica"; set it only
      # if WeCom issued your organisation its own source. Nothing but the QR
      # install endpoints reads it.
      MULTICA_WECOM_SOURCE_ID: ${MULTICA_WECOM_SOURCE_ID:-}
      # 1 = log every inbound and outbound WeCom frame, including the first
      # 120 runes of each message body, so a real-device session can be
      # checked against the server afterwards. Off unless set; turn it on only
      # for a debugging session and unset it when that session ends, because
      # what it records is user message content.
      MULTICA_WECOM_TRACE: ${MULTICA_WECOM_TRACE:-}
      # Which language the bot's own copy is written in for readers whose
      # language cannot be looked up — a group chat, or anyone not linked to a
      # Multica account yet. A linked person reads their own profile language
      # instead. zh-Hans (default) or en; an unrecognised value leaves the
      # default in place and the resolved one is logged at boot.
      MULTICA_WECOM_DEFAULT_LOCALE: ${MULTICA_WECOM_DEFAULT_LOCALE:-}
      # Telegram bot integration. MULTICA_TELEGRAM_SECRET_KEY is the
      # opt-in: unset = integration disabled. It decrypts the
      # per-installation bot token stored in channel_installation.config
      # (bot_token_encrypted), so this single deployment-wide key is all
      # the operator needs to set here.
      MULTICA_TELEGRAM_SECRET_KEY: ${MULTICA_TELEGRAM_SECRET_KEY:-}
      # Tool steps shown in the streamed reply: auto (default; details in
      # private chats, kind of work in groups), off, summary or detail.
      MULTICA_TELEGRAM_TOOL_PROGRESS: ${MULTICA_TELEGRAM_TOOL_PROGRESS:-}
    restart: unless-stopped

  frontend:
    image: ${MULTICA_WEB_IMAGE:-ghcr.io/multica-ai/multica-web}:${MULTICA_IMAGE_TAG:-latest}
    depends_on:
      - backend
    ports:
      - "127.0.0.1:${FRONTEND_PORT:-3000}:3000"
    environment:
      HOSTNAME: "0.0.0.0"
      REMOTE_API_URL: ${REMOTE_API_URL:-http://backend:8080}
      DOCS_URL: ${DOCS_URL:-}
      NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-}
      NEXT_PUBLIC_WS_URL: ${NEXT_PUBLIC_WS_URL:-}
      GOOGLE_ANALYTICS_ID: ${GOOGLE_ANALYTICS_ID:-}
    restart: unless-stopped

volumes:
  pgdata:
  backend_uploads: