1. 项目概述:在 Ubuntu 20.04 上部署 Discourse 社区论坛,为什么这件事值得花两小时认真做一遍?
Discourse 是目前全球范围内最成熟、最活跃的开源社区论坛系统之一,它不是 WordPress 插件那种“加个论坛模块”的轻量方案,而是从消息推送、实时协作、内容审核、用户成长体系到反垃圾机制全部重头设计的现代讨论平台。我最早在 2016 年用它给一个开源硬件项目搭内部协作站,当时还手写 nginx 配置、手动编译 Ruby、为 PostgreSQL 调内存参数;到了今天,Discourse 官方早已把整套部署逻辑彻底容器化——核心就是一套经过千锤百炼的 Docker Compose 编排方案。而 Ubuntu 20.04 这个版本,恰好是 LTS(长期支持)周期中承上启下的关键节点:它自带较新的内核(5.4)、默认启用 systemd-resolved、对 cgroups v2 的支持趋于稳定,同时又避开了 22.04 中某些激进的网络栈变更。换句话说,它不是“最新”,但它是“最稳”——尤其适合跑 Discourse 这种对 I/O 延迟、DNS 解析稳定性、文件系统一致性要求极高的服务。
你可能会问:现在不是有现成的 SaaS 托管服务吗?Discourse 官方也提供托管版。没错,但托管版意味着你无法深度定制邮件模板、无法接入自有 LDAP/SSO 系统、无法修改主题渲染逻辑、无法审计日志留存策略,更无法把用户行为数据和你的数据分析平台打通。更重要的是,Discourse 的核心哲学是“可审计、可迁移、可重建”。它的整个配置不是藏在数据库某个 blob 字段里,而是明文写在app.yml文件中;它的所有数据不是耦合在某个神秘的二进制格式里,而是清晰分离为 PostgreSQL 数据库、Redis 缓存、上传附件(默认存在本地/shared目录下,可轻松挂载到 NFS 或对象存储)。这种设计,让一次成功的本地部署,本质上就是一次完整的“基础设施即代码”(IaC)实践演练。
所以,这不是一个“装个论坛”的简单任务,而是一次对 Linux 系统管理、Docker 容器编排、网络服务调试、SSL 证书生命周期管理的综合检验。你不需要是 DevOps 工程师,但你需要理解:为什么必须关闭 swap?为什么docker-compose启动后要等 3–5 分钟才能访问?为什么第一次登录时邮箱收不到激活链接?这些不是 bug,而是 Discourse 在 Ubuntu 20.04 这个特定土壤上生长时,必然要与之对话的底层逻辑。接下来的内容,我会带你从零开始,不跳过任何一个看似“理所当然”的步骤,把每一个docker exec命令背后的意图、每一次systemctl restart的影响范围、每一条日志里隐藏的线索,都掰开揉碎讲清楚。这不是教程,这是我在过去三年里,为 7 个不同客户、3 所高校、2 个开源组织部署 Discourse 时,踩过的坑、记下的笔记、验证过的最优解。
1.1 核心需求解析:我们到底在部署什么,又在规避什么?
Discourse 的官方安装文档(https://github.com/discourse/discourse/blob/main/docs/INSTALL-cloud.md)其实非常精炼,但它默认读者已经具备“Linux 服务器基础运维常识”。而现实是,很多尝试者卡在第一步——连git clone都失败,或者./discourse-setup脚本报错后不知所措。问题不在于脚本本身,而在于我们没搞清这个部署包的真实构成。
它不是一个传统意义上的“软件包”,而是一个高度封装的运维工作流。整个discourse_docker仓库(https://github.com/discourse/discourse_docker)包含三个核心层:
最外层:
launcher脚本
这是一个用 Bash 写的“指挥官”,它不直接启动容器,而是负责检查系统环境(如 Docker 版本、可用内存、swap 状态)、生成最终的docker-compose.yml、调用docker-compose执行构建与启动,并提供rebuild、stop、logs等便捷子命令。它的存在,是为了屏蔽掉docker-compose build和docker-compose up -d之间那些容易出错的手动衔接。中间层:
containers/app.yml模板
这是整个部署的“心脏”。它不是配置文件,而是一个 Jinja2 模板(虽然扩展名是.yml),里面混杂了 shell 变量(如${DISCOURSE_HOSTNAME})、条件判断(如- "templates/web.china.template.yml")和注释说明。当你运行./discourse-setup,它会读取这个模板,结合你输入的域名、邮箱等信息,生成一份真正的、可执行的docker-compose.yml。很多人误以为改了app.yml就生效了,其实不然——必须./launcher rebuild app才会重新渲染并应用。最内层:官方镜像与依赖服务
Discourse 官方维护着一套完整的镜像链:discourse/base(基于 Ubuntu 20.04 构建的基础镜像,预装了 Ruby、Node.js、PostgreSQL 客户端等)、discourse/postgres(定制版 PostgreSQL,优化了 WAL 日志和连接池)、discourse/redis(精简版 Redis,去除了不必要的模块)。它们共同构成了一个“自洽的运行时环境”,完全不依赖宿主机的 Ruby 或 Python 版本。这也是为什么你可以在一台只装了 Docker 的干净 Ubuntu 20.04 上,几分钟内就拉起一个功能完整的论坛。
因此,我们的核心需求不是“安装 Discourse”,而是:
✅确保宿主机(Ubuntu 20.04)满足容器运行的底层约束(如内核参数、cgroup 权限、DNS 可靠性);
✅正确生成并理解app.yml中每一行配置的真实作用域(哪些影响 Web 层,哪些影响邮件发送,哪些决定备份策略);
✅掌握launcher工作流的完整生命周期(从bootstrap到rebuild,再到enter进入容器调试);
✅建立一套可持续的运维习惯(如定期./launcher cleanup清理旧镜像、用./launcher logs app实时追踪错误、通过psql直连数据库执行紧急修复)。
这四点,缺一不可。跳过任何一点,你得到的都不是一个“能用”的论坛,而是一个随时可能在凌晨三点因磁盘爆满或证书过期而失联的定时炸弹。
1.2 为什么 Ubuntu 20.04 是当前最稳妥的选择?
网上关于 “ubuntu没声音20.04”、“ubuntu 20.04 搜狗输入法” 的搜索热度,恰恰印证了它作为桌面系统的广泛普及。但对服务器场景而言,它的价值远不止于此。我们来拆解几个关键点:
内核与 cgroups 兼容性
Ubuntu 20.04 默认使用 Linux kernel 5.4。这个版本对 cgroups v1 的支持极其成熟,而 Discourse 的官方基础镜像discourse/base是明确基于 cgroups v1 构建的。如果你强行在 Ubuntu 22.04(默认 cgroups v2)上部署,会遇到Failed to create cgroup类错误,因为discourse/base镜像里的systemd初始化进程无法在 v2 环境下正确接管服务。虽然可以通过内核参数systemd.unified_cgroup_hierarchy=0强制降级,但这属于“打补丁”,违背了“最小改动原则”。Docker 生态的黄金匹配期
Ubuntu 20.04 发布于 2020 年 4 月,而 Docker Engine 20.10(当前主流稳定版)的首个 LTS 版本发布于 2020 年 12 月。两者在overlay2存储驱动、seccomp安全策略、--cgroup-parent参数支持等方面,经过了长达两年的线上大规模验证。相比之下,Ubuntu 22.04 预装的 Docker 20.10.12 虽然也能用,但在处理 Discourse 大量小文件(如 emoji 图标、用户头像缩略图)的 I/O 时,偶发出现inotify watch limit reached报错,需要额外调大fs.inotify.max_user_watches,而 20.04 几乎无需此操作。PostgreSQL 与 Redis 的版本锁定
Discourse 官方明确声明:仅支持 PostgreSQL 12+ 和 Redis 6.0+。Ubuntu 20.04 的 APT 仓库中,postgresql-12和redis-server(6.0.9)是默认提供的稳定版本,无需添加第三方 PPA 或手动编译。而 Ubuntu 18.04 的postgresql-10已被 Discourse 3.0+ 彻底弃用;Ubuntu 22.04 的postgresql-14虽然兼容,但 Discourse 的db:migrate迁移脚本在某些边缘 case 下(如从老版本升级)会出现column "xxx" does not exist错误,根源在于 PG 14 对GENERATED ALWAYS AS语法的 stricter 解析。20.04 的 PG 12,是经过 Discourse 团队 CI/CD 流水线每日验证的“事实标准”。SSL 证书自动续期的可靠性
Discourse 内置了 Let's Encrypt 集成,通过acme.sh脚本实现自动签发与续期。这个流程极度依赖宿主机的cron和systemd-timers的精确触发。Ubuntu 20.04 的systemd版本(245)对 timer 的 jitter 控制非常精准,实测连续 6 个月未出现过acme.sh因时间漂移导致的续期失败。而某些云厂商定制的 Ubuntu 20.04 镜像(如阿里云 ECS 的ubuntu_20_04_x64_20G_alibase_20230323.vhd)会预装cloud-init,它有时会干扰systemd-timers的首次启动顺序,导致acme.sh第一次运行时找不到discourse用户的 home 目录,从而静默失败——这个问题,在标准 Ubuntu 20.04 Server ISO 安装的纯净系统上,从未复现。
所以,选择 Ubuntu 20.04,不是因为“它新”,而是因为它代表了一段被时间验证过的、各组件版本相互咬合的“稳定三角区”。在这个三角区内,Discourse 的launcher脚本可以发挥出 100% 的设计效能,而你,可以把精力聚焦在业务配置上,而不是底层兼容性调试上。
2. 环境准备与前置检查:别急着敲命令,先让系统“准备好迎接 Discourse”
在 Ubuntu 20.04 上部署 Discourse,最大的陷阱不是技术难度,而是“想当然”。很多人复制粘贴curl -L https://raw.githubusercontent.com/discourse/discourse_docker/master/scripts/install | bash就以为万事大吉,结果./discourse-setup卡在Checking for docker...死循环,或者rebuild时提示no space left on device。这些都不是 Discourse 的问题,而是宿主机环境没达到它的“最低健康阈值”。下面这七项检查,我建议你逐条执行、逐条确认,哪怕看起来“多此一举”。
2.1 确保系统更新到最新状态,并禁用 swap
Discourse 的容器对内存管理极为敏感。它内部的 Ruby 进程(Puma)和 Node.js 进程(Sidekiq)会大量使用内存,如果系统启用了 swap,当物理内存不足时,Linux 内核会将部分内存页交换到磁盘,导致 Discourse 的响应延迟飙升(从毫秒级变为秒级),用户点击“发布”按钮后要等 5 秒以上才有反应,体验极差。官方文档明确要求:“Disable swap”。
# 1. 更新系统(这一步不能省!Ubuntu 20.04 的初始镜像可能缺少关键内核补丁) sudo apt update && sudo apt full-upgrade -y sudo reboot # 重启确保新内核生效 # 2. 检查并永久禁用 swap sudo swapoff -a # 编辑 fstab,注释掉所有 swap 行 sudo sed -i '/swap/d' /etc/fstab # 验证:输出应为空 swapon --show提示:有些云服务器(如 AWS EC2)的 AMI 默认不创建 swap 分区,但会配置
zram(压缩内存作为 swap)。请务必运行zramctl查看是否存在 zram 设备,如有,需执行sudo systemctl stop zramswap并禁用对应 service。
2.2 验证 Docker 是否已正确安装且版本达标
Discourse 的launcher脚本对 Docker Engine 版本有硬性要求:必须 >= 20.10.0。低于此版本,docker-compose的profiles功能无法识别,会导致web_only模式启动失败。而 Ubuntu 20.04 的 APT 仓库默认提供的是 Docker 19.03,必须手动升级。
# 检查当前版本 docker --version # 如果显示 19.03.x,则必须升级 curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh sudo usermod -aG docker $USER # 退出当前 SSH 会话,重新登录,使 group 生效 # 验证:此时 docker --version 应显示 20.10.x 或更高 docker --version注意:不要使用
snap install docker。Snap 包的 Docker 运行在严格沙箱中,其docker.sock路径与launcher脚本预期的/var/run/docker.sock不一致,会导致launcher无法与 Docker daemon 通信,报错Cannot connect to the Docker daemon at unix:///var/run/docker.sock.
2.3 检查 DNS 解析是否可靠(关键!)
Discourse 在启动过程中,会频繁进行 DNS 查询:检查域名 A 记录、查询 MX 记录以验证邮件配置、向 Let's Encrypt 的 ACME 服务器发起 HTTPS 请求。如果宿主机的 DNS 解析不稳定,./launcher bootstrap app会卡在Ensuring web is started...阶段,超时后报错Timeout waiting for web to respond。
# 1. 检查当前 DNS 设置 cat /etc/resolv.conf # 2. 测试解析速度与稳定性(对 discourse.org 和 letsencrypt.org) time dig +short discourse.org @1.1.1.1 time dig +short acme-v02.api.letsencrypt.org @1.1.1.1 # 3. 如果发现超时或响应慢,强制修改为 Cloudflare DNS echo "nameserver 1.1.1.1" | sudo tee /etc/resolvconf/resolv.conf.d/head sudo resolvconf -u实操心得:我曾在一个使用国内某运营商 DNS 的 VPS 上部署,
dig测试平均耗时 800ms,./launcher bootstrap总是失败。切换到1.1.1.1后,耗时降至 20ms,一次成功。这不是玄学,Discourse 的启动脚本内部有严格的 30 秒超时限制,DNS 响应慢 1 秒,就可能让整个流程中断。
2.4 预留足够磁盘空间与 Inodes
Discourse 的数据增长模式很特殊:它不是“大文件”型(如视频网站),而是“海量小文件”型。每个用户头像、每张上传图片、每个 emoji 表情,都会被缩放为多个尺寸(2x,1x,mobile),并以独立文件形式存储在/var/discourse/shared/standalone/uploads/下。一个拥有 1000 用户、日均发帖 50 篇的社区,一年下来会产生超过 200 万个文件。这意味着,你不仅要看df -h的剩余空间,更要看df -i的剩余 inodes。
# 检查磁盘空间(建议至少 20GB 可用) df -h /var # 检查 inodes 使用率(建议剩余 > 30%) df -i /var # 如果 inodes 接近 100%,即使空间充足,Discourse 也会因无法创建新文件而崩溃 # 解决方案:重新格式化分区,使用更高 inode ratio # mkfs.ext4 -i 4096 /dev/sdb1 # 将每 4KB 分配一个 inode(默认是 16KB)2.5 配置防火墙(UFW)开放必要端口
Ubuntu 20.04 默认安装 UFW(Uncomplicated Firewall)。Discourse 作为 Web 服务,需要暴露 80(HTTP)和 443(HTTPS)端口。但很多人忽略了./discourse-setup脚本在配置 Let's Encrypt 时,会临时开启 80 端口用于 HTTP-01 挑战,如果 UFW 拦截了,证书签发就会失败。
# 开放 80 和 443 sudo ufw allow 80/tcp sudo ufw allow 443/tcp # 确保 UFW 已启用 sudo ufw enable # 验证规则 sudo ufw status verbose注意:不要开放 22(SSH)以外的其他端口。Discourse 的 PostgreSQL 和 Redis 容器默认只监听
127.0.0.1:5432和127.0.0.1:6379,它们与 Web 容器在同一 Docker 网络内通信,无需暴露给外部。开放这些端口是严重的安全风险。
2.6 创建专用部署目录并设置权限
Discourse 的launcher脚本要求所有操作必须在一个非 root 用户的家目录下进行。它会自动创建shared目录存放数据,并赋予discourse用户所有权。如果你用root用户执行git clone,后续./launcher rebuild会因权限问题失败。
# 创建专用用户(推荐,避免污染 root 环境) sudo adduser --disabled-password --gecos "" discourse sudo usermod -aG docker discourse # 切换到 discourse 用户 sudo su - discourse # 创建部署目录(路径固定,不可更改) mkdir -p /var/discourse cd /var/discourse2.7 验证系统时间与时区准确性
Let's Encrypt 的证书有效期只有 90 天,其签发和验证过程极度依赖准确的 UTC 时间。如果服务器时间偏差超过 5 分钟,ACME 协议会直接拒绝请求,报错urn:acme:error:rateLimited或Invalid response from http://xxx/.well-known/acme-challenge/xxx。
# 检查当前时间与 NTP 同步状态 timedatectl status # 如果显示 "System clock synchronized: no",则强制同步 sudo timedatectl set-ntp on # 等待 30 秒,再次检查 timedatectl status # 输出应为 "System clock synchronized: yes"完成这七项检查后,你的 Ubuntu 20.04 系统才真正准备好迎接 Discourse。这不是繁琐的仪式,而是为后续 90% 的“莫名失败”提前扫清障碍。记住,Discourse 的设计哲学是“约定优于配置”,它假设你已经完成了这些基础加固。跳过它们,等于在沙滩上盖楼。
3. 核心部署流程详解:从克隆仓库到首次访问,每一步都在做什么?
现在,我们进入真正的部署环节。整个流程分为四个阶段:获取代码 → 配置参数 → 构建镜像 → 启动服务。我会详细解释每个命令背后发生了什么,以及为什么必须按这个顺序执行。
3.1 获取官方 Discourse Docker 仓库
Discourse 的部署代码并不在主项目仓库(discourse/discourse)中,而是在一个独立的、专门为此目的设计的仓库:discourse/discourse_docker。这个仓库只包含launcher脚本、app.yml模板和一些辅助脚本,体积很小(约 2MB),下载极快。
# 确保你在 /var/discourse 目录下(由上一步创建) cd /var/discourse # 克隆官方仓库(注意:不是 discourse/discourse!) git clone https://github.com/discourse/discourse_docker.git . # 验证克隆结果 ls -la # 你应该看到 launcher, containers/, samples/ 等目录为什么是
git clone ... .而不是git clone ... discourse_docker?
因为launcher脚本的硬编码路径是./containers/app.yml。如果你把它克隆到子目录discourse_docker/下,launcher就找不到配置文件,会报错Can't find containers/app.yml。这是一个典型的“路径约定”,也是新手最容易栽跟头的地方。
3.2 配置app.yml:理解每一行配置的真实含义
app.yml是整个部署的灵魂。它位于containers/app.yml,是一个 YAML 格式的模板文件。./discourse-setup脚本会读取它,根据你的输入生成最终的docker-compose.yml。但官方脚本只是“傻瓜式向导”,它无法覆盖所有生产环境需求。因此,我们必须手动编辑它。
# 编辑配置文件 nano containers/app.yml下面,我逐行解读最关键的配置项(只列出必须修改的,其余保持默认):
## this is the all-in-one, standalone, simple setup ## ## After editing this file, you must run `./launcher rebuild app` to apply the changes. # 1. 主机名(必须是公网可解析的域名!) hostname: 'forum.example.com' # 2. 邮箱配置(用于管理员通知、用户注册验证) env: DISCOURSE_DEVELOPER_EMAILS: 'admin@example.com' # SMTP 服务器配置(以腾讯企业邮箱为例) SMTP_ADDRESS: smtp.exmail.qq.com SMTP_PORT: 587 SMTP_USER_NAME: admin@example.com SMTP_PASSWORD: "your_app_password_here" # 注意:这里必须是 SMTP 应用专用密码,不是邮箱登录密码! SMTP_ENABLE_START_TLS: true # 3. 数据库存储位置(默认在 /shared,这是最佳实践) volumes: - volume: host: /var/discourse/shared/standalone guest: /shared # 4. 日志卷(必须挂载,否则容器重启后日志丢失) - volume: host: /var/discourse/shared/standalone/log/var-log guest: /var/log # 5. 关键模板引入(决定 Discourse 的功能集) templates: - "templates/postgres.template.yml" - "templates/redis.template.yml" - "templates/web.template.yml" - "templates/web.ratelimited.template.yml" # 启用速率限制,防刷 # - "templates/web.ssl.template.yml" # 注释掉!SSL 由 Let's Encrypt 自动处理,不要手动指定证书路径 # - "templates/web.letsencrypt.ssl.template.yml" # 同上,不要重复启用 # 6. 额外的安全加固(强烈推荐) params: db_default_text_search_config: "pg_catalog.english" # 7. 环境变量(控制 Discourse 行为) env: LANG: en_US.UTF-8 # 启用 Let's Encrypt 自动证书(关键!) DISCOURSE_SMTP_ADDRESS: ${SMTP_ADDRESS} DISCOURSE_SMTP_PORT: ${SMTP_PORT} DISCOURSE_SMTP_USER_NAME: ${SMTP_USER_NAME} DISCOURSE_SMTP_PASSWORD: ${SMTP_PASSWORD} DISCOURSE_SMTP_ENABLE_START_TLS: ${SMTP_ENABLE_START_TLS} # 设置管理员邮箱(必须与上面的 DISCOURSE_DEVELOPER_EMAILS 一致) DISCOURSE_ADMIN_EMAIL: 'admin@example.com' # 设置站点名称 DISCOURSE_TITLE: 'My Awesome Community'实操心得:
hostname必须是你购买的、已正确解析到这台服务器 IP 的域名。localhost、127.0.0.1、内网 IP(如192.168.1.100)全部无效,Let's Encrypt 不会为你签发证书。- SMTP 密码必须是“应用专用密码”。例如,腾讯企业邮箱需要在邮箱后台开启“IMAP/SMTP 服务”,然后生成一个 16 位的“客户端专用密码”。直接填邮箱登录密码,
./launcher rebuild会成功,但 Discourse 启动后发不出任何邮件,日志里全是535 Login Fail。web.ssl.template.yml和web.letsencrypt.ssl.template.yml只能启用一个。前者是手动指定证书路径,后者是自动申请。同时启用会导致nginx配置冲突,rebuild时会报错nginx: [emerg] SSL_CTX_use_certificate_chain_file("/shared/ssl/forum.example.com.cer") failed (SSL: error:02001002:system library:fopen:No such file or directory)。
3.3 运行./discourse-setup并理解其内部逻辑
./discourse-setup是一个交互式 Bash 脚本,它会引导你输入域名、管理员邮箱、SMTP 信息等。但它的本质,是一个智能的app.yml渲染器。它不会修改你的原始app.yml,而是根据你的输入,生成一个全新的、带变量替换的app.yml,并保存为containers/app.yml(覆盖原文件)。
# 运行向导 ./discourse-setup # 它会依次询问: # Hostname: forum.example.com # Email address for admin account(s): admin@example.com # SMTP server address: smtp.exmail.qq.com # SMTP port: 587 # SMTP username: admin@example.com # SMTP password: [输入你的应用专用密码] # ...为什么推荐手动编辑
app.yml而不是完全依赖./discourse-setup?
因为./discourse-setup是一个“最小化配置”工具。它只会设置最基础的字段,而像DISCOURSE_FORCE_HTTPS: true(强制跳转 HTTPS)、DISCOURSE_CDN_URL: https://cdn.example.com(CDN 加速)、DISCOURSE_LOG_LEVEL: debug(调试日志)等高级选项,它一概不问。这些选项对生产环境至关重要,必须手动添加到env:区块下。
3.4 执行./launcher rebuild app:构建、启动、等待
这是整个流程中最耗时,也最关键的一步。rebuild不是简单的docker-compose up -d,它是一个复合操作,包含以下子步骤:
docker-compose build:根据app.yml中定义的templates,拉取或构建所有依赖镜像(discourse/base、discourse/postgres、discourse/redis、discourse/web)。首次运行会下载约 1.2GB 镜像。docker-compose down:停止并删除所有旧容器(如果存在)。docker-compose up -d:以后台模式启动新容器。wait-for-it.sh:一个内置的等待脚本,它会持续检查http://127.0.0.1:3000是否返回 HTTP 200,直到 Discourse Web 进程完全就绪(通常需要 3–5 分钟)。rake任务执行:自动运行bundle exec rake db:migrate(数据库迁移)和bundle exec rake assets:precompile(前端资源编译)。
# 执行重建(耐心等待 5-10 分钟) ./launcher rebuild app # 查看实时日志(按 Ctrl+C 退出) ./launcher logs app常见误区:很多人看到
Rebuilding app...后,立刻打开浏览器访问http://forum.example.com,页面显示502 Bad Gateway。这是正常的!因为rebuild还在执行第 4 步(等待 Web 就绪),此时nginx容器已经启动,但后端的rails进程还没完全加载完毕。正确的做法是:运行./launcher logs app,观察日志末尾是否出现Started GET "/" for 127.0.0.1 at 2023-10-05 10:20:30 +0000。只有看到这个日志,才表示服务真正可用。
3.5 首次访问与管理员账户创建
当./launcher logs app显示 Web 进程已启动后,你就可以在浏览器中访问https://forum.example.com了。首次访问会自动跳转到管理员注册页面。
- 填写信息:邮箱地址必须与
app.yml中的DISCOURSE_DEVELOPER_EMAILS完全一致(大小写敏感)。 - 设置密码:密码强度要求很高(至少 15 位,含大小写字母、数字、符号)。
- 完成注册:提交后,Discourse 会立即发送一封激活邮件到该邮箱。注意:这封邮件可能被 Gmail 或 QQ 邮箱归类为“推广邮件”或“订阅邮件”,请务必检查垃圾邮件箱。
提示:如果等了 10 分钟还没收到邮件,请立即执行以下诊断:
# 进入容器内部,查看 Sidekiq(异步任务队列)日志 ./launcher enter app tail -f /var/log/sidekiq.log如果日志里有
Net::SMTPAuthenticationError或Connection refused,说明 SMTP 配置错误;如果有550 User not found,说明DISCOURSE_DEVELOPER_EMAILS填错了。
4. 后续运维与故障排查:让 Discourse 稳定运行一年以上的实战技巧
部署成功只是开始。Discourse 是一个活的服务,它会随着时间推移产生日志、缓存、备份,也会遇到证书过期、磁盘占满、插件冲突等问题。下面这些技巧,是我从数百次线上故障中总结出的“保命清单”。
4.1 日常维护命令速查表
| 任务 | 命令 | 说明 |
|---|---|---|
| 查看实时日志 | ./launcher logs app -t | -t参数启用时间戳,便于定位问题发生时间 |
| 进入容器调试 | ./launcher enter app | 进入web容器,可执行psql,rails c,ls等任意命令 |
| 重启服务(不重建) | ./launcher restart app | 适用于修改了 nginx 配置或环境变量后,快速生效 |
| 清理旧镜像与停止的容器 | ./launcher cleanup | 释放磁盘空间,建议每月执行一次 |
| 手动触发 Let's Encrypt 续期 | ./launcher enter app -- bash -c "cd /var/www/discourse && bundle exec rake ssl:renew" | 当证书剩余 < 30 天时,可手动测试续期是否正常 |
4.2 磁盘空间告警与清理策略
Discourse 的日志和上传文件是磁盘空间的两大消耗源。/var/discourse/shared/standalone/log/下的production.log和sidekiq.log会无限增长;/var/discourse/shared/standalone/uploads/下的图片文件永远不会自动删除。
# 1. 设置 logrotate(防止日志撑爆磁盘) sudo nano /etc/logrotate.d/discourse # 添加以下内容: /var/discourse/shared/standalone/log/*.log { daily missingok rotate 30 compress delaycompress notifempty create 644 discourse discourse sharedscripts postrotate /var/discourse/launcher restart app > /dev/null endscript } # 2. 定期清理无用上传(Discourse 2.8+ 内置) # 进入容器,运行 Rails 控制台 ./launcher enter app rails c # 执行以下 Ruby 代码(删除 90 天前未被引用的上传) Upload.where("created_at < ?", 90.days.ago).where("id NOT IN (SELECT upload_id FROM post_uploads UNION SELECT upload_id FROM user_avatars)").delete_all exit4.3 SSL 证书自动续期失败的终极排查法
Let's Encrypt 续期失败是最常见的线上事故。./launcher logs app里通常只显示acme.sh: Renew failed,没有具体原因。以下是分层排查法:
第一层:检查acme.sh日志
./launcher enter app cat /var/www/discourse/shared/standalone/ssl/log/acme.sh.log # 查找关键词 "error", "failed", "timeout"第二层:手动模拟 ACME 挑战
# 进入容器,模拟 Let's Encrypt 的 HTTP-01 检查 curl -v http://forum.example.com/.well-known/acme-challenge/test # 正常应返回 404(文件不存在),但如果返回 502 或超时,说明 nginx 配置或网络有问题第三层:验证 DNS 与端口连通性
# 在宿主机上,用 Let's Encrypt 的官方检查工具 sudo apt install curl jq curl -s "https://acme-v02.api.letsencrypt.org/d