日期:2026-10-19
标签:#Redmine #Docker #项目管理 #敏捷开发 #AI #技术实践
环境:Redmine 7.0.2、PostgreSQL 16、Docker Compose、群晖 NAS、Nginx Proxy Manager
一、为什么重新选择 Redmine?
最近一直在寻找一套适合个人及小型团队的项目管理系统。
此前先后了解和尝试过 OpenProject、Leantime、YouTrack 等工具。它们各有优势,但也存在一些问题:有的部署和维护复杂,有的功能受到商业版本限制,有的在反向代理环境下容易出现访问问题。
Redmine 是一款比较传统的开源项目管理工具,界面谈不上现代,但有几个吸引我的特点:
- 免费开源,核心项目管理功能完整。
- 支持项目、任务、缺陷、工时、Wiki、版本和甘特图。
- 支持多项目管理和细粒度权限控制。
- 有比较成熟的插件扩展机制。
- 支持 Docker 部署,可以自行维护数据库和数据。
- 相比一些现代项目管理平台,更容易掌握和控制自己的数据。
因此决定重新部署 Redmine,并尝试把它作为统一的项目管理平台。
今天花了不少时间,从系统部署、插件安装一路折腾到界面定制、敏捷工作流以及 AI 集成,也逐渐对 Redmine 的设计方式有了更深入的认识。
二、Docker 部署:简单,但不代表没有坑
使用官方 Docker 镜像:
- Redmine 7.0.2
- PostgreSQL 16 Alpine
- Docker Compose
- 数据库和附件使用 Volume 持久化
核心服务配置比较简单:
services:
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: redmine
POSTGRES_USER: redmine
POSTGRES_PASSWORD: "${DB_PASSWORD}"
volumes:
- redmine_db:/var/lib/postgresql/data
redmine:
image: redmine:7.0.2
restart: unless-stopped
depends_on:
- db
ports:
- "8088:3000"
environment:
REDMINE_DB_POSTGRES: db
REDMINE_DB_DATABASE: redmine
REDMINE_DB_USERNAME: redmine
REDMINE_DB_PASSWORD: "${DB_PASSWORD}"
volumes:
- redmine_files:/usr/src/redmine/files
volumes:
redmine_db:
redmine_files:
真正麻烦的是后面的反向代理和插件环境。
2.1 双层反向代理导致端口丢失
我的访问链路是:
浏览器
↓ HTTPS :7777
群晖反向代理
↓
Nginx Proxy Manager
↓
Redmine :3000
访问首页正常,但登录、保存或其他页面跳转时,有时会丢掉外部的 7777 端口。
排查过程中认识了三个重要请求头:
Host: redmine.example.com:7777
X-Forwarded-Host: redmine.example.com:7777
X-Forwarded-Proto: https
Nginx 的 $host 通常不保留原始 Host 中的端口,而 $http_host 保留客户端传入的完整 Host。
此外,Rails 在生成 URL 时也可能参考 X-Forwarded-Host。因此,不能只修改一处 Host 就认为问题已经解决。
Redmine 后台也需要设置正确的外部地址:
管理 → 配置 → 一般 → 主机名称和路径
例如:
redmine.example.com:7777
这次得到的经验是:遇到跳转异常,首先应检查 HTTP 响应中的 Location 和反向代理实际传递的请求头,避免在应用层反复修改无关配置。
2.2 浏览器缓存和 HTTP 重定向
浏览器可能缓存永久重定向,因此 URL 异常也不一定完全来自当前服务端配置。
可以通过浏览器 F12 的 Network 查看:
- Request URL
- HTTP 状态码
- Response Headers 中的 Location
这比单纯清除 Cookie 更有针对性。
三、Redmine 插件生态:丰富,但安装体验比较传统
这是今天比较深刻的感受。
Redmine 的插件生态确实丰富,但远不如 WordPress 那样方便。
很多插件的安装流程仍然是:
- 下载插件源码。
- 复制到
plugins目录。 - 安装 Ruby Gem 依赖。
- 执行数据库迁移。
- 重启 Redmine。
- 必要时编译静态资源。
其中最麻烦的是 Ruby 依赖和版本兼容性。
我不希望为了安装几个插件专门维护一个自定义 Dockerfile,所以选择继续使用官方镜像,通过宿主机挂载插件目录管理代码。
3.1 主要研究的插件
| 插件 | 作用 | 认识 |
|---|---|---|
| Dashboard | 任务看板 | 适合项目内任务管理 |
| Agile | 敏捷看板 | 适合状态流转及敏捷实践 |
| Checklists | 任务检查清单 | 适合标准化执行步骤 |
| Issue Templates | 问题模板 | 减少重复填写 |
| Time Logger | 工时计时 | 补充原生工时功能 |
| View Customize | 页面定制 | 灵活性很高 |
| SSO Suite | 单点登录 | 安装简单,但遇到了兼容问题 |
| AI Helper | AI 辅助项目管理 | 功能强大,安装和配置更复杂 |
并不是所有插件都值得安装。尤其在 Redmine 7 和较新的 Rails、Ruby 环境下,插件声称兼容也不能保证实际启动成功。
3.2 Dashboard 缺少 slim-rails
安装 Dashboard 后,遇到:
Could not find gem 'slim-rails' in locally installed gems.
这说明插件需要的 Ruby Gem 没有安装。
可以在 Redmine 根目录检查并安装依赖:
cd /usr/src/redmine
bundle check
bundle install
不过,Docker 官方镜像中的 Gem 如果直接安装在容器文件系统内,重新创建容器后可能丢失。
这也是不使用自定义镜像所需要承担的维护成本。
3.3 Bundler 国内镜像的问题
由于网络原因,执行 bundle install 时连接 RubyGems 比较困难。
尝试通过 Bundler 配置镜像:
---
BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/: "https://mirrors.tuna.tsinghua.edu.cn/rubygems/"
BUNDLE_TIMEOUT: "30"
BUNDLE_RETRY: "2"
再把配置文件挂载到容器。
过程中还遇到了两类错误:
Gem sources must be absolute. You provided '/'.
以及:
SSL alert handshake failure
前者需要检查 Bundler 配置格式;后者属于 HTTPS/TLS 连接问题,不能简单归因于插件。
最终的经验是:先验证镜像站网络连通性,再运行 Bundler,而不是看到依赖缺失就反复安装。
另外,日志中反复出现:
Your Gemfile lists the gem puma more than once.
这属于 Gemfile 重复声明警告,不应直接把它当成 Redmine 无法启动的根因。
四、SSO 集成:一个 Zeitwerk 命名错误
希望让 Redmine 使用群晖 SSO 登录,因此尝试了 Redmine SSO Suite。
插件不需要复杂的 Gem 安装,但启动时遇到了真正的兼容性问题:
uninitialized constant RedmineSsoSuite::Version
Did you mean? RedmineSsoSuite::VERSION
这是 Rails Zeitwerk 自动加载机制发现的命名不一致。
插件文件使用了:
module RedmineSsoSuite
VERSION = '...'
end
而自动加载器期望文件提供:
RedmineSsoSuite::Version
通过在保留原常量的前提下补充兼容定义,解决了这次启动异常。
例如:
Version = VERSION
当然,这属于针对当前问题的临时兼容补丁,不代表插件其他功能已经完全兼容。
插件启动后,还遇到了 OAuth/OIDC 重定向地址错误地使用 localhost 的问题。
这次经验提醒我:SSO 不仅仅是填写 Client ID 和 Secret,还要保证 Redmine 对自身外部地址的认知正确。
尤其在多层反向代理环境中,Issuer、Redirect URI、外部端口都必须一致。
五、项目管理:真正需要的是整体项目看板
一开始研究 Dashboard 和 Agile 时,我发现自己其实混淆了两种看板。
5.1 任务看板和项目组合看板
任务看板的对象是问题或任务:
待办 → 进行中 → 已完成
而我更希望的是项目组合看板:
所有项目
├── 项目 A:进行中
├── 项目 B:有风险
├── 项目 C:已完成
└── 项目 D:待启动
理想的项目总览应该能够看到:
- 项目名称和负责人。
- 当前进度与状态。
- 未完成任务数量。
- 近期活动及延期风险。
- 点击后进入项目内部。
Redmine 原生项目列表提供了基础信息,但缺乏现代化的项目组合驾驶舱。
Agile 和 Dashboard 主要解决任务级管理,并不直接等于项目组合管理。
因此,后续需要把「项目总览」与「项目内部任务看板」分开考虑。
5.2 项目关闭与归档
Redmine 原生支持:
- 关闭项目:项目变成只读,通常仍可查询。
- 归档项目:从日常项目列表中隐藏,但保留数据。
归档主要在管理后台操作,并不是直接在普通项目页面完成。
我认为合理的项目生命周期应该是:
计划中 → 进行中 → 已完成 → 关闭 → 归档
这里的前几步属于项目管理流程,后两步使用 Redmine 原生的关闭与归档能力。
归档不会删除数据,也不会自动减少存储空间。
六、敏捷工作流:减少状态比增加状态更重要
Redmine 默认使用「新建、进行中、已解决、已关闭」等问题状态。
今天认真思考后发现,对个人开发和小型团队而言,过多状态可能只会增加操作成本。
尤其是「已解决」和「已关闭」,如果没有专门的测试或验收角色,两者的区别其实并不大。
我准备尝试更简单的三状态流程:
待办 → 进行中 → 已完成
| 状态 | 定义 |
|---|---|
| 待办 | 已决定处理,但未开始 |
| 进行中 | 正在开发、测试或处理 |
| 已完成 | 满足完成标准,不需要进一步操作 |
如果未来有独立的测试、评审或验收环节,再增加「待验收」。
Redmine 中,问题是否属于关闭状态,并不完全由状态名称决定,而取决于「管理 → 问题状态」中的「问题已关闭」标记。
因此,可以把「已关闭」重命名为「已完成」,并继续将其视为关闭状态。
另外,可以保留一个特殊的「已取消」状态,用于区分真正完成的任务和决定不再执行的任务。
这里最重要的并不是把看板做得多复杂,而是明确什么叫“完成”。
七、工时管理:Redmine 原生能力基本够用
Redmine 自带工时登记,能够关联项目、问题、活动和日期。
其中一个实用配置是设置默认工时活动。
进入:
管理 → 枚举值 → 活动(时间跟踪)
可以指定全局默认活动,例如「开发」。
项目还可以单独启用或禁用相应活动。
对于实际使用,我觉得不必把工时分类做得特别复杂,初期保留几类即可:
- 开发
- 测试
- 设计
- 项目管理
另外,Redmine 的问题详情页虽然可以通过「编辑」修改状态,但缺少足够直接的「解决」「关闭」快捷按钮。
问题列表右键修改状态反而更加方便。
这也是 Redmine 传统界面在操作效率上的不足之一。
八、View Customize:最有价值的界面扩展工具
今天发现比较有潜力的插件是 Redmine View Customize。
它支持在管理后台编写 HTML、CSS、JavaScript,根据 URL 匹配规则修改页面。
与为了每个小功能单独安装插件相比,它更适合解决一些轻量界面定制需求。
8.1 在「我的工作台」添加项目列表
Redmine 原生「我的工作台」更侧重问题、活动和工时,没有足够理想的项目组合入口。
通过 View Customize,可以在 /my/page 注入 JavaScript,读取 /projects 页面,提取项目名称和链接,再显示成项目卡片。
配置示例:
Path pattern: ^/my/page/?$
Type: JavaScript
Insertion position: Bottom of all pages
最初尝试通过 REST API:
/users/current.json?include=memberships
但遇到了用户名密码认证弹窗。
由于不希望在浏览器脚本里保存 API Key,后来改为读取原生 /projects HTML 页面。
这个办法的优点是不需要额外 API 认证,而且能够复用当前网页登录会话。
缺点是依赖页面 DOM 结构,主题升级或 Redmine 版本变化后可能需要调整。
随后又把原来的表格改成响应式卡片,提高了工作台的可读性。
8.2 首页跳转到我的工作台
还尝试通过 View Customize,将 Redmine 首页 / 自动跳转到 /my/page。
核心实现非常简单:
if (window.location.pathname === "/") {
window.location.replace("/my/page");
}
不过,是否执行还涉及登录状态判断、规则匹配和脚本注入位置。
这一项还需要继续验证和完善,不应把代码简单保存就视为已经实现。
8.3 一个新的认识
View Customize 的优势是修改界面和交互,但它不是完整的后端扩展框架。
适合用它实现:
- 自定义工作台内容。
- 改善项目卡片样式。
- 添加快捷导航入口。
- 调整表格、菜单及页面布局。
不适合依靠它实现复杂的数据管理、权限绕过或数据库业务逻辑。
涉及数据修改的功能,仍然应该使用 Redmine 正式提供的服务端接口并遵守权限控制。
九、AI Helper:Redmine 开始具备 AI 管理能力
今天还尝试安装了 Redmine AI Helper。
它吸引我的地方不只是增加一个 AI 聊天框,而是能够结合 Redmine 中已有的项目信息,辅助完成任务分析和项目管理。
主要能力包括:
- 查询与总结问题。
- 辅助拆分任务。
- 搜索相似问题。
- 分析项目健康情况。
- 生成项目相关报告。
- 接入不同大模型。
- 通过 MCP 与外部 AI 工具协作。
9.1 安装时遇到的问题
首先出现:
Missing secret_key_base for 'production'
这是 Rails 在生产环境执行数据库迁移时没有正确获取密钥,而不是 AI 模型配置问题。
随后 PostgreSQL 又出现:
relation "ai_helper_settings" does not exist
说明插件尝试访问尚未创建的数据表,或迁移尚未完整完成。
这次认识到:Redmine 插件能够出现在管理页面,不代表安装已经完整完成。
仍然需要检查数据库迁移状态和实际功能。
9.2 AI Helper 的全局与项目级能力
插件配置完成后,在启用了 AI Helper 的项目中可以看到聊天界面,但在项目外看不到。
检查插件的 Hook 代码,发现:
render_on :view_layouts_base_body_top,
partial: "ai_helper/chat/sidebar"
这说明插件使用了 Redmine 全局页面布局 Hook,但实际是否显示聊天入口,还可能受到模板内部、当前项目上下文和权限判断影响。
同时需要区分两件事:
跨项目查询能力,不等于聊天入口能在所有页面显示。
这是今天比较重要的认识。
AI Helper 可以帮助查询和分析多个项目,但它并不天然等于一个全局 AI 项目组合管理界面。
未来可能需要进一步定制全局入口,或者直接通过 MCP 让外部 AI Agent 访问 Redmine。
十、今天最重要的几个经验
经过这一整天的部署和配置,对 Redmine 有了更清晰的认识。
第一,Redmine 的优势在后端管理能力,而不在默认界面。
项目、任务、权限、工作流和工时能力比较完整,但使用体验需要适当定制。
第二,插件不是越多越好。
每增加一个插件,就增加一个依赖和兼容性风险。尤其是 Redmine 7、Rails 8、Ruby 4 的新组合,不能仅凭插件 README 中的兼容声明判断能否正常使用。
第三,工作流应该尽可能简单。
敏捷管理更重要的是透明、快速流转和明确完成标准,而不是创建更多的问题状态。
第四,项目管理与任务管理需要两个视角。
项目组合视图负责回答“哪些项目需要关注”,任务看板负责回答“具体哪些事情需要执行”。
第五,AI 的价值不应该停留在聊天。
真正有意义的是让 AI 读取项目上下文,帮助分析进度、识别风险、生成报告,并与现有开发工具形成协作。
十一、下一步计划
今天先完成了 Redmine 的初步部署和功能探索,接下来准备继续完善:
- 优化「我的工作台」,形成真正的跨项目入口。
- 完善三状态敏捷工作流,统一任务处理方式。
- 解决 AI Helper 在项目外缺少聊天入口的问题。
- 验证群晖 SSO 登录的完整流程。
- 减少功能重复、维护成本高的插件。
- 评估通过 MCP 与外部 AI Agent 集成的实际价值。
结语
今天最大的感受是:Redmine 是一个成熟、可扩展的项目管理底座,但要把它变成顺手的工作平台,仍然需要花时间做减法和适度定制。
与其不断寻找功能更多的新项目管理软件,不如先明确自己真正需要的工作流程,再决定保留什么、扩展什么。
目前来看,我需要的并不是一套复杂的企业级项目管理系统,而是一个能够统一管理多个项目、快速处理任务、记录工时,并逐步接入 AI 能力的轻量工作平台。
Redmine 有机会承担这个角色。
但接下来的重点,应该从“安装更多插件”转向“减少操作成本,让系统真正用起来”。