
在公司内网搭一套能在线打开、编辑、协同处理 Word、Excel、PPT 的文档服务OnlyOffice 几乎是最绕不开的选择只要你的机器上装了 Docker用 Docker Compose 部署 OnlyOffice 又是目前公认最省心的一条路。官方镜像把 Nginx、Node.js、PostgreSQL、Redis 这些依赖都打在一个容器里写一份十几行的 yaml 就能把整套服务拉起来。这篇文章不打算只贴一份 docker-compose.yml 了事。我会把自己在实际部署里踩过的坑、反复确认过的配置项、以及像 JWT 密钥、持久化卷、反向代理、Moodle 集成这类容易翻车的地方全部串起来讲清楚。适合正在做 OA、网盘、知识库集成的开发也适合想在实验环境快速体验在线协同编辑的运维同学。1. 为什么用 Docker Compose 部署 OnlyOffice架构与选型思路1.1 OnlyOffice 到底解决什么问题OnlyOffice 是一套开源的在线办公套件核心能力是让用户直接在浏览器里打开和编辑 docx、xlsx、pptx 等 Office 格式文档同时支持多人协同编辑、批注评论、版本历史。和本地 Office 最大的区别在于你不需要把文件下载到桌面再上传回去浏览器里改完服务端负责保存和回调整个编辑链路都在你自建的服务里完成。这套东西最常见的落地位置有三类一类是私有网盘或知识库里的在线预览和编辑比如 Nextcloud、Seafile一类是 OA/ERP 里的附件预览让审批附件不用下载就能看还有一类是教育平台里的作业批改比如 Moodle 里直接打开 Word 文档批注。说到底它解决的是“文档内容不出内网”的问题数据在自己手里离线可用也方便做权限控制。1.2 Docker Compose 方案为什么比裸装更省心OnlyOffice 底层不是一个单进程应用。它需要 Nginx 做入口Node.js 跑文档服务PostgreSQL 存元数据Redis 做缓存RabbitMQ 做队列。如果裸装你得手动把这些依赖一个个准备好版本不匹配、环境变量漏配、端口冲突任何一个环节都可能让你半天起不来。官方提供的 onlyoffice/documentserver 镜像把这些服务全部封进一个容器里容器启动时由内部进程管理器统一拉起。但问题是直接用 docker run 部署的命令越来越长环境变量、卷挂载、网络参数混在一起时间久了根本没法维护。Docker Compose 干的就是把这一堆参数写进声明式配置一条 docker compose up -d 全部搞定。它带来的好处很直接配置可以放进 Git 做版本管理换机器可以一键恢复团队协作时大家用同一份 yaml 而不是复制粘贴长命令。1.3 部署拓扑最小与生产两种形态最简部署只需要一个文档服务器容器加几个数据卷。官方镜像在容器内部已经把 PostgreSQL、Redis、RabbitMQ 都内置了所以你不需要额外跑数据库容器。对中小团队、测试环境、内部工具来说这种单容器形态是最稳的因为少一个组件就少一类故障。生产环境如果想拆细可以把 PostgreSQL、Redis、RabbitMQ 分别用独立容器或外部托管再把 OnlyOffice 容器通过环境变量指过去。这么做的收益是资源隔离更彻底、数据库可以单独备份扩容但维护成本也上来了。我的建议是初期先用单容器跑通业务后再按需拆分别一上来就把整个架构复杂化。流量路径大致是这样外部请求先到反向代理443代理转发到宿主机映射端口比如 8080再进入容器内部 Nginx 的 80 端口。容器内部再路由到文档服务、转换服务或者协同编辑的 WebSocket 服务。理解这条链路后面排查 502、WebSocket 连不上才有清晰的方向。2. 从零编写 docker-compose.yml核心配置逐项拆解2.1 镜像版本latest 还是固定 tag示例配置里我用的是 onlyoffice/documentserver:latest因为演示方便、第一次拉取不需要查版本号。但生产环境我强烈建议固定一个具体 tag。原因很简单OnlyOffice 升级有时候会改存储结构、数据库迁移脚本、JWT 策略你昨天还跑得好好的镜像今天重新 pull 一下可能就起不来了。固定 tag 后升级变成你主动选择的一个动作而不是某次重建容器时被动的意外。镜像体积非常大印象里解压后好几个 GB拉取时间也比一般镜像长。内网环境如果拉不动正确做法是找一台能联网的机器先 docker pull再 docker save 成 tar 包导入内网后面我会专门说。另外要注意这个容器对内存不客气。因为它内部同时跑着数据库、缓存、队列、文档服务实测内存小于 2GB 时启动很容易失败或者用一段时间被 OOM 杀死。个人经验是至少 4GB 内存起步生产场景建议 4 核 8GB。2.2 端口、网络与容器名容器内部 Nginx 监听的是 80 端口。宿主机如果已经有 Nginx、Apache 或者别的 Web 服务占了 80就把对外端口映射成 8080 或某个高位端口。这里有个很多人不理解的点你暴露的是宿主机端口不是容器内端口所以 8080:80 表示访问宿主机 8080 就等价于访问容器内 80。容器名固定有一个好处日志、备份、docker exec 排查时不用每次去查随机 ID。restart 策略我统一用 unless-stopped意思是容器异常退出会自动拉起但你手动 stop 后它不会自己跑起来很适合长期运行的服务。网络方面建议创建一个独立 bridge 网络。如果你的 OnlyOffice 要对接 Moodle、Nextcloud 或者其他容器化应用把这些服务放进同一个 docker network就可以用容器名互相访问不需要把业务服务端口完全暴露到宿主机。2.3 JWT 与安全项最容易翻车的三个点OnlyOffice 从 7.2 版本开始默认把 JWT 鉴权拉得很紧。文档编辑器、文档转换服务、回调通知之间会通过 JWT 签名做身份校验一旦启用所有请求都得带上正确的签名否则直接 401 或拒绝处理。环境变量里最关键的是这三个JWT_ENABLED 控制开关JWT_SECRET 是签名密钥JWT_HEADER 指定签名放在请求头的哪个字段。集成的第三方系统必须和 OnlyOffice 使用完全相同的 JWT_SECRET否则编辑器加载不出来、保存回调失败、转换接口报错问题看起来五花八门根源往往就是这个密钥对不上。密钥生成可以用 openssl rand -hex 3232 字节的随机十六进制字符串足够安全。如果你是在内网使用还需要注意一个反向场景容器的回调请求目标是某个内网地址时可能会被 OnlyOffice 内部的“拒绝访问私有 IP”策略拦掉。遇到这种情况可以加上环境变量 ALLOW_PRIVATE_IP_ADDRESStrue让容器允许回调到 192.168 / 10 / 172.16 这类私有网段。2.4 持久化卷哪些目录必须挂不挂卷的容器就像一次性用品容器一删数据全没。OnlyOffice 官方镜像声明的持久化目录有这几个容器内路径作用丢失影响/var/www/onlyoffice/Data证书、配置、文件存储需要重新配置自定义证书丢失/var/log/onlyoffice运行日志排查问题没有历史日志/var/lib/onlyoffice文档缓存和部分运行数据缓存失效需要重新渲染/var/lib/postgresqlPostgreSQL 数据目录版本历史、文档元数据全部丢失/var/lib/rabbitmqRabbitMQ 数据队列任务状态丢失/var/lib/redisRedis 持久化数据缓存数据丢失很多人在意文件本体放哪其实 OnlyOffice 本身一般不存源文件源文件由集成方Moodle、网盘、OA来保管。但如果版本历史、协同编辑状态、文档转换缓存这些没了体验会大打折扣。特别是 /var/lib/postgresql版本历史记录就在数据库里想保留“谁在什么时候改过哪里”这个卷必须挂好。Docker 的命名卷named volume迁移方便适合只通过 docker compose 管理绑定挂载bind mount适合你想直接用 vim 看日志、备份文件。我一般用命名卷然后用 docker run --volumes-from 的方式做备份后面实操部分会写具体命令。2.5 一份可直接复制的最小 compose 文件下面这份是我给多数项目起底的模板去掉了外部数据库和队列最大程度降低初次部署的复杂度。services: onlyoffice-documentserver: image: onlyoffice/documentserver:latest container_name: onlyoffice-documentserver restart: unless-stopped ports: - 8080:80 environment: JWT_ENABLED: true JWT_SECRET: replace-with-openssl-rand-hex-32-output JWT_HEADER: Authorization JWT_IN_BODY: true ALLOW_PRIVATE_IP_ADDRESS: true volumes: - ds_data:/var/www/onlyoffice/Data - ds_log:/var/log/onlyoffice - ds_lib:/var/lib/onlyoffice - ds_db:/var/lib/postgresql - ds_rabbitmq:/var/lib/rabbitmq - ds_redis:/var/lib/redis networks: - onlyoffice_net volumes: ds_data: ds_log: ds_lib: ds_db: ds_rabbitmq: ds_redis: networks: onlyoffice_net: driver: bridge你不需要真的把这段全部照抄关键是理解每个字段在干什么。image 决定版本ports 做端口映射environment 传鉴权信息volumes 定义持久化networks 让容器有独立网络。JWT_SECRET 这一项务必替换成自己的随机字符串不要用模板里的占位符否则等于没设密码。注意如果你用的是新版 Docker Compose v2命令是 docker compose中间有空格不是老旧的 docker-compose。配置文件里也不需要写 version 字段Compose 会根据文件内容自动选择语法版本。3. 部署实操从下载镜像到启动验证3.1 环境准备与 Docker Compose 安装系统里没有 Docker 的话先装基础运行时。以 Ubuntu/Debian 为例可以直接用系统包管理器安装 Docker Engine 和 Compose 插件。sudo apt update sudo apt install -y docker.io docker-compose-plugin sudo systemctl enable --now docker docker compose version执行完 docker compose version 能看到版本号说明 Compose 插件已经就位。如果你之前只装了 docker 没装 compose 插件也可以单独补装。CentOS/RHEL 系列则是用 dnf 装 docker-ce 和 docker-compose-plugin名字略有差异。内网离线环境装 Docker 是另一个话题这里先给一个最实用的思路在有外网的机器上把镜像打成 tar 包再导入内网。docker pull onlyoffice/documentserver:latest docker save onlyoffice/documentserver:latest -o onlyoffice-documentserver.tar到了内网机器上执行 docker load -i onlyoffice-documentserver.tar镜像就进本地了。此时再跑 docker compose up -dCompose 发现本地有同名镜像就不会去远程拉取。3.2 启动服务与常用管理命令把上面的 docker-compose.yml 保存到某个目录比如 /opt/onlyoffice然后在这个目录里执行cd /opt/onlyoffice docker compose up -d第一次启动会先拉镜像镜像很大需要耐心等一会儿。拉完镜像后容器开始内部初始化这时候你会看到容器状态从 created 变成 starting最后变成 running。内部要同时拉起好几个服务初始化时间通常在一到两分钟不要刚看到容器起来就急着访问。日常管理最常用的几条命令一起列出来# 查看容器状态 docker compose ps # 跟踪日志排查启动过程中的报错 docker compose logs -f onlyoffice-documentserver # 进入容器排查 docker exec -it onlyoffice-documentserver bash # 重启容器 docker compose restart onlyoffice-documentserver # 停止并删除容器卷不会被删除 docker compose down注意 docker compose down 默认只删容器和网络不会删命名卷所以数据还在。如果你想连卷一起清掉要加 -v 参数但这句话我必须说在前头加了 -v 等于把 OnlyOffice 的数据库、日志、缓存全删了非必要不要碰。3.3 验证部署健康检查、首页与临时文档容器起来后别急着开会先做三个层面的验证。第一层是看进程状态docker compose ps 里 STATUS 显示 Up 且没有 Restarting 字样基本说明容器没崩溃。第二层是探测健康检查接口curl http://localhost:8080/healthcheck如果服务正常这个接口会返回一段文本常见结果是 true 或类似状态信息。如果返回 404 或者连接拒绝说明容器内部服务还没就绪或端口映射不对先看日志。第三层是确认 API 文件能访问因为 OnlyOffice 的前端编辑器依赖这段脚本curl -I http://localhost:8080/web-apps/apps/api/documents/api.jsHTTP 状态 200 就说明 Web 服务正常。之后可以用浏览器打开主机地址比如 http://你的服务器IP:8080看到 OnlyOffice 的欢迎页或编辑器页面。如果要更仔细地验证在线编辑可以准备一个最简单的 HTML 页面把 DocsAPI 编辑器嵌进去指向服务器上的 test.docx这样能确认 JWT、回调、WebSocket 整条链路是通的。3.4 常见启动问题与排查技巧实录部署过程里翻车最多的不是配置复杂而是基础环境出了问题。我把自己遇到过的几类典型问题整理成一张速查表现象排查方向解决办法容器一直 Restarting日志里有 OOM内存不够给机器加内存至少 4GB或检查是不是同时跑了太多容器访问 8080 端口 404 / 502容器内部还没完全启动等 1-2 分钟再访问docker compose logs 看启动进度8080 端口被占用宿主机上已有服务占端口把映射改成 8081:80或先停掉占用端口的服务编辑器加载失败接口报 401JWT 密钥不一致确认集成方配置的 secret 与容器环境变量 JWT_SECRET 完全一致转换文档报错提示无法访问文件容器无法回调内网地址设置 ALLOW_PRIVATE_IP_ADDRESStrue并检查网络连通性协同编辑时对方看不到文档刷新WebSocket 没透传反向代理必须支持 Upgrade 和 Connection 头排查这些问题有个统一的顺序先 docker compose ps 看存活状态再 docker compose logs 看最近的错误最后 curl 健康检查接口看服务是否就绪。大多数“起不来”的问题到这一步就能定位清楚了。4. 生产环境进阶HTTPS、Moodle 集成与转换参数4.1 让域名走 HTTPS反向代理与 WebSocket 透传OnlyOffice 文档编辑用了 WebSocket 做协同通信所以反向代理不能只是简单转发 HTTP必须额外处理 WebSocket 的 Upgrade 握手。很多人配完 Nginx 后打开页面是好的但一旦两个人同时编辑文档状态不同步十有八九是代理层把 WebSocket 请求当成普通请求转发导致长连接建立失败。这里给一份 Nginx 反向代理的关键配置片段location / { proxy_pass http://127.0.0.1:8080; 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_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; client_max_body_size 100m; }注意 proxy_set_header Connection 一定要是 upgrade而且这一行不能与普通的 keepalive 逻辑混掉。X-Forwarded-Proto 也很重要它告诉 OnlyOffice 用户当前通过 HTTPS 访问这样内部生成的回调地址、下载链接才不会错误地使用 http。如果你用 Caddy配置会简单不少它默认就会处理 WebSocket 和 HTTPS 证书申请。但生产系统里 Nginx 依然是主流掌握上面的写法比背工具更重要。4.2 和 Moodle 集成装插件与密钥对齐Moodle 接入 OnlyOffice 的官方路径是安装由 OnlyOffice 提供的 Moodle 插件。插件可以在 Moodle 后台直接上传 Zip 安装站点管理 - 插件 - 安装插件然后上传下载到的插件压缩包按提示启用。装完插件后进入插件的配置页面需要填两个最核心的信息文档服务器地址和 JWT 密钥。文档服务器地址要填完整比如 https://doc.example.com/结尾斜杠别漏JWT 密钥必须和 docker-compose.yml 里的 JWT_SECRET 一模一样。这两处对不上你在 Moodle 里打开附件时会一直转圈或者直接报错。比较隐蔽的一个问题是Moodle 所在的服务器必须能访问到 OnlyOffice 容器最好在同一网络或者通过防火墙放行。如果 Moodle 和 OnlyOffice 都在 Docker 里推荐让两个容器加入同一个外部网络这样 Moodle 可以用容器名解析 OnlyOffice。提示集成完以后先上传一个小文件试一下“在线编辑”和“保存回传”。OnlyOffice 编辑完会回调 Moodle 保存文件如果回调地址被安全策略拦截版本历史和编辑保存都会失效。4.3 转换请求里的 assemblyformatasorigintrue 到底是干嘛的如果你对接过 OnlyOffice 的转换接口大概率见过某个 URL 参数叫 assemblyformatasorigintrue。这个参数不常出现在文档里但真遇到格式错乱的问题时它往往是关键。我个人的理解和使用经验是它告诉 OnlyOffice请以文件的原始装配格式作为转换基准不要只凭 URL 后缀或者外部传入的 filetype 做判断。举个例子有些系统导出的文件后缀是 .doc但内部实际是 docx 的 Open XML 结构不带这个参数时转换服务可能按老二进制格式去解析结果导出 PDF 后排版错乱、中文乱码。加上 assemblyformatasorigintrue服务端会先尝试识别文件真实的内部格式再决定转换路径。在实际对接时你可能不是在页面上直接改这个参数而是在集成代码的转换请求 URL 里把它拼接进去。比如https://doc.example.com/ConvertService.ashx?url...#outputtypepdfassemblyformatasorigintrue如果你当前没有遇到转换异常不需要刻意加。但把这条经验记在脑子里等将来有人反馈“某个 doc 转出来是乱的”排查方向就会清晰很多。4.4 协同编辑与版本历史开启要做什么OnlyOffice 的协同编辑本身不需要额外配置容器起来就能用但要让多人编辑实时同步整个链路必须满足三个条件。第一浏览器能通过 WebSocket 连接到文档服务所以反向代理要处理好 Upgrade第二所有客户端和回调请求使用同一个 JWT 密钥签名不一致的请求会被拒绝第三文档的保存回调要能回到集成方否则只在 OnlyOffice 内部改了业务系统里的源文件不会更新。版本历史在 OnlyOffice 编辑器界面的“版本历史”入口能看到每次保存会生成一个新版本。和“代码查看历史修改记录”一样你可以比较当前版本和之前版本的差异。这个功能依赖于 PostgreSQL 卷因为版本元数据存在数据库里。如果你发现版本历史里只有一条记录或者改动记录无故消失先检查两件事数据库卷有没有正常挂载以及保存回调有没有失败。很多容器重建后用了一个新的匿名卷历史数据就“丢”了其实旧数据还在原来的卷里只是没挂回来。5. 避坑指南与实用建议5.1 镜像拉取慢或拉不下来怎么办OnlyOffice 镜像体积大拉取慢是个很实际的问题。最稳的办法是给 Docker daemon 配置 registry mirror也就是镜像加速器。修改 /etc/docker/daemon.json加入 registry-mirrors 配置项然后重启 Docker。{ registry-mirrors: [https://你的镜像加速地址] }配置完记得 docker info 查看 Registry Mirrors 是否生效。如果你使用的加速地址不可用就换一个或者干脆走离线导入路线。还有一个小技巧不要在高峰期反复拉同一个镜像第一次拉失败产生的残层会占用磁盘空间定期 docker system prune 清一下避免磁盘被占满。5.2 备份与升级策略只要容器正常挂着卷备份并不复杂。最简单的方式是用一个临时容器挂载 OnlyOffice 容器的所有卷打包整个数据目录。docker run --rm --volumes-from onlyoffice-documentserver \ -v $(pwd):/backup alpine \ tar czf /backup/onlyoffice-backup.tar.gz \ /var/www/onlyoffice/Data /var/lib/postgresql /var/lib/redis \ /var/lib/rabbitmq /var/lib/onlyoffice /var/log/onlyoffice这个命令会把关键数据全部打包到当前目录的 onlyoffice-backup.tar.gz。恢复的时候先把新容器停掉再用同样的方式把 tar 解包回对应目录即可。备份频率取决于你们的业务量我的建议是每次升级前必须备份日常每天或每周定时跑一次。升级 OnlyOffice 时不要直接 docker compose pull 然后 up -d 就完事。先看官方发布说明确认新版本是否包含数据库迁移或配置变更再按“备份 - 修改 yaml 里的镜像 tag - docker compose pull - docker compose up -d”的顺序操作。升级失败就快速把 tag 回滚到旧版本重新 up -d所以固定 tag 的好处在这时候体现得淋漓尽致。5.3 我给新手的几个操作建议不要一上来就用 latest 跑生产也不要一开始就拆外部数据库更不要图省事把 JWT 关掉。关掉 JWT 会让整个服务处于裸奔状态任何人只要能访问到端口就可以调用转换接口、伪造回调这个风险完全不值得冒。部署时建议把敏感配置抽到 .env 文件里在 docker-compose.yml 里用 ${JWT_SECRET} 引用。这样 yml 本身可以提交到 Git真正的密钥留在服务器本地。我第一次部署就是直接把密钥写死在 yml 里结果同事把仓库同步到自己的开发机密钥也一起带走了后来只能重新生成并同步所有集成方教训很深。最后再分享一个小习惯每次改动 compose 文件或者升级镜像之前先看一眼当前容器是否能正常出健康检查再备份数据。看起来是土办法但我在实际项目里靠这个习惯避免了至少两次“升级一时爽回滚火葬场”的场面。Docker Compose 给了我们一键部署的能力但真正让服务稳定跑下去的永远是那些不起眼的备份和验证动作。