Self-Host 快速上手
在自己的服务器或本机用 Docker 把 lehuo Agents 跑起来(也可以在 Kubernetes 上用 Helm)。约 10 分钟。
这一页带你用 Docker 把 lehuo Agents 的服务器(后端 + 前端 + PostgreSQL)跑在自己的机器或服务器上。走完这一篇你的数据就完全在自己手里——包括 工作区、issue、评论、智能体 配置。
智能体执行还是靠你本地跑的 守护进程 + 本地装好的 AI 编程工具——这点和 Cloud 完全一样。Self-host 换掉的是服务器那一层,不是执行那一层。
前置要求
- Docker 安装好并且能跑
docker compose - Git 可选(推荐——可以拉源码)
- 一台能长期开机的机器(本地 / 内网 / 云主机都行)
- 至少一款 AI 编程工具装在运行守护进程的机器上(不一定是跑服务器的机器——可以是你开发用的笔记本)
1. 拉取项目 + 一键启动后端
已经有 Kubernetes 集群? 不用走 Docker,直接用 Helm chart——跳到下面的 Kubernetes 部署(替代方案),装完再回到 第 4 步 完成登录。
# Download the self-host bundle from https://agents.lehuo.app/download
curl -fsSL https://agents.lehuo.app/download/selfhost.tar.gz -o lehuo-selfhost.tar.gz
tar -xzf lehuo-selfhost.tar.gz
cd lehuo
make selfhostmake selfhost 会:
- 如果没有
.env文件,从.env.example自动生成一份并生成随机 JWT_SECRET - 拉取官方 Docker 镜像(PostgreSQL、lehuo Agents backend、lehuo Agents frontend)
- 用
docker-compose.selfhost.yml启动全部服务 - 等后端
/health端点准备就绪
如果是启动完成后的生产探针,想让数据库或 migration 异常也体现为失败,请改用 /readyz。
后端容器启动时会自动跑数据库 migration(docker/entrypoint.sh 在启动 server 前执行 ./migrate up)——你会在 backend 日志里看到 migration 输出。升级版本时同样自动处理。
镜像还没发布? 如果 make selfhost 报拉不到镜像,可能是你在某个未发布的版本标签上。切到稳定版本或直接从源码构建:make selfhost-build。
启动完成后:
所有端口只监听 127.0.0.1。 docker-compose.selfhost.yml 把每个 publish 出来的端口都绑到 loopback —— ss -tlnp 不会看到 0.0.0.0:8080,外网/其它机器默认根本连不上。这是为了避免默认 JWT_SECRET 和 Postgres 凭据被直接暴露到公网。要做跨机访问,请用反向代理在前面终结 TLS,详见下方 Step 5b —— 跨机访问:用反向代理把服务挡在前面。
2. 重要:保持生产安全配置
docker-compose.selfhost.yml 默认把 APP_ENV 设成 production,并让 LEHUO_DEV_VERIFICATION_CODE 为空,所以公网实例默认没有固定验证码。
只在本地或私有测试自动化里设置 LEHUO_DEV_VERIFICATION_CODE。如果在 APP_ENV 非 production 时启用了固定验证码,任何能请求验证码的人都能用这个固定值登录。详见 登录与注册配置 → 固定本地测试验证码。
公网部署前一定检查 .env 里 APP_ENV=production,且 LEHUO_DEV_VERIFICATION_CODE 为空。
3. 配置邮件服务(可选但推荐)
如果不配邮件,用户无法通过邮件收到验证码;server 会把生成的验证码打印到 stdout。
支持两种发送通道,按部署环境二选一:
Option A — Resend(公网/云端部署):
-
在 Resend 注册并拿一个 API key
-
验证一个你控制的发件域名
-
在
.env里设:RESEND_API_KEY=re_xxxxxxxxxxxx RESEND_FROM_EMAIL=noreply@yourdomain.com
Option B — SMTP relay(内网/自部署):
适合内网无法访问 api.resend.com,或已经有内部邮件中继(Microsoft Exchange、Postfix、自部署 SendGrid 等)的场景。同时设置时 SMTP_HOST 优先级高于 Resend,验证码和邀请邮件不会走外部 provider。暂不支持 465(SMTPS / 隐式 TLS),请使用 25 或 587。
匿名 Exchange 内部 relay(端口 25) —— 主机按 IP 被信任,不需要凭据:
SMTP_HOST=exchange.internal.example.com
SMTP_PORT=25
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_TLS_INSECURE=false
RESEND_FROM_EMAIL=noreply@yourdomain.com # 同时作为 SMTP From: 头认证提交(端口 587,STARTTLS) —— relay 需要 service account;服务端 advertise STARTTLS 时自动升级:
SMTP_HOST=smtp.internal.example.com
SMTP_PORT=587
SMTP_USERNAME=lehuo
SMTP_PASSWORD=...
SMTP_TLS_INSECURE=false # 仅在私有 CA / 自签证书时改成 true
RESEND_FROM_EMAIL=noreply@yourdomain.com之后重启:docker compose -f docker-compose.selfhost.yml restart backend。重启时 backend 会打印当前选择的 provider(EmailService: SMTP relay … / Resend API / DEV mode),密码不会被记录,所以这行截图给同事是安全的。
更多 auth 配置(OAuth、注册白名单)以及完整的 SMTP 变量说明见 登录与注册配置 和 环境变量。
4. 首次登录 + 创建工作区
- 输入你的邮箱
- 从你配置的邮件后端(Resend 或 SMTP relay)收到的邮件里拿验证码;两者都没配的话,从 server 容器的 stdout 里抄
[DEV] Verification code这行 - 不要直接使用
888888;只有在非 production 私有实例上显式设置LEHUO_DEV_VERIFICATION_CODE=888888后它才会生效 - 登录后创建第一个工作区
5. 连接命令行工具到你自己的 server
命令行装法和 Cloud 快速上手 → 2. 装命令行工具 一样——用安装脚本或从 agents.lehuo.app/download 下载。
5a. 同一台机器
CLI 和 server 在同一台机器上时,默认参数就够用:
lehuo setup self-host会自动连 http://localhost:8080(backend)+ http://localhost:3000(frontend),引导你在浏览器里登录、把 PAT 存到本地、自动启动守护进程。
5b. 跨机访问:用反向代理把服务挡在前面
因为 compose 默认只监听 127.0.0.1,从别的机器跑的 daemon 是连不上 http://<server-ip>:8080 的——这也是有意为之,否则默认 JWT_SECRET 等于直接暴露在公网。正确做法是在 server 上跑一个反向代理(Caddy / nginx / Cloudflare Tunnel),由它终结 TLS,再反代到 127.0.0.1:8080(backend)和 127.0.0.1:3000(frontend)。然后把 CLI 指到公开的 HTTPS 域名:
lehuo setup self-host \
--server-url https://<你的域名> \
--app-url https://<你的域名>最小可用的 Caddyfile,单域名同时挂前后端(带 WebSocket 转发,daemon 和网页端都依赖):
lehuo.example.com {
# WebSocket 路由——必须在 catch-all 之前
@ws path /ws /ws/*
handle @ws {
reverse_proxy 127.0.0.1:8080 {
flush_interval -1
}
}
# Backend API
handle /api/* {
reverse_proxy 127.0.0.1:8080
}
# 其它请求 → 前端
reverse_proxy 127.0.0.1:3000
}代理起好之后,记得在 server 的 .env 里把 FRONTEND_ORIGIN 设成 https://lehuo.example.com 并重启后端,否则 WebSocket 的 origin 校验会把浏览器拒掉(见 故障排查 → WebSocket 连不上)。
Cloudflare Tunnel 也是不错的选择——它直接给一个公开域名 + TLS,host 上不用对外暴露任何端口。Nginx 也能做(分 app. / api. 两个域名 + proxy_set_header Upgrade 转 WebSocket),关键就是终结 TLS、并在 /ws 上转发 Upgrade 头。
6. 创建智能体 + 分配第一个任务
流程和 Cloud 一样——见 Cloud 快速上手 → 5-6 步。
7. 调度用量汇总任务(Usage Dashboard 必需)
Usage / Runtime 看板读的是派生表 task_usage_hourly,需要 rollup_task_usage_hourly() 周期性运行才能填充。默认的 pgvector/pgvector:pg17 镜像不带 pg_cron,后端进程内部也不会跑这个 rollup——什么都没调度的话,原始 task_usage 行会继续写入,但 dashboard 会一直停在 0,不会报错。
三种支持路径,三选一即可。
Option A —— 外部 cron / systemd-timer(最简单)。 在任意外部调度器上每 5 分钟跑一次 rollup。函数是幂等的、按 watermark 推进,丢一两个 tick 下次能补上:
# /etc/cron.d/lehuo-rollup —— 每 5 分钟跑一次
*/5 * * * * root docker compose -f /path/to/lehuo/docker-compose.selfhost.yml \
exec -T postgres psql -U lehuo -d lehuo \
-c "SELECT rollup_task_usage_hourly();" >/dev/nullOption B —— 换成自带 pg_cron 的 Postgres 镜像。 把 docker-compose.selfhost.yml 里的 pgvector/pgvector:pg17 换成同时带 pgvector 和 pg_cron 的镜像(比如 supabase/postgres,或自己 build 一份),把 shared_preload_libraries=pg_cron 配上、重启 Postgres,然后注册一次任务:
CREATE EXTENSION IF NOT EXISTS pg_cron;
SELECT cron.schedule(
'rollup_task_usage_hourly',
'*/5 * * * *',
$$SELECT rollup_task_usage_hourly()$$
);Option C —— 先回填历史(升级路径)。 如果你是从 v0.3.4 升级到 v0.3.5+ 且数据库里已经有 task_usage 行,migration 103 会以 refusing to drop legacy daily rollups: ... 报错并中止 migrate up,直到 hourly 表被 seed 过。先跑一次内置的 backfill 命令,然后再配 Option A 或 Option B 让新数据持续流进来:
docker compose -f docker-compose.selfhost.yml exec backend \
./backfill_task_usage_hourly --sleep-between-slices=2s--sleep-between-slices=2s 用来在繁忙的数据库上限制读压力。回填跑完后重启后端容器(migration 在启动时自动跑),升级就能继续。
完整参考(含 Kubernetes CronJob 模板和升级顺序)见仓库的 SELF_HOSTING_ADVANCED.md → Usage Dashboard Rollup。
Kubernetes 部署(替代方案)
如果你已经在跑 Kubernetes 集群,仓库里也带了一个 Helm chart,路径 deploy/helm/lehuo/。它就是 k8s 版的 make selfhost——一样的 backend 镜像、frontend 镜像、pgvector/pgvector:pg17 Postgres,封装成 Deployment / Service / Ingress,再加上一个由 values.yaml 渲染出来的 ConfigMap。这套 chart 是按照 k3s + Traefik + local-path 写的,集群里只要有 Ingress controller 和默认的 ReadWriteOnce StorageClass 就能跑,其他类型的集群稍微改一改也能用。
这个 chart 不会模板化任何敏感值。它通过 name 引用一个叫 lehuo-secrets 的 Secret,所以真实的 JWT / DB / Resend / Google 密钥永远不用进 git,也不用进 values.yaml。先用 kubectl 一次性把命名空间和 Secret 建好:
kubectl create namespace lehuo
kubectl -n lehuo create secret generic lehuo-secrets \
--from-literal=JWT_SECRET="$(openssl rand -hex 32)" \
--from-literal=POSTGRES_PASSWORD="$(openssl rand -hex 16)" \
--from-literal=RESEND_API_KEY="" \
--from-literal=GOOGLE_CLIENT_SECRET="" \
--from-literal=CLOUDFRONT_PRIVATE_KEY="" \
--from-literal=LEHUO_DEV_VERIFICATION_CODE=""再装 chart:
# Download the self-host bundle from https://agents.lehuo.app/download
curl -fsSL https://agents.lehuo.app/download/selfhost.tar.gz -o lehuo-selfhost.tar.gz
tar -xzf lehuo-selfhost.tar.gz
cd lehuo
helm install lehuo deploy/helm/lehuo -n lehuo默认主机名是 lehuo.dev.lan(web)和 api.lehuo.dev.lan(backend)。把它们加进 /etc/hosts(或者本地 DNS),指向任意一个 Ingress 可达的节点 IP 就行。要换主机名,就把 deploy/helm/lehuo/values.yaml 复制一份,改掉 ingress.frontend.host / ingress.backend.host,再把 backend.config.appUrl / frontendOrigin / localUploadBaseUrl / googleRedirectUri 改成相应的地址,然后 helm install ... -f my-values.yaml。
冷集群上 backend 可能会 Running 但 Not Ready 持续几分钟,等 Postgres 起来并跑完 migration——startupProbe 会兜住这一段,pod 不会被 liveness 重启。等它 Ready 之后:
curl -H "Host: api.lehuo.dev.lan" http://<ingress-ip>/healthz
# {"status":"ok","checks":{"db":"ok","migrations":"ok"}}然后浏览器打开 http://lehuo.dev.lan,回到上面的 第 4 步——首次登录 继续。命令行连到你的 Ingress 主机:
lehuo setup self-host \
--server-url http://api.lehuo.dev.lan \
--app-url http://lehuo.dev.lan只想拉最新镜像、不动 chart:kubectl -n lehuo rollout restart deploy/lehuo-backend deploy/lehuo-frontend。要锁到某个 lehuo Agents 版本,就在 values 文件里设 images.backend.tag / images.frontend.tag,再 helm upgrade。helm -n lehuo uninstall lehuo 只删工作负载,PVC 和 Secret 都保留;kubectl delete namespace lehuo 才会全清。
完整参考——三种登录方式、为了绕过 web 镜像 build-time 写死的 REMOTE_API_URL 而加的 backend ExternalName 别名、资源限制、TLS——都在仓库的 SELF_HOSTING.md。
常见问题
- 后端起不来:看容器日志
docker compose -f docker-compose.selfhost.yml logs backend;常见是.env里DATABASE_URL或JWT_SECRET有问题 - 验证码收不到:没配任何邮件后端(Resend 和 SMTP 都没设) → 从
docker compose logs backend里找[DEV] Verification code - WebSocket 连不上:公网部署必须设
FRONTEND_ORIGIN成你真实的前端域名;见 故障排查 → WebSocket 连不上 - Usage / Runtime 看板一直是 0:没人调度
rollup_task_usage_hourly()—— 见上面的 第 7 步 和 故障排查 → Usage 看板一直是 0 migrate up报refusing to drop legacy daily rollups:v0.3.4 → v0.3.5+升级路径的 fail-closed guard。先跑backfill_task_usage_hourly—— 见 第 7 步 → Option C