
做研发管理和项目跟踪这几年我先后试用过不少在线协作工具最后又折腾回自建这条路。Redmine 这个开源、基于 Ruby on Rails 的项目管理平台在需求管理、缺陷跟踪、工时登记、Wiki 沉淀这些场景里始终是我最愿意安利给团队的选择。这篇文章就基于一次完整的 Redmine 安装部署实战把环境评估、容器化部署、源码安装、反向代理与 HTTPS、邮件通知、备份维护以及启动失败、邮件发不出这些高频问题的排查思路一次性讲透。如果你正在给团队挑选内部项目管理系统或者被 SaaS 订阅费用卡住想自建这篇内容可以直接当操作手册用。1. 为什么自建 Redmine选型之前先想清楚这几点1.1 Redmine 到底帮你管理了什么Redmine 的核心不是任务看板多炫而是一套围绕项目组织的完整工作流你可以同时维护多个项目每个项目下有独立的模块开关从问题跟踪、Wiki、文档、新闻、论坛到工时登记都能按需启用。问题跟踪支持自定义状态、优先级、指派、关联父任务、子任务、前置任务配合自定义字段后研发流程里的需求、缺陷、测试用例都能用一套数据结构串起来。最值钱的部分其实是时间管理。Redmine 的工时登记表可以按成员、按项目、按日期汇总月底生成人员负载和项目进度报表时比让团队成员去 Excel 里手填省事太多。再加上内置的甘特图跨多个里程碑的排期能不能按期落地看一张图就清楚。对于不想引入重型项目平台的团队Redmine 的开箱配置加少量自定义字段基本就能覆盖日常研发迭代的全部诉求。1.2 自建和 SaaS 工具的取舍我在选型时对比过商业 SaaS也对比过开源生态里的其他工具。SaaS 的好处是零运维但数据在别人手里、按席位收费、自定义能力受平台限制。对于内部系统来说Redmine 的优势在于数据完全自主、功能可以对着插件仓库和源码随意扩展、一次部署终身使用。成本侧去算自建 Redmine 的付出主要是运维一台低配服务器、一个数据库、偶尔的版本升级和备份检查。换来的是没有人数限制、没有功能订阅墙、管理员可以随时改代码改样式。团队规模在几十人以内、流程又偏传统研发模式的场景这套组合非常划算。1.3 什么样的人不该选 Redmine也得说句公道话。如果你的团队重度依赖敏捷看板、每日站会、迭代燃尽图这类动态视图Redmine 原生界面会显得老派需要额外插件才能补齐看板体验。另外它的界面风格偏朴素追求 SaaS 工具那种现代交互的成员可能会抱怨。我见过不少团队把 Redmine 部署完结果因为 UI 接受度不高又迁走的案例。所以选型前先确认你需要的是一套稳定、灵活、能长期积累项目资产的管理系统还是只是想要一个好看的协作看板。前者 Redmine 很合适后者建议另找方案。2. 部署前的环境评估硬件、操作系统与数据库选型2.1 低配服务器也能带得动Redmine 对硬件的要求比多数人想象的低。理论上官方文档给出的最低配置是 1GB 内存但以我的实际经验2 核 CPU、2GB 内存的云主机跑一个十来个并发用户、几万条问题记录的实例响应速度完全可以接受。真正吃内存的是 Ruby 进程和数据库缓存访问量不大时不用焦虑。必须注意的是磁盘 IO。Redmine 的文件目录会随附件增长数据库的 WAL 日志也在持续写入。给宿主机挂一块 SSD 云盘比任何调优都见效快。我踩过一次机械盘带 Redmine 的坑列表页带甘特图时接口能跑到几秒换成 SSD 之后体感立刻不一样。所以预算有限时优先保证磁盘而不是盲目加内存。2.2 数据库选型的实际差异Redmine 官方支持 PostgreSQL、MySQL/MariaDB 和 SQLite仅限开发环境。生产环境中我在不同项目里分别用过 PostgreSQL 和 MariaDB结论是优先选 PostgreSQL。原因有三一是 Redmine 对 PG 的兼容性测试覆盖最完整中文全文检索的配置也更顺手二是 PG 的 mvcc 并发控制在这种多用户写操作频繁的系统里表现更稳三是日常备份工具链如 pg_dump 用起来简单直接。如果你团队现有基础设施已经以 MySQL 为主那用 MariaDB 也完全可以但要注意两点字符集必须设为 utf8mb4否则中文和 emoji 会出现乱码或插入错误sql_mode 建议保持宽松模式避免一些历史表的兼容问题。SQLite 只适合本地体验多人并发访问时会有锁问题不建议进生产。2.3 三条部署路径怎么选我实际接触过的主流部署方式有三种官方仓库包安装、Docker Compose、源码手动安装。官方仓库包如 apt 源里的 redmine 包适合不折腾的系统管理员但它依赖的 Ruby 和 Rails 版本通常落后且定制插件路径不灵活出了问题想改代码很别扭。Docker Compose 是我现在最常用的方案隔离干净、回滚直觉、环境变量管理清晰适合绝大多数中小团队。源码安装改造自由度最大适合需要深度集成或二次开发的场景但升级和依赖维护的成本也最高。选择前想清楚一个问题你们会不会在未来半年内引入自定义插件或修改 Redmine 源码如果会别用发行版自带包直接走 Docker 挂载插件目录或者源码安装。否则后期重来一遍的迁移成本比当时省下的半小时高得多。3. Docker Compose 部署从零到能打开页面3.1 一次性写清楚的 Compose 文件下面这份 compose 配置是我在模拟项目 X 里整理出来的基线版本数据库用 PostgreSQL 16Redmine 镜像固定在 6.0 大版本保证可复现。先把文件保存为docker-compose.ymlservices: redmine: image: redmine:6.0 container_name: redmine restart: unless-stopped environment: REDMINE_DB_POSTGRES: db REDMINE_DB_PORT: 5432 REDMINE_DB_USERNAME: redmine REDMINE_DB_PASSWORD: please-change-this REDMINE_DB_DATABASE: redmine REDMINE_SECRET_TOKEN: 用命令生成的一长串随机值 volumes: - redmine_files:/usr/src/redmine/files - redmine_log:/usr/src/redmine/log - redmine_plugins:/usr/src/redmine/plugins ports: - 127.0.0.1:3000:3000 depends_on: db: condition: service_healthy networks: - redmine-net db: image: postgres:16 container_name: redmine-db restart: unless-stopped environment: POSTGRES_USER: redmine POSTGRES_PASSWORD: please-change-this POSTGRES_DB: redmine TZ: Asia/Shanghai volumes: - db_data:/var/lib/postgresql/data healthcheck: test: [CMD-SHELL, pg_isready -U redmine -d redmine] interval: 5s timeout: 3s retries: 12 networks: - redmine-net volumes: redmine_files: redmine_log: redmine_plugins: db_data: networks: redmine-net:启动之前先用一条命令生成 REDMINE_SECRET_TOKENopenssl rand -hex 64这个值如果不设置Redmine 每次重启可能生成不同的会话签名导致用户登录状态随机失效。把它替换进 compose 文件然后执行docker compose up -d docker compose ps docker logs -f redmine首次启动要等数据库初始化看到日志里出现端口监听提示后浏览器访问http://服务器IP:3000默认账号是 admin / admin登录后系统会强制改密码。3.2 首次启动前必须处理的三个环境变量第一个是REDMINE_DB_POSTGRES。它接受的是服务名也就是 compose 里 db 这个服务的名字而不是宿主机 IP。很多第一次用容器的人在这里填了 localhost结果容器内部访问不到日志直接报数据库连接失败。第二个是数据库密码。compose 里 db 服务的POSTGRES_PASSWORD和 redmine 服务的REDMINE_DB_PASSWORD必须保持一致否则容器启动时初始化脚本建库完成后应用连接阶段会立刻报 password authentication failed。这里我习惯把两个变量的密码一起替换避免漏改一处。第三个是TZ时区。PostgreSQL 容器默认使用 UTC如果不设置Redmine 记录的时间会和本地差 8 小时。虽然可以之后改数据库时区但容器重建后又会还原不如一开始就在 compose 里固定。3.3 容器方案最容易忽略的坑端口绑定我刻意写了127.0.0.1:3000:3000只允许本机访问。这样做是因为后面一定会加 Nginx 反向代理对于没有经验的人把 3000 端口直接暴露到公网等于把默认密码的管理后台暴露到公网非常危险。安全原则是只开放必须的端口。数据持久化方面redmine_files对应附件目录redmine_plugins对应插件目录这两块必须挂载。如果漏挂容器重建之后用户上传的附件全部丢失插件也要重新安装。数据库数据在db_data卷里但卷不好单独迁移建议后面配定时备份任务而不只是依赖 Docker volume。还有一个高频坑depends_on里的condition: service_healthy在旧版 Docker Compose 里不认识会直接报错。升级到 Compose v2 或者使用docker compose带横杠的插件版就能正常识别。如果你还在用早期 docker-compose 独立二进制文件建议先升级再跑这套配置。4. 源码安装适合深度定制场景的另一条路4.1 准备 Ruby 运行时源码安装的底层逻辑是Redmine 是 Ruby on Rails 应用所以机器上需要 Ruby、Bundler、数据库客户端库以及编译依赖的 C 库。以 Debian/Ubuntu 系为例先把编译工具链装齐sudo apt update sudo apt install -y build-essential git libpq-dev libmagickcore-dev libmagickwand-dev \ libssl-dev zlib1g-dev libyaml-dev libreadline-dev libxml2-dev libxslt1-dev我推荐用 rbenv 管理 Ruby 版本而不是直接用系统自带 Ruby。系统包里的 Ruby 版本往往偏旧Redmine 6.x 要求 Ruby 3.2 以上用 rbenv 可以按项目锁定版本升级 Redmine 时也方便切换git clone https://github.com/rbenv/rbenv.git ~/.rbenv echo export PATH$HOME/.rbenv/bin:$PATH ~/.bashrc echo eval $(rbenv init -) ~/.bashrc exec $SHELL rbenv install 3.2.3 rbenv global 3.2.3装完检查ruby -v确认版本正确后再拉代码。源码目录我习惯放/opt/redmine签出 stable 分支而不是最新 tag因为 stable 分支会包含同版本内的安全修复适合长期运行。4.2 数据库与初始化配置拉完源码后先复制数据库配置文件cd /opt/redmine cp config/database.yml.example config/database.yml编辑config/database.yml把 production 段落改成下面这样production: adapter: postgresql database: redmine host: localhost username: redmine password: 这里写强密码 encoding: unicode注意 Redmine 源码目录下默认也是不含config/configuration.yml的这个文件用于覆盖邮件、附件路径、日志级别等系统配置。首次部署可以不用创建系统会走默认值等配置邮件时再补。初始化数据库和默认数据依次执行gem install bundler bundle install --without development test RAILS_ENVproduction rake db:create RAILS_ENVproduction rake db:migrate RAILS_ENVproduction rake redmine:load_default_data执行load_default_data时系统会问选择哪种语言输入zh就能把界面语言默认设为简体中文。4.3 systemd 托管 Puma 服务源码跑起来的方式很多官方推荐 Passenger 集成到 Apache/Nginx但我更习惯用 Puma 配合 systemd 托管。Puma 是多线程 Ruby 服务器部署轻、排错直观。先安装 Puma gem 并确认依赖bundle exec puma -v然后创建 systemd 服务文件/etc/systemd/system/redmine.service[Unit] DescriptionRedmine server Afternetwork.target postgresql.service [Service] Typesimple Userredmine Groupredmine WorkingDirectory/opt/redmine EnvironmentRAILS_ENVproduction ExecStart/opt/redmine/bin/bundle exec puma -b tcp://127.0.0.1:3000 -e production Restartalways [Install] WantedBymulti-user.target建议单独创建 redmine 系统用户不要用 root 跑应用进程。从长期运维角度看独立的运行用户配合目录权限控制可以有效减少权限混乱导致的文件可写性问题。创建用户并授权后执行sudo useradd -r -d /opt/redmine -s /bin/bash redmine sudo chown -R redmine:redmine /opt/redmine sudo systemctl daemon-reload sudo systemctl enable --now redmine看到Active: active (running)后再访问http://127.0.0.1:3000确认页面能打开。源码方式的核心优点是可以直接改视图模板和追加初始化代码但每次升级都要重新bundle install并跑数据迁移运维节奏比 Docker 方案重不少适合需要频繁二次开发的项目。5. Nginx 反向代理与 HTTPS 配置细节5.1 为什么要加一层 NginxRedmine 自带的 Puma 虽然已经能对外提供 HTTP 服务但在生产环境直接暴露有几个问题并发连接由单个 Ruby 进程承担扛不住静态资源压力没有统一的 TLS 终止点证书管理分散也没有办法在同一台服务器上按域名分发多个服务。Nginx 这层反向代理就是把这些能力收拢到一起的标准做法。静态资源交给 Nginx 处理动态请求通过反向代理转发到后端的 Puma 或 Docker 容器。nginx 配置还有一个容易忽略的收益它可以统一处理上传大小限制。Redmine 默认限制附件大小为 4096KB4MB很多人部署完传文件报错以为是业务问题其实要么是 Nginx 的client_max_body_size没调要么是 Redmine 自身的附件配置没调。Nginx 这一层先放开后面排查范围会小很多。5.2 一份能直接上线的站点配置假设域名是redmine.example.com正确配置的核心是把请求转发到127.0.0.1:3000并添加必要的请求头和大小限制server { listen 80; server_name redmine.example.com; client_max_body_size 20m; location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 300; } }proxy_set_header X-Forwarded-Proto $scheme这一行特别重要。Redmine 通过请求头判断当前连接是否为 HTTPS如果缺少这个头即使浏览器地址栏是 https页面内部仍然会把链接生成成 http导致下次登录时 Cookie 被浏览器拦截表现为登录成功但一刷新又退回登录页。如果 Redmine 跑在 Docker 里注意proxy_pass的地址不能直接写 localhost因为容器内的 3000 端口绑定的是宿主机回环地址时才能这样写。如果你把端口绑到了0.0.0.0:3000建议把 Nginx 指向http://宿主IP:3000逻辑更清楚。5.3 证书与安全响应头证书我直接用自动化工具签发相比手动上传证书文件自动化续签避免了一个真实的运维坑证书过期后用户访问出现安全警告然后整个团队开始找运维。拿到证书后补一个 443 的 server 块并把 80 端口配置为强制跳转server { listen 443 ssl; http2 on; server_name redmine.example.com; ssl_certificate /etc/letsencrypt/live/redmine.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/redmine.example.com/privkey.pem; client_max_body_size 20m; add_header X-Frame-Options SAMEORIGIN; add_header X-Content-Type-Options nosniff; add_header Referrer-Policy strict-origin-when-cross-origin; location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 300; } }安全响应头里的X-Frame-Options SAMEORIGIN是为了防止别人把你的 Redmine 页面嵌到 iframe 里做点击劫持nosniff是防止浏览器对 MIME 类型猜测。这两条不仅安全还是不少安全扫描工具的检查项加上之后审计报告会好看不少。6. 上线前的邮件、备份和日常维护6.1 SMTP 配置发不发得出去是另一回事Redmine 的密码找回、邮件通知都依赖 SMTP 配置。如果用的是企业邮箱的 SMTP 服务需要把config/configuration.ymlDocker 方式则是环境变量里的配置写对。以常见的 587 端口提交方式为例配置文件config/configuration.yml添加production: email_delivery: delivery_method: :smtp smtp_settings: address: smtp.example.com port: 587 domain: example.com authentication: :login user_name: notifyexample.com password: 这里的密码 enable_starttls_auto: true改完配置后我到管理后台的邮件通知页面发送测试邮件验证。这一步经常发现的问题集中在端口对不上的465 与 587 的 SSL 模式差异、发件人账号被服务商强制校验、被反垃圾策略拦截。所以我把邮件配置单独列为一个验收步骤而不是等到用户报收不到邮件再排查。Docker 部署时可以不走配置文件直接用SMTP_*环境变量注入但要注意部分镜像对SMTP_ENABLE_STARTTLS_AUTO等变量名的兼容性。我建议统一用configuration.yml的挂载方式因为它的字段和官方文档一一对应排错时不容易产生歧义。6.2 备份策略数据库加 files 目录Redmine 的所有业务数据分两头关系数据在数据库里附件文件在files目录容器方案在redmine_files卷里。备份必须两者都覆盖缺一个都是残的。我常用的备份脚本逻辑是#!/bin/bash BACKUP_DIR/data/backup/redmine DATE$(date %Y%m%d%H%M) mkdir -p $BACKUP_DIR pg_dump -h localhost -U redmine -Fc redmine $BACKUP_DIR/redmine_$DATE.dump tar czf $BACKUP_DIR/redmine_files_$DATE.tar.gz -C /var/lib/redmine/files . find $BACKUP_DIR -type f -mtime 30 -delete接着用 cron 每天凌晨执行一次。恢复时可以先用 pg_restore 建库再把 files 目录释放到对应路径顺序不能反因为附件路径记录在数据库里文件还没到位时用户虽然能看到记录但下载会报错。恢复后建议立刻登录后台点开一条带附件的旧工单验证完整性。6.3 升级流程和版本节奏Redmine 属于小版本迭代较快但 API 稳定的项目我通常跟随 stable 分支而不是每个 tag 都用。Docker 方案升级最简单备份数据后把镜像 tag 换成新大版本docker compose pull docker compose up -d启动时官方镜像会自动跑数据迁移。源码方案则需要先拉新代码、重新 bundle、再手动rake db:migrate多几步但也不复杂。升级前必做的一件事是去官方发布说明里看升级注意事项比如某个大版本对 Ruby 版本的要求变化或者数据库迁移的额外说明。我吃过一次亏从旧版本直接跨大版本升级中间跳过了必要的两段迁移导致页面部分字段报错最后只能从备份恢复重来。所以不管多急备份和版本路径都不要省。7. 高频问题排查从启动失败到邮件发不出7.1 页面 500先翻 production.logRedmine 的 500 错误绝大多数时候不是代码问题而是环境问题。我的排查顺序永远固定先看日志再看权限最后看数据库连接。日志位置在部署方式的差异下并不难找——源码方式在log/production.logDocker 方式直接docker logs redmine。常见的第一类问题是files或tmp目录不可写表现为日志里有Permission denied rb_sysopen解决方法是把目录 owner 改成运行用户。第二类常见问题是数据库连接被拒日志里会出现could not connect to server。这通常发生在数据库服务重启后应用连接池没有及时释放重启一下 Redmine 进程就恢复了。如果你频繁遇到这种问题检查数据库的max_connections和应用的连接池配置把池大小调小一些避免连接数被打满。7.2 邮件发不出的几类根因邮件发不出证据链通常能从一份测试邮件里拿到。如果测试邮件报 530 或 535说明认证失败优先检查账号密码和 SMTP 提交方式是否匹配如果报超时则是服务器到 SMTP 服务商之间的网络不通或者端口被防火墙拦截可以先用 telnet 手动测试端口连通性再排查配置。还有一个很容易被忽略的点很多邮件服务商要求发件域名正确配置 SPF 和反垃圾记录否则会拒信或进垃圾箱。Redmine 报发送成功但收不到基本都是这一层的问题。我的经验是先发给企业邮箱自己的别名如果进垃圾箱再查邮件服务商的投递日志比盲目改代码有用得多。7.3 反向代理之后的登录失效与上传受限这类问题集中在新上 Nginx 反代的部署里。表现为直接访问 IP:3000 一切正常通过域名访问却反复跳登录页。90% 的原因是缺少X-Forwarded-Proto头导致 Redmine 认为自己跑在 http 下拒绝读取浏览器仅允许 https 下回传的 Cookie。解决方案就是前面 Nginx 配置里的那一行加上后重启 Redmine 再验证。上传失败则是第二个高频点报错信息往往是文件过大或直接请求被中断。检查顺序是 Nginxclient_max_body_size→ Redmine 管理后台附件大小限制→ 磁盘剩余空间。前两项都改了还不行看看files目录挂载的宿主磁盘是不是满了这个坑我在日志里见过太多次。8. 我最后想留给你的一点点经验部署 Redmine 这类系统技术上其实不难难的是把它当成一个长期运行的基础设施来对待。我在多次部署和迁移中最大的体会是不要把部署完成当成交付完成把备份验证、版本升级演练、问题排查手册这三样工作纳入常规运维清单才是真正让这套系统能一直用下去的关键。如果你现在还在犹豫用哪种方式部署我建议直接上手 Docker Compose 方案快速跑通以后再根据团队是否需要深度定制逐步迭代。一个能长期稳定运行、数据由自己掌控的项目管理后台在团队协作里带来的踏实感是任何花哨工具都比不了的。