Multica Docs

自托管快速上手

用 Docker Compose 启动 Multica,完成登录,并连接第一台执行电脑。

自托管 Multica 分成两部分:

部分运行什么放在哪里
Multica 服务Web、API 和 PostgreSQL一台安装了 Docker 的机器
执行电脑Multica 守护进程和 AI 编程工具开发者实际工作的电脑

它们可以是同一台机器,也可以分开。自托管替换的只是 Multica Cloud 部分。

这篇文档使用 Docker Compose。Kubernetes 部署见仓库中的 Self-hosting guide

开始前

运行 Multica 服务的机器需要:

  • Docker Engine 或 Docker Desktop,并且 docker compose 可以运行
  • Git、Make、curl 和 OpenSSL
  • 本机的 30008080 端口未被占用

先确认 Docker 和 Compose 可用:

docker info
docker compose version

Multica 使用 Compose v2,也就是 docker compose。旧版的 docker-compose v1 不受支持。

执行电脑还需要至少一款已经安装并登录的 AI 编程工具,例如 Claude Code、Codex 或 Cursor。第 5 步再安装 Multica CLI。

1. 启动 Multica

在准备运行服务的机器上执行:

git clone --depth 1 https://github.com/multica-ai/multica.git
cd multica
make selfhost

第一次运行时,make selfhost 会:

  1. .env.example 创建 .env
  2. 随机生成 JWT_SECRET、PostgreSQL 密码和 MULTICA_VCS_SECRET_KEY(自托管 Git 集成的加密密钥)
  3. 拉取 PostgreSQL、Multica backend 和 Multica frontend 镜像
  4. 创建持久化数据卷并启动三个容器
  5. 等待 backend 开始响应健康检查

之后再次运行 make selfhost 会继续使用现有的 .env 和数据卷,不会重新生成密钥。

make selfhost 拉取已发布的镜像,不会编译当前 checkout 中的代码。需要测试本地源码时,使用 make selfhost-build

已发布的镜像跟随最新的 release tag,而直接 git clone 检出的是 main,通常领先于发版。如果你要在这个 checkout 上构建任何东西——CLI,或通过 make selfhost-build 构建镜像——先切换到与镜像对应的 release tag,让构建产物和运行中的容器保持同一版本:

git fetch --tags --depth 1
git checkout $(git tag -l 'v*' --sort=-v:refname | head -1)

2. 确认服务已经就绪

查看容器状态:

docker compose -f docker-compose.selfhost.yml ps

postgres 显示为 healthybackendfrontend 应处于运行状态。然后检查 backend、数据库和 migration:

curl -fsS http://localhost:8080/readyz

正常响应是:

{"status":"ok","checks":{"db":"ok","migrations":"ok"}}

Backend 容器每次启动时都会先运行数据库 migration,再启动服务,不需要手动执行 migration 命令。

3. 选择访问方式

本机访问

