踩坑实录:离线内网服务器 Docker 部署 PaddleOCR-VL 1.5 完全指南
目标:在公司 完全离线 的内网服务器上部署 OCR 识别服务,供内部人员通过 API 调用。
难点:本地电脑不能访问 GitHub、服务器不能访问外网、公司网络有各种限制、部署者是新手。
整个过程历时 3 天,跨越环境准备、离线传输、配置修正、API 联调四个阶段,最终成功交付。
一、背景与目标
公司有一批维保合同的扫描件,需要自动识别其中的统一社会信用代码、公司名称、登记机关印章、日期等关键字段。这些文件通常带有印章遮挡、排版错位,传统 OCR 很难处理。
我选择的方案是 百度的 PaddleOCR VL 1.5------这是一个多模态大模型,支持版面分析和文字识别,对复杂排版有很好的鲁棒性。
参考的部署案例来自博客园:iamkun2005 的部署文章。
二、环境与前置准备
2.1 硬件配置
| 项目 | 配置 |
|---|---|
| 服务器 GPU | 单张 NVIDIA L20,46GB 显存 |
| 本地电脑 | Windows + Docker Desktop |
| 操作系统 | Ubuntu 24.04 (WSL 2) |
虽然不是原作者的双卡 4090,但 46GB 显存完全够用。
2.2 关键限制
- 本地电脑:公司网络屏蔽了 GitHub,但可以访问国内镜像站(如 Gitee、百度镜像仓库)
- 服务器:完全无法访问外网,只能通过 U 盘或者内网文件共享传输文件
- 连接方式:堡垒机登录,无法直接使用 SSH 隧道
2.3 服务器环境验证
部署前需要先确认服务器的基础环境是否就绪:
bash
# GPU 信息
nvidia-smi
# 输出:NVIDIA L20,46GB 显存,CUDA 12.6 ✅
# Docker 版本
docker --version
# 输出:27.3.1 ✅
# NVIDIA Container Toolkit
nvidia-ctk --version
# 输出:1.16.2 ✅
# 检查 daemon.json 中的 nvidia runtime 配置
cat /etc/docker/daemon.json
# 输出:runtimes.nvidia 已配置 ✅
三、第一阶段:本地电脑准备离线包
因为服务器不能联网,所有镜像和配置文件必须在能联网的电脑上准备好,再拿到服务器上部署。
3.1 拉取 Docker 镜像
两个镜像都来自百度的容器镜像仓库(国内地址,不受 GitHub 限制):
powershell
# 镜像 1:推理引擎(vLLM 加速)
docker pull ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddleocr-genai-vllm-server:latest-nvidia-gpu-offline
# 镜像 2:API 服务(Web 服务器 + 版面分析)
docker pull ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddleocr-vl:latest-nvidia-gpu-offline
两个镜像总共约 25GB(压缩后),下载需要 1-2 小时。
3.2 保存镜像为 tar 文件
下载完成后保存为离线文件:
powershell
docker save ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddleocr-genai-vllm-server:latest-nvidia-gpu-offline -o D:\paddleocr-vlm-server.tar
docker save ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddleocr-vl:latest-nvidia-gpu-offline -o D:\paddleocr-vl-api.tar
最终两个文件大小:
paddleocr-vlm-server.tar:6.84 GBpaddleocr-vl-api.tar:5.91 GB
3.3 获取配置文件
因为 GitHub 打不开,我从Gitee(国内代码托管平台)获取了官方配置文件:
https://gitee.com/paddlepaddle/PaddleOCR/tree/main/deploy/paddleocr_vl_docker/accelerators/nvidia-gpu
核心需要的 3 个文件:
.env:定义镜像版本号和环境变量compose.yaml:Docker Compose 编排文件pipeline_config_vllm.yaml:OCR 流水线配置(版面分析、文字识别的具体参数)
⚠️ 关键教训 :一开始我手写了一个
docker-compose.yml,但缺少启动命令和环境变量,导致服务起不来。务必使用官方配置文件。
四、第二阶段:文件传输与服务器加载
4.1 文件传输
把本地准备好的 3 个文件传到服务器上:
paddleocr-vlm-server.tarpaddleocr-vl-api.tarcompose.yaml、.env、pipeline_config_vllm.yaml
我放在了 /root/hjl/paddleocr-deploy/ 目录下。
4.2 加载镜像
bash
cd /root/hjl/paddleocr-deploy/
docker load -i /root/hjl/paddleocr-vlm-server.tar
docker load -i /root/hjl/paddleocr-vl-api.tar
验证:
bash
docker images | grep paddleocr
# 看到两个镜像,大小分别为 14.4GB 和 10.5GB ✅
4.3 端口冲突修正
官方配置文件默认两个服务都用 8080 端口,但服务器上已经跑着 Kubernetes,可能会有端口冲突。我把映射端口改成了:
- API 服务:宿主机的 6511 → 容器内 8080
- 推理引擎:不对外暴露(仅内部通信)
yaml
# compose.yaml 修正部分
paddleocr-vl-api:
ports:
- "6511:8080" # 原来是 8080:8080
4.4 防火墙放行
服务器本地防火墙没有拦截 6511 端口,但公司网络设备(交换机/ACL)限制了非标准端口。最后是提工单请网络管理员放行了 127.0.0.1:6511。
五、第三阶段:启动服务与验证
5.1 启动
bash
cd /root/hjl/paddleocr-deploy && docker compose up -d
启动很快(约 95 秒),两个容器都是 healthy 状态:
bash
docker ps
# paddleocr-vlm-server Up (healthy) 8080/tcp
# paddleocr-vl-api Up (healthy) 6511→8080/tcp
5.2 健康检查
bash
curl http://localhost:6511/health
# {"errorCode":0,"errorMsg":"Healthy"} ✅
5.3 验证 GPU 占用
bash
nvidia-smi
# GPU 显存占用约 20GB / 46GB,VLLM::EngineCore 占用主要显存 ✅
六、第四阶段:API 联调(最大的坑)
6.1 问题:curl 调用返回 422
我想用文件上传的方式调用:
bash
curl -X POST http://localhost:6511/layout-parsing -F "file=@/root/hjl/3.jpg"
返回:
json
{"errorCode": 422, "errorMsg": "Input should be a valid dictionary..."}
6.2 排查发现:API 接受 JSON,不是 Form Data
查看 OpenAPI 文档:
bash
curl http://localhost:6511/openapi.json | python3 -m json.tool
发现 /layout-parsing 接受的是 JSON ,file 字段类型是 string,需要传 Base64 编码。
6.3 正确的调用方式
最终成功的请求体:
json
{
"file": "Base64编码的图片字符串",
"fileType": 1
}
关键参数:
fileType: 0→ 文件路径(如/home/user/doc.pdf)fileType: 1→ Base64 编码的二进制数据
💡 核心教训 :我在这一步卡了整整 2 个小时,试了纯 Base64、带
data:image/...前缀、file://协议都不行。最后发现是fileType参数用错了------传 0 的话 API 会把 base64 字符串当成文件路径去解析,自然找不到文件。
6.4 最终成功
Swagger 页面上测试通过:http://127.0.0.1:6511/docs,直接上传图片即可得到识别结果。
识别的示例(一张设备识别卡):
[doc_title] 设备识别卡
[text] SMT-CV-1471
[text] 型号:YCS-G042
[text] 厂商:JuDi
[text] 序列号:0191730
[text] 固定资产:NA
[footer] 回非关健设备
七、总结与经验
7.1 踩过的坑 & 解决方案
| 问题 | 表现 | 解决 |
|---|---|---|
| GitHub 无法访问 | 获取不了配置文件 | 用 Gitee 替代 |
| Docker Desktop 安装 | 权限错误 | 以管理员身份运行,清理残留 |
| 手写配置文件不完整 | 缺少启动命令/模型挂载 | 使用官方配置文件 |
| 端口冲突 | 默认 8080 被占用 | 改为 6511 并申请防火墙放行 |
| API 调用 422 错误 | curl 参数格式不对 | 用 JSON + Base64 + fileType=1 |
| 堡垒机无法 SSH 隧道 | 端口被公司网络封锁 | 提工单请管理员放行 |
7.2 核心要点
- 配置文件必须用官方的:手写的缺胳膊少腿,很容易漏掉启动命令和流水线配置
- 端口冲突要提前规划:尤其是跑着其他服务的生产服务器
- fileType 参数是调试 API 的关键:0 是文件路径,1 是 Base64 编码
- 健康检查很重要:vlm-server 加载模型需要时间,API 服务依赖它 healthy 后才能启动
- Swagger UI 比 curl 方便:FastAPI 自带的界面能自动处理参数校验,调试效率高很多
7.3 部署清单速查
bash
# 停止服务
cd /root/hjl/paddleocr-deploy && docker compose down
# 启动服务
cd /root/hjl/paddleocr-deploy && docker compose up -d
# 查看日志
docker logs paddleocr-vl-api --tail 20
docker logs paddleocr-vlm-server --tail 20
# 查看 GPU
nvidia-smi
# 健康检查
curl http://localhost:6511/health
八、下一步计划
- 性能优化:双卡 L20 可以尝试模型并行,提升推理吞吐量
- 监控告警:接入 Prometheus,监控 GPU 使用率和 API 调用量
- 批量处理:写个脚本支持批量 PDF 上传和结果导出
- docker-compose 优化:加入日志轮转、资源限制等生产化配置
如果你也在做类似的离线部署,欢迎评论区交流,我会尽力解答~
版权声明:本文基于真实的部署经历和与 AI 助手的交互记录整理而成,希望对你有所帮助。