Ubuntu 20.04 部署 Discourse 论坛完整实践指南

发布时间:2026/6/23 10:06:35
Ubuntu 20.04 部署 Discourse 论坛完整实践指南 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_hierarchy0强制降级但这属于“打补丁”违背了“最小改动原则”。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-server6.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 内置了 Lets 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 确保系统更新到最新状态并禁用 swapDiscourse 的容器对内存管理极为敏感。它内部的 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 记录以验证邮件配置、向 Lets 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 预留足够磁盘空间与 InodesDiscourse 的数据增长模式很特殊它不是“大文件”型如视频网站而是“海量小文件”型。每个用户头像、每张上传图片、每个 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默认是 16KB2.5 配置防火墙UFW开放必要端口Ubuntu 20.04 默认安装 UFWUncomplicated Firewall。Discourse 作为 Web 服务需要暴露 80HTTP和 443HTTPS端口。但很多人忽略了./discourse-setup脚本在配置 Lets 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注意不要开放 22SSH以外的其他端口。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 验证系统时间与时区准确性Lets 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就找不到配置文件会报错Cant 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: adminexample.com # SMTP 服务器配置以腾讯企业邮箱为例 SMTP_ADDRESS: smtp.exmail.qq.com SMTP_PORT: 587 SMTP_USER_NAME: adminexample.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 由 Lets 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 # 启用 Lets 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: adminexample.com # 设置站点名称 DISCOURSE_TITLE: My Awesome Community实操心得hostname必须是你购买的、已正确解析到这台服务器 IP 的域名。localhost、127.0.0.1、内网 IP如192.168.1.100全部无效Lets 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): adminexample.com # SMTP server address: smtp.exmail.qq.com # SMTP port: 587 # SMTP username: adminexample.com # SMTP password: [输入你的应用专用密码] # ...为什么推荐手动编辑app.yml而不是完全依赖./discourse-setup因为./discourse-setup是一个“最小化配置”工具。它只会设置最基础的字段而像DISCOURSE_FORCE_HTTPS: true强制跳转 HTTPS、DISCOURSE_CDN_URL: https://cdn.example.comCDN 加速、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 # 查看实时日志按 CtrlC 退出 ./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释放磁盘空间建议每月执行一次手动触发 Lets 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 证书自动续期失败的终极排查法Lets 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 挑战# 进入容器模拟 Lets Encrypt 的 HTTP-01 检查 curl -v http://forum.example.com/.well-known/acme-challenge/test # 正常应返回 404文件不存在但如果返回 502 或超时说明 nginx 配置或网络有问题第三层验证 DNS 与端口连通性# 在宿主机上用 Lets Encrypt 的官方检查工具 sudo apt install curl jq curl -s https://acme-v02.api.letsencrypt.org/d