我最近被一个很实际的需求折腾了一下:要把一批扫描版PDF里的公式、表格、版式完整地抽成可编辑的Markdown。手写当然不现实,人工校对又费时间,所以就盯上了开源文档解析引擎 MinerU。这东西对论文、合同、扫描件的版面识别确实强,但它要真正跑起来是有门槛的——一堆深度学习模型、Python依赖、版本匹配问题,直接按老套路去装,很容易先耗费一两个小时搭环境。
后来我换了思路,用 Docker Compose 来做 MinerU 本地部署,把镜像、模型缓存、待处理文件全部通过一份 compose 文件管理起来。正是这个决定让整个过程压缩到了10分钟左右,而且换机器换目录都能复用。这篇文章就把完整的部署过程、版本自查方法、Compose 文件设计思路,以及我在实际跑通后遇到的几个高频坑一起写出来。无论你是不是第一次接触 Docker,只要跟着下面的步骤走,应该都能把这套解析环境搭起来。
1. 为什么用 Docker Compose 部署 MinerU,而不是先 pip install 一堆依赖
先聊一个可能劝退很多人的问题:MinerU 到底是个什么东西,为什么不能像普通 Python 包那样“pip install 完直接用”?
MinerU 本质上是一个文档解析引擎,底层依赖了多种用于版面检测、公式识别、表格结构还原、文字 OCR 的深度学习模型。它的工作方式不是单纯把 PDF 里的文字读出来,而是先做版面分析,识别出标题、正文、图片、表格、公式这些区域,再分别调用对应的识别模型,最后组合成结构化的 Markdown 或 JSON。
这就带来了一个直接问题:它依赖的 Python 版本、PyTorch 版本、CUDA 版本、模型权重文件,如果全部靠本机环境去管理,非常容易冲突。我记得早期我为了跑类似工具,曾在 conda 里折腾过半天,最后被一个 torch 版本不兼容问题卡住。而且模型权重往往上百 MB 甚至几个 GB,下载一次、缓存一次,路径还不统一,时间久了机器上会散落一堆不知道能不能复用的模型文件。
Docker 的意义在于把这些复杂依赖一次性打进镜像里。你拿到的是一个已经校准过运行环境的容器,里面有配好的 Python、依赖库和推理框架,不需要操心本机装了什么版本。而 Docker Compose 的加入,解决的则是第二个问题:你要怎么管理运行参数。
单独用 docker run 也能启动 MinerU,但如果你不希望每次敲一长串参数,还希望把输入目录、输出目录、模型缓存目录固定下来,Compose 这种声明式配置就是更好的选择。写一份 docker-compose.yml 放到项目里,以后不管换机器还是换部署环境,只要文件还在,执行同一条命令就能把环境拉起来。
这也解释了为什么标题里敢写“10分钟部署”。真正的时间消耗不在配置本身,而是环境固化和模型缓存准备。Compose 文件一次写好,后续就是重复使用的事。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 部署前的环境基线:Docker 20.10+ 与 Compose 2.0+ 意味着什么
很多人在部署 MinerU 时遇到的第一个问题不是 MinerU 本身,而是本机的 Docker 环境太老。项目官方在安装文档里通常会明确要求 Docker 20.10+ 和 Docker Compose 2.0+,这两个数字不是随便定的,它们背后有实际的技术原因。
先看 Docker 20.10 这个门槛。如果你只是在 CPU 上跑 MinerU,老版本 Docker 未必不能用,但一旦想用 GPU 加速,20.10 对 NVIDIA 容器运行时的集成才比较完善。MinerU 这类深度学习工具的镜像通常比较大,里面包含的推理框架也依赖新版 Docker 对资源和设备的管理能力。从降低排错成本的角度讲,直接跨过旧版本门槛是值得的。
再看 Compose 2.0+。如果你以前用过 Docker,大概率知道老的 docker-compose 命令(带横杠),它和现在主推的 docker compose(带空格)是两代东西。Compose v2 已经用 Go 语言重写,并作为 Docker CLI 的官方插件发布,安装和升级路径比原来干净得多。
检查本机环境,执行这两条命令就够了:
bash复制docker --version
docker compose version
如果第一条能正常显示版本号,说明 Docker 已经装了;第二条能显示类似 Docker Compose version v2.x.x,说明 compose 插件也在。
不同系统的表现有差异,我简单列一下可能遇到的情况:
| 系统/环境 | 一般检查方式 | 常见问题 |
|---|---|---|
| Ubuntu/Debian | 上面两条命令 | 如果 compose 命令不存在,需要安装 docker-compose-plugin |
| CentOS/RHEL | 上面两条命令 | 老版本 CentOS 自带的是旧 Docker 包,可能需要先升级到 Docker CE |
| Windows + WSL2 | WSL2 内执行上面命令 | 要用 WSL2 后端,不要用老的 Hyper-V 后端 |
| macOS Docker Desktop | 菜单里查看 Engine 版本 | Docker Desktop 一般自带新版本,注意区分 compose v1/v2 |
如果你在 CentOS 上发现 docker compose 这条命令不存在,而系统里只有 docker-compose,不代表完全不能跑,但我个人建议还是把新版补齐。旧版 Compose v1 在很多细节上和 v2 行为不一致,排错时容易踩到预期之外的坑。
还要强调一个细节:Docker 服务是否正常启动是很多人忽略的检查项。有时候命令版本都对,但 daemon 没起来,任何后续操作都会报连接错误。可以用下面这条命令确认:
bash复制docker info
能显示容器、镜像、运行中容器数量等信息,就说明 daemon 是活的。
3. Compose 文件准备:目录结构、镜像选择与挂载设计
环境确认没问题后,下一步不是急着拉镜像,而是先把目录结构和 docker-compose.yml 准备好。为什么先说目录?因为 MinerU 在容器内处理文件时,输入输出都依赖挂载目录,如果目录没规划好,后面执行命令时会频繁出现路径找不到的问题。
我先说一个通用目录结构,你可以根据自己的习惯调整:
text复制mineru-deploy/
├── docker-compose.yml
├── cache/
├── input/
└── output/
cache:存放模型缓存,容器每次重建后不需要重新下载模型。input:把待解析的 PDF 放在这里。output:解析出的 Markdown、JSON 等结果会写到这个目录。
接下来是核心配置文件。我用下面这份 Compose 文件作为基础版本,来说明每个字段的作用:
yaml复制services:
mineru:
image: opendatalab/mineru:latest
container_name: mineru
volumes:
- ./cache:/root/.cache
- ./input:/data/input
- ./output:/data/output
environment:
- TZ=Asia/Shanghai
逐行看:
image: opendatalab/mineru:latest 指定了从 Docker Hub 拉取官方 MinerU 镜像。这里故意用了 latest,方便你第一次部署时拿到最新可用版本。如果之后确定了一个更稳定的版本,我会建议把 latest 改成具体的镜像 tag,这样后续升级不会因为镜像更新而出现意外行为。
container_name: mineru 是给容器取一个固定名字。如果同一台机器上可能要跑多个 MinerU 容器,这个名字就需要改掉,否则会冲突。如果你不喜欢固定名字,也可以把这行注释掉,让 Docker 自动生成。
volumes 是这份文件里最值得重视的部分。./cache:/root/.cache 把宿主机的 cache 目录映射到容器内的 /root/.cache。MinerU 首次运行时会把模型权重下载到这个位置,如果你不把目录持久化,每次删除容器、重建容器后都得重新下载一遍。对于动辄几百 MB 甚至几 GB 的模型来说,这个代价是非常不划算的。
./input:/data/input 和 ./output:/data/output 则分别把输入输出目录映射进容器。容器内统一使用 /data/input 和 /data/output 这两个路径,不管宿主机的实际目录在哪。这样好处是:解析命令写成固定的容器内路径即可,不会因为宿主机目录差异而反复修改命令。
environment 里我设了一个 TZ=Asia/Shanghai,主要是让容器内的日志时间戳和本机时区一致。对于只做离线解析任务的场景,时区影响不大,但如果你后面要接入 HTTP 服务、看日志排错,时区统一能省不少困惑。
这里需要说清楚一件事:Compose 文件里我没有设置 command。因为 MinerU 的常见用法是“输入一个 PDF,输出一份 Markdown”,它更适合作为命令行工具被调用,而不是作为一个需要持续运行的 Web 服务。所以这份 Compose 文件的定位是一个“随时可调用的解析容器”,每次执行解析任务时,用 docker compose run 来启动它。
如果你确实需要让它以 API 服务形态常驻,需要在 command 字段里改成对应服务的启动命令,并且加上 ports 暴露端口。不同版本的 MinerU API 入口有过调整,最靠谱的做法是先启动一个容器进去查看帮助,根据实际输出调整启动命令。我会在下一部分演示如何查看容器内部的命令,避免凭印象写死。
4. 第一次完整启动:从 docker compose up 到成功解析 PDF
配置准备好后,就可以开始真正的部署了。进入 mineru-deploy 目录,执行:
bash复制docker compose up -d
这条命令会做三件事:检查本地是否存在 opendatalab/mineru:latest 镜像,不存在就拉取;根据 Compose 配置创建容器;在后台启动容器。
第一次执行时,镜像拉取时间会比较长,这取决于网络情况和镜像大小。如果拉取持续卡住或报超时,先确认执行机器能否正常连接 Docker Hub,这类网络连通性问题往往是你等了很久之后才暴露出的元凶。
容器启动后,可以用下面的命令确认状态:
bash复制docker compose ps
docker compose logs --tail=50
按照我上面的配置,由于没有 command,容器的默认启动命令可能执行后直接退出。这其实不影响后续使用,因为我们要用 docker compose run 来执行解析任务。但如果你想先看看容器里有什么命令可用,可以这样操作:
bash复制docker compose run --rm mineru bash
进入容器后,先敲一下帮助命令,确认可用的 CLI 入口:
bash复制mineru --help
不同版本的输出可能有差异,但大体上你会看到类似“指定输入 PDF/图片路径、指定输出目录”的选项。看到帮助信息,说明环境本体已经能工作了。
接下来准备一个测试 PDF。把一个简单的 PDF 放进 mineru-deploy/input 目录,比如叫 demo.pdf,然后退出容器,在宿主机执行:
bash复制docker compose run --rm mineru mineru -p /data/input/demo.pdf -o /data/output
这条命令的意思是:临时启动一个 MinerU 容器,执行容器内的 MinerU 解析命令,输入路径是容器内 /data/input/demo.pdf,对应宿主机 input/demo.pdf;输出目录指向容器内 /data/output,对应宿主机 output 目录。
第一次运行通常会触发模型下载,因为容器内的 /root/.cache 还没有对应的模型文件。此时你会在终端里看到类似“downloading model”的日志。模型下载完成后,才会进入真正的版面分析阶段。模型一旦下载到宿主机 cache 目录,下一次运行就不会再重复下载了,这也是为什么我反复强调缓存目录持久化很重要的原因。
解析完成后,打开宿主机的 output 目录,里面应该会按 PDF 名称生成一个子目录,里面包含 Markdown 文件和中间 JSON 文件。用编辑器打开 Markdown,检查重点:
- 正文段落是否按原顺序排列
- 表格是否还原成 Markdown 表格
- 公式是否以合适的形式保留
- 图片是否被抽取到独立目录
我第一次跑通时,最大的感受是表格识别比预想中好很多——复杂表格的边框线、合并单元格都被还原到 Markdown 结构里了,这比直接用 PyMuPDF 提取文字然后手工整理要省太多事。
5. 启动失败、停止报错与模型加载:几个高频坑的排查笔记
部署本身不算复杂,但“跑通”和“稳定运行”之间还是隔着几个真实的坑。我把自己遇到过或帮别人排查过的几类问题整理出来,每个都有对应的排查思路。
第一个是 docker compose stop 或 docker compose down 时报错,类似:
text复制Cannot stop docker compose application. Reason: compose [stop] exit status 1
看到 exit status 1 时,第一反应不应该是一个具体配置问题,而是要看容器当前处于什么状态。这种情况经常出现在容器主进程已经异常退出、但 Docker 还保留着容器记录,或者容器内某个脚本收到了 SIGTERM 信号却没有在规定时间内退出。先执行:
bash复制docker compose ps -a
docker inspect mineru --format '{{.State.Status}}'
如果容器状态是 exited,但 docker compose down 还报错,可以直接强制删除容器再执行 down:
bash复制docker rm -f mineru
docker compose down
这个操作不会动到 cache 和 input/output 挂载目录里的数据,所以可以放心执行。为了避免下次再被 stop 超时卡住,建议在 Compose 文件里加上:
yaml复制restart: unless-stopped
这样即使容器进程意外退出,Docker 也会自动把它重新拉起来,不会留下一堆半死状态的容器记录。
第二个坑是模型加载失败或初始化报错。常见表现是执行解析命令后,日志走到“加载模型”阶段就中断,或者直接提示找不到模型文件。原因不外乎几种可能:
- 容器内
/root/.cache没有写入权限 - 宿主机
cache目录磁盘空间不足 - 挂载路径不对,导致模型文件没落到容器预期位置
排查时,先进容器确认挂载是否生效:
bash复制docker compose run --rm mineru ls -la /root/.cache
如果这个目录内容为空,而宿主机 cache 目录里已经有下载好的文件,多半是挂载没生效或者容器内路径不一致。此时可以用 docker compose exec 查看当前运行容器的挂载情况,也可以直接使用 docker inspect 检查 Mounts 信息:
bash复制docker inspect mineru --format '{{json .Mounts}}'
看到宿主机路径和容器路径一一对应,问题基本就在容器内路径是否写错了。
第三个坑更隐蔽:解析英文 PDF 正常,但解析中文 PDF 时出现乱码或字体缺失。原因是容器镜像里没有预装中文字体,遇到 PDF 内嵌字体信息不完整时,OCR 或渲染环节就会出问题。解决思路是给容器补中文字体,但每次都手动进容器安装不是长久之计。更干净的做法是写一个自定义 Dockerfile,在官方镜像基础上安装字体包后重新打一个镜像,再把 Compose 文件里的 image 改成自己的镜像。这样跨机器部署时,字体问题不会再反复出现。
第四个问题对用过 Dify 等平台的人比较有参考价值。如果你在 Dify 里接入 MinerU 做文档解析,偶尔会看到类似“An error occurred in the langgenius/mineru/mineru, please contact the author”的提示。看到这个先别急着怀疑插件作者,绝大多数情况是 MinerU 所在的容器没有和 Dify 容器处于同一个 Docker 网络,或者调用时填的模型服务地址不对。检查两个容器是否在同一网络:
bash复制docker network ls
docker inspect <容器名> --format '{{json .NetworkSettings.Networks}}'
再把服务地址改成实际可达的容器名或 IP,一般就能解决。这类问题跟 MinerU 本身的解析能力无关,更多是容器网络互通的问题。
6. 内网环境与 GPU 加速:两个越往后越需要的延伸话题
如果你的场景只是在个人电脑上偶尔解析几个文档,上面四部分内容已经够用了。但部署这件事,越往后越会碰见两个延伸需求:一是在内网服务器上离线部署,二是使用 GPU 加速解析速度。这两个问题在实际工作中出现的频率非常高,而且很多人在这一步才真正理解了镜像和模型缓存的关系。
先看内网部署。很多企业或实验室的服务器是不直接对外开放下载权限的,这时候没法在那台机器上直接 docker pull。标准做法是“一台有下载权限的机器上打包镜像,再传到内网机器上导入”。
具体流程是:
- 在具备 Docker Hub 访问条件的机器上执行:
bash复制docker pull opendatalab/mineru:latest
- 把镜像保存成压缩包:
bash复制docker save opendatalab/mineru:latest | gzip > mineru-image.tar.gz
- 将压缩包拷贝到目标内网机器,然后导入:
bash复制docker load < mineru-image.tar.gz
- 导入完成后,在目标机器的项目目录里执行
docker compose up -d。
这里要注意:docker save 保存的是镜像,不是模型缓存。MinerU 的模型权重如果之前没打到镜像里,首次运行还是需要联网下载。所以在内网场景下,我建议在具备网络条件的那台机器上先手动把模型下载到缓存目录,然后把模型缓存目录一起拷贝到内网机器,再挂载进容器。实际操作中,可以压缩 /root/.cache 下对应的 MinerU 模型目录,随镜像压缩包一起拷贝。否则你会发现镜像虽然导进去了,但每次解析都会卡在模型下载这一步。
再聊 GPU 加速。MinerU 在 CPU 上也能跑,但解析一个大文件时,CPU 推理的时间可能会让人失去耐心。如果你手头有 NVIDIA GPU,官方支持通过 GPU 加速。
启用 GPU 前先确认两件事:
- NVIDIA 显卡驱动已安装,
nvidia-smi能正常输出 - NVIDIA Container Toolkit 已安装,容器才能访问 GPU
检查工具是否就绪:
bash复制nvidia-smi
docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi
上面第一条命令验证驱动,第二条命令验证 Docker 是否能调用 GPU。第二条命令如果报错,说明 Container Toolkit 还没装好,先去安装并重启 Docker 服务。
确认无误后,在 Compose 文件里加上 GPU 资源声明:
yaml复制services:
mineru:
image: opendatalab/mineru:latest
volumes:
- ./cache:/root/.cache
- ./input:/data/input
- ./output:/data/output
environment:
- TZ=Asia/Shanghai
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
重新执行:
bash复制docker compose up -d
容器启动后,进容器验证 GPU 是否可见:
bash复制docker compose run --rm mineru nvidia-smi
如果能看到 GPU 信息,说明 MinerU 的推理可以走 GPU。实际效果上,公式密集、表格复杂的文档提升最明显,CPU 可能需要几十秒甚至几分钟,GPU 通常能压缩到几秒到十几秒的水平。
GPU 版本有一个容易踩的坑:驱动版本和镜像内 CUDA 版本不匹配。遇到启动容器时报 CUDA 相关错误时,先看镜像里用的是哪个 CUDA 版本,再对照宿主机驱动支持的 CUDA 版本,不要盲目换驱动或换镜像。
最后分享一个我实际用下来的体会:真正影响部署时间的往往不是执行命令本身,而是是否提前把缓存目录和输入输出目录想清楚。只要缓存目录持久化做到位,Compose 文件里各路径固定,这套环境在个人电脑上跑通之后,搬到服务器上也能做到一小时内完成部署。如果你一开始只是贪快、跳过目录规划这一步,后面遇到模型重下、路径失效这类问题时,付出的时间会远超节省下来的那几分钟。