直接打开 [http://localhost:3000P172。后面运行 multica setup self-host 时也不需要传 URL。

远程访问

Docker Compose 默认只把 30008080 绑定到 127.0.0.1。不要直接改成 0.0.0.0 暴露到公网;使用带 HTTPS 的反向代理。

下面以两个域名为例:

  • app.example.com:Multica Web
  • api.example.com:API、健康检查和守护进程连接

先在 .env 中设置公开地址:

FRONTEND_ORIGIN=https://app.example.com
MULTICA_APP_URL=https://app.example.com
MULTICA_PUBLIC_URL=https://api.example.com

以上配置让浏览器流量全部走 app 域,cookie 不跨域,因此不需要配置 COOKIE_DOMAIN。如果改为让浏览器直接访问 api 域,则必须配置它,见环境变量

然后配置 DNS,并用 Caddy 代理本机端口:

app.example.com {
 # 浏览器的 WebSocket 直接交给 backend
 @ws path /ws /ws/*
 handle @ws {
 reverse_proxy 127.0.0.1:8080 {
 flush_interval -1
 }
 }

 # 其余路径交给 frontend;它会转发 API 和登录请求
 handle {
 reverse_proxy 127.0.0.1:3000
 }
}

api.example.com {
 reverse_proxy 127.0.0.1:8080 {
 flush_interval -1
 }
}

如果想让所有流量走单一 origin(一个域名,或一台主机一个端口——小服务器上常见),把守护进程依赖的路径显式转给 backend,其余交给 frontend:

multica.example.com {
 # CLI 可达性探测:`multica setup` 会 GET <server-url>/health 并要求 200,
 # 旧版 frontend 不转发这个路径,直接路由到 backend
 handle /health {
 reverse_proxy 127.0.0.1:8080
 }

 # 网页端 realtime WebSocket——前端镜像代理不了 WS 升级,直通 backend
 handle /ws {
 reverse_proxy 127.0.0.1:8080 {
 flush_interval -1
 }
 }

 # daemon 长连接:拨的是 {server-url}/api/daemon/ws(不是 /ws)。
 # 缺了这条,握手失败后 daemon 会静默回退到轮询
 handle /api/daemon/ws {
 reverse_proxy 127.0.0.1:8080 {
 flush_interval -1
 }
 }

 # 其余路径交给 frontend;它会转发 API 和登录请求
 handle {
 reverse_proxy 127.0.0.1:3000
 }
}

单一 origin 模式下,两个 URL 都指向它(.env 里的 FRONTEND_ORIGINMULTICA_APP_URL,以及 multica setup self-host--server-url--app-url)。

Caddy 会申请 TLS 证书并转发 WebSocket。修改 .env 后,用 up -d 重新创建容器,让新配置生效:

docker compose -f docker-compose.selfhost.yml up -d
curl -fsS https://api.example.com/readyz
curl -fsS https://app.example.com/api/config | grep -o '"daemon_server_url":"[^"]*"'

最后一条命令输出 daemon_server_url——daemon 连接 API 用的地址:设了 MULTICA_DAEMON_SERVER_URL 时用它,其次是 MULTICA_PUBLIC_URL,否则用 app URL(MULTICA_APP_URL,未设置时回退到 FRONTEND_ORIGIN)。当 MULTICA_APP_URLFRONTEND_ORIGIN 都没设时,这个字段会整个从响应里省略(上面的 grep 无输出)——即使设了 MULTICA_DAEMON_SERVER_URLMULTICA_PUBLIC_URL 也是如此。如果输出的是 localhost,说明 .env 里还是本地默认值:不要依赖从 .env.example 复制来的 ${FRONTEND_PORT} 引用,把 FRONTEND_ORIGINMULTICA_APP_URL 显式设置为公网地址,然后重建容器。

docker compose restart 只重启现有容器,不会重新读取 .env。修改配置后,重新读取 .env 需要运行 docker compose -f docker-compose.selfhost.yml up -d

4. 登录并创建工作区

打开本机的 http://localhost:3000,或刚才配置的 https://app.example.com,输入邮箱获取验证码。

默认没有配置验证码投递服务。请求验证码后,可以从 backend 日志中读取:

docker compose -f docker-compose.selfhost.yml logs backend \
 | grep "Verification code"

日志中会出现类似内容:

[DEV] Verification code for [email protected]: 123456

输入验证码并创建第一个工作区。配置飞书、Resend 或 SMTP 后,验证码会通过对应通道投递,成员无需读取容器日志,具体见登录与注册配置

自托管部署默认使用 APP_ENV=production,不会启用固定验证码。不要在公网实例上设置 MULTICA_DEV_VERIFICATION_CODE

5. 连接执行电脑

下面的命令在运行 AI 编程工具的电脑上执行,不一定是运行 Docker 的服务器。

task 以运行守护进程的用户的全部权限执行——该用户能读写的一切,task 都能读写。请用专用 Unix 用户、容器或虚拟机来运行守护进程,而不是你的个人账号。详见安全模型

先安装 Multica CLI:

macOS / Linux

curl -fsSL https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.sh | bash

Windows PowerShell

irm https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.ps1 | iex

如果 Multica 服务也在这台电脑上,运行:

multica setup self-host

如果 Multica 服务在另一台机器上,传入刚才配置的两个地址:

multica setup self-host \
 --server-url https://api.example.com \
 --app-url https://app.example.com

这条命令会先检查 <server-url>/health,再打开浏览器完成登录。登录完成后,它会保存本机凭据并启动守护进程。这一步报 Server not reachable,说明 /health 没有通过探测拿到 200:可能是代理把所有流量转给了不转发该路径的旧版 frontend,可能是代理根本没把 /health 路由到 backend,也可能是更底层的问题(DNS、TLS、防火墙、超时、backend 返回 5xx)——见故障排查

确认连接状态:

multica daemon status

正常情况下,输出中会显示:

  • Daemon: running
  • Agents 中包含本机已安装的 AI 编程工具
  • Workspaces 大于 0

6. 完成第一次执行

回到 Multica。运行时列表中出现在线的运行时后,创建一个智能体,并把第一个任务分配给它。

执行日志变为"已完成"、时间线中出现智能体的回复,说明自托管服务、执行电脑和 AI 编程工具已经连通。详细步骤见快速上手第 3-5 步。

常用管理命令

以下命令都在 multica 仓库目录中运行:

# 查看状态
docker compose -f docker-compose.selfhost.yml ps

# 查看 backend 日志
docker compose -f docker-compose.selfhost.yml logs -f backend

# 应用 .env 变化
docker compose -f docker-compose.selfhost.yml up -d

# 停止服务,保留数据卷
docker compose -f docker-compose.selfhost.yml down

升级已发布的镜像:

git pull --ff-only
docker compose -f docker-compose.selfhost.yml pull
docker compose -f docker-compose.selfhost.yml up -d
curl -fsS http://localhost:8080/readyz

Docker Compose 部署有两种升级写法。在已有部署上两者结果相同——Makefile 里的 selfhost target 跑的就是同一套 docker compose pull + up -d,只是额外多做了两件事:.env 缺失时生成一份,以及等 /health 就绪后打印一段状态汇总。用哪个都行:

cd multica
git pull
make selfhost
cd multica
git pull
docker compose -f docker-compose.selfhost.yml pull
docker compose -f docker-compose.selfhost.yml up -d

git pull 到底更新了什么

git pull 更新的是 docker-compose.selfhost.yml 本身——新增的环境变量、新增的服务、改过的 healthcheck。它不是拉新版本 Multica 的机制。

真正决定你跑哪个版本的是 docker compose pull:它去 GHCR 查这个 tag 当前指向哪个镜像。所以一个几个月没更新的 checkout 照样能拉到今天的 latest 镜像;反过来,只跑 git pull 而不拉镜像、不重建容器,什么都不会变。

如果你在 .env 里钉死了 MULTICA_IMAGE_TAG,两种写法都升不上去。 两个镜像都写成 ${MULTICA_IMAGE_TAG:-latest}docker-compose.selfhost.yml:42:125),而 .env.example 里默认是 MULTICA_IMAGE_TAG=latest。一旦你把它改成了具体版本,pull 只是把同一个 tag 重拉一遍,版本原地不动——不报错,也没有任何警告。升级前先确认:

grep MULTICA_IMAGE_TAG .env
# MULTICA_IMAGE_TAG=v0.4.5 ← 钉死了:先改成 latest(或你想要的版本)

.env 不会被覆盖

make selfhost 只在 .env 不存在的时候才生成它。在已有部署上重复跑,你的 JWT_SECRET、Postgres 密码、邮件配置、FRONTEND_ORIGIN 都原样保留。

升级前先备份 Postgres

migration 只往前走、不回滚,所以正式环境升级前先导一份:

docker compose -f docker-compose.selfhost.yml exec -T postgres \
 pg_dump -U multica multica > multica-backup.sql && gzip multica-backup.sql

不要把 pg_dump 直接管道接给 gzip shell 取的是管道里最后一条命令的退出码,所以 pg_dump ... | gzip > backup.sql.gz 在导出失败时照样返回 0,留下一个格式完全合法、但里面什么都没有的 20 字节压缩包。先重定向到文件,pg_dump 自己的退出码才算数,&& 也只会压缩真正成功的那份导出。

如果你改过 POSTGRES_USER / POSTGRES_DB,把 multica 换成 .env 里的实际值。数据存在名为 multica_pgdata 的 volume 里,docker compose down 不会删它——但 down -v 会。

migration 自动执行

第 1 步一样,backend 容器启动时会在对外提供服务之前先跑 ./migrate updocker/entrypoint.sh)。没有单独的升级命令——把新镜像起起来,本身就是升级这一步。想看过程:

docker compose -f docker-compose.selfhost.yml logs -f backend

Migration 在 backend 启动时自动运行;需要回填历史数据的 migration(如 103)也会自动完成回填。自动回填极少数情况下失败,报 refusing to drop legacy daily rollups,处理见故障排查

/readyz 验证,不要用 /health

/healthliveness 探针,只要进程还活着就返回 {"status":"ok"},migration 失败了它照样是 ok。/readyzserver/cmd/server/router.go:680/healthz 是它的别名)会真正检查数据库和已应用的 migration 集合,升级出问题能被它拦下来:

curl -s localhost:8080/readyz
# {"status":"ok","checks":{"db":"ok","migrations":"ok"}}

只要不是 HTTP 200 且两项都是 ok,就说明新版本没把 migration 跑完,先去看 backend 日志,别急着放流量进来。

Kubernetes

Helm 有自己的升级路径:在 values 文件里把 images.backend.tag / images.frontend.tag 设成你要的版本,然后 helm upgrade。改 tag 就改了 pod spec,Kubernetes 会拉新镜像并滚动更新——这是可靠的路径。

kubectl -n multica rollout restart 本身不是升级。chart 默认 pullPolicy: IfNotPresentdeploy/helm/multica/values.yaml),节点上已经缓存过这个 tag 的话,restart 只会用回旧镜像,什么都没变——和钉死 MULTICA_IMAGE_TAG 是同一类坑。真要靠浮动 tag 走这条路,得先把 images.backend.pullPolicy / images.frontend.pullPolicy 设成 Always。详见仓库的 Self-hosting guide

docker compose down 会保留 pgdatabackend_uploads。加上 -v 会删除这些数据卷,包括数据库;除非确定要清空实例,否则不要运行 docker compose down -v

常见问题

现象先检查什么
/readyz 没有返回 ok运行 docker compose -f docker-compose.selfhost.yml logs backend postgres
收不到验证码先查看 backend 启动日志中的 Feishu webhookSMTP relayResend APIDEV mode,再请求一次验证码。
setup self-host 提示 server 不可达在执行电脑上请求 https://api.example.com/health,确认 DNS、TLS 和反向代理都可达。返回 404 说明代理把 /health 转给了不转发该路径的旧版 frontend——改为直通 backend(见故障排查)。
守护进程没有列出 Agents确认 AI 编程工具在 PATH 中并已登录,然后运行 multica daemon restart
任务一直排队运行 multica daemon status,确认守护进程正在运行并连接了工作区。

更多情况见故障排查

接下来