Discourse + AI 社区部署教程 | 搭建 Agent 议事厅
最近在搭一个 Agent 社区 / 机器人协作社区。核心想法不是再做一个聊天群,而是有一个更像“议事厅”的地方:人类可以发布任务、讨论设计、沉淀决策;机器人可以被 @、被分配任务、补充资料、执行工具,并把结果写回同一个讨论串。
这次先落地了第一步:在服务器上部署一套 Discourse + Discourse AI。
先澄清一下:这里部署的是 Discourse,一个可以自托管的论坛/社区系统;不是 Discord。Discord 是外部 SaaS 聊天平台,后续可以作为实时入口接进来,但不是本文的部署主体。
最终入口:

目标
这次部署的目标是:
- 使用官方推荐的
discourse_docker部署 Discourse。 - 不让 Discourse 容器直接占用公网
80/443,而是绑定本机端口,再由 Nginx 反代。 - 接入现有的 local/public 双域名入口。
- 启用 Discourse AI,并接入一个 OpenAI-compatible LLM。
- 初始化 Agent 社区需要的分类和标签。
- 先进入可验证的 staging 状态,SMTP 后续再补。
完成后的状态:
| 项目 | 结果 |
|---|---|
| Discourse | 已部署 |
| 运行方式 | 官方 discourse_docker |
| 容器名 | app |
| 端口绑定 | 127.0.0.1:3088 -> container:80 |
| 反向代理 | Nginx local vhost |
| Public 入口 | 由已有 public-entry 自动转回 local |
| Discourse AI | 已启用 |
| AI Bot | 已启用 |
| LLM | ark-code-latest |
| SMTP | 尚未配置,当前为 staging/bootstrap 状态 |
目录规划
长期服务不要放在一次性 project 目录里。这里把 Discourse 放到 ChatArch 的长期服务目录:
1 | /home/zhihong/.chatarch/discourse/ |
目录结构大致是:
1 | /home/zhihong/.chatarch/discourse/ |
部署过程、报告、脚本和验证日志则放在项目目录里:
1 | /home/zhihong/Playground/projects/agent-community/07-27-discourse-ai-service/ |
这样区分之后,长期服务和一次性任务记录不会混在一起。
前置条件
这台服务器已有:
- Ubuntu 22.04 系列环境。
- Docker 27.x。
- Docker Compose v2。
- Nginx。
- 已有 wildcard 证书。
- 已有 local/public 双域名入口机制。
Discourse 官方生产部署推荐走 Docker,也就是 discourse_docker。源码裸部署理论上可以,但 Rails、PostgreSQL、Redis、Sidekiq、Nginx、assets、升级和迁移都要自己维护,不适合作为第一版长期服务。
第一步:准备服务目录
1 | mkdir -p /home/zhihong/.chatarch/discourse/{secrets,logs,backups} |
然后 clone 官方 Docker 部署仓库:
1 | git clone https://github.com/discourse/discourse_docker.git docker |
如果已经 clone 过,就只需要更新或检查当前状态。
第二步:生成 app.yml
Discourse 的 Docker 部署核心配置在:
1 | /home/zhihong/.chatarch/discourse/docker/containers/app.yml |
这份配置基于官方 samples/standalone.yml 修改。关键点是:
- 只把容器端口绑定到本机。
- 使用 existing Nginx 做 HTTPS 终止和反代。
- 开启 bundled Discourse AI plugin。
- 暂时跳过 SMTP bootstrap。
- 后续把 canonical hostname 设置为 public 域名。
示意配置如下:
1 | templates: |
当前 Discourse AI 已经在 Discourse 主仓库的 plugins/discourse-ai 中维护;如果你的版本没有 bundled plugin,需要按官方当前文档添加插件,不要照抄旧的 standalone discourse-ai 仓库。关键原则是不把 API key、SMTP 密码、admin password 写进普通项目文档。
第三步:bootstrap 并启动容器
执行官方 launcher:
1 | cd /home/zhihong/.chatarch/discourse/docker |
启动后验证容器:
1 | docker ps --filter name=app --format 'table {{.Names}}\t{{.Ports}}\t{{.Status}}' |
这次结果是:
1 | container: app |
本机探测:
1 | curl --noproxy '*' -I http://127.0.0.1:3088/ |
应返回 HTTP/1.1 200 OK 或在 Rails 启动初期短暂返回 503,稍等后恢复。
第四步:配置 Nginx local vhost
我们的 WZHECNU 域名入口有一套约定:
1 | <service>.local.wzhecnu.cn -> 本机 local nginx vhost |
也就是说,普通服务接入时,只应该在本机 Nginx 添加 local vhost,不需要为每个服务改 DNS、改 FRP、改 public tunnel,也不应该在本机 Nginx 里额外写 public host。
Discourse 的 Nginx 配置路径:
1 | /etc/nginx/sites-available/discourse-local.conf |
核心配置:
1 | server { |
然后测试并 reload:
1 | sudo nginx -t |
本机 local 验证:
1 | curl --noproxy '*' -k -I \ |
Public 验证:
1 | curl --noproxy '*' -k -I https://discourse.public.wzhecnu.cn/ |
第五步:处理 local/public 和 canonical hostname
这里踩了一个坑。
一开始只配置了 discourse.local.wzhecnu.cn,但本机 HTTP vhost 写成了:
1 | return 301 https://$host$request_uri; |
public 请求进入 public-entry 后,已经正确回到了本机 local vhost。但这个 local vhost 又把浏览器重定向到:
1 | https://discourse.local.wzhecnu.cn/ |
如果访问者不在 local/overlay 网络里,就访问不了这个 local 域名。修复方法不是把 public host 写进本机 Nginx,而是让 local HTTP vhost 直接 proxy 到 Discourse。
第二个坑是 Discourse 的 canonical URL。
Discourse 不是只用相对链接的简单应用,它会根据 DISCOURSE_HOSTNAME 生成 RSS、OpenSearch、图片、topic、用户页等绝对链接。如果这个值是:
1 | DISCOURSE_HOSTNAME=discourse.local.wzhecnu.cn |
那么 public 页面里也会出现一堆 local 绝对链接。因此最终把它改成:
1 | DISCOURSE_HOSTNAME=discourse.public.wzhecnu.cn |
然后重新创建容器,让环境变量生效:
1 | cd /home/zhihong/.chatarch/discourse/docker |
数据放在 /shared 持久卷里,重新创建容器不会删除站点数据。
验证:
1 | docker exec -u discourse -i app bash -lc ' |
结果应类似:
1 | {"hostname":"discourse.public.wzhecnu.cn","base_url":"https://discourse.public.wzhecnu.cn"} |
第六步:创建管理员账号
因为 SMTP 还没配置,不能依赖邮件完成管理员注册。可以用 Rails runner 创建/激活管理员账号。
管理员密码只存放在服务器本地 secret 文件里:
1 | /home/zhihong/.chatarch/discourse/secrets/admin.env |
注意:不要把密码、API key、SMTP password 写进博客、项目文档或聊天记录。
管理员最终状态需要满足:
1 | active: true |
第七步:启用 Discourse AI
Discourse AI 是 Discourse 官方生态里的 AI 插件能力集合。它适合做:
- AI Bot / Persona
- 写作辅助
- Topic 总结
- 审核/治理辅助
- 后续接 embedding 后的语义搜索
它不是完整的多 Agent 调度器,不能替代 Agent Router。
这次配置结果:
1 | { |
LLM 使用 ChatArch 的 OpenAI-compatible profile,API key 存在 Discourse 的 AiSecret,不写进普通文件。容器内验证 LLM 调用返回:
1 | {"status": 200, "ok": true, "has_choices": true} |
也就是说,Discourse 容器内部能访问模型 endpoint。
第八步:初始化 Agent 社区分类和标签
初始分类:
1 | Tasks |
初始标签:
1 | task |

这些分类和标签的目标是让 Discourse 不只是“聊天论坛”,而是逐步成为一个面向任务和产物的 Agent 社区:
1 | Discourse Topic/Post/Tag/Mention |
第九步:系统调优
Bootstrap 时 Redis 提示 vm.overcommit_memory 未开启。按 Discourse/Redis 长期运行建议,持久化:
1 | echo 'vm.overcommit_memory = 1' | sudo tee /etc/sysctl.d/99-chatarch-discourse.conf |
验证:
1 | sysctl -n vm.overcommit_memory |
应返回:
1 | 1 |
第十步:验证页面
最终验证:
1 | curl --noproxy '*' -k -I https://discourse.public.wzhecnu.cn/ |
页面标题:
1 | ChatArch Agent Community |
公开页面:
- https://discourse.public.wzhecnu.cn/
- https://discourse.public.wzhecnu.cn/t/agent-ai/14
- https://discourse.public.wzhecnu.cn/t/agent-discourse-ai/15

当前还没完成的部分
这套部署现在是可用的 staging / internal 状态,但还不是完整生产状态。
最重要的缺口是 SMTP。
当前使用了:
1 | DISCOURSE_SKIP_EMAIL_SETUP=1 |
这意味着:
- 页面能访问。
- 管理员能登录。
- AI 能力能用。
- Topic / 分类 / 标签能用。
但这些还不能算生产完成:
- 邀请邮件
- 通知邮件
- 找回密码
- 邮件确认
后续 SMTP 可以有几种路线:
- 使用现有可靠外部 SMTP。
- 使用企业/个人邮箱的 SMTP bootstrap。
- 另行部署 DMS/docker-mailserver 作为长期邮件服务。
如果走 DMS,还需要 MX、A、PTR/rDNS、SPF、DKIM、DMARC、TLS 证书和端口策略,这应该作为另一个独立任务做。
总结
这次部署的关键点不是“把 Discourse 跑起来”这么简单,而是把它放进 ChatArch 的长期服务和 Agent 社区架构里:
1 | Discourse = 结构化讨论社区和 canonical topic space |
这一步完成后,我们已经有了一个可以真实发帖、讨论、沉淀和测试 AI 能力的社区入口。下一步可以开始做最小 Agent Router:监听 Discourse webhook,识别 @researcher 或 @scribe,调用 Agent Runtime,再把结果写回原 topic。







