踩坑实录:离线内网服务器 Docker 部署 PaddleOCR-VL 1.5 完全指南

踩坑实录:离线内网服务器 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 GB
  • paddleocr-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 个文件:

  1. .env:定义镜像版本号和环境变量
  2. compose.yaml:Docker Compose 编排文件
  3. pipeline_config_vllm.yaml:OCR 流水线配置(版面分析、文字识别的具体参数)

⚠️ 关键教训 :一开始我手写了一个 docker-compose.yml,但缺少启动命令和环境变量,导致服务起不来。务必使用官方配置文件


四、第二阶段:文件传输与服务器加载

4.1 文件传输

把本地准备好的 3 个文件传到服务器上:

  • paddleocr-vlm-server.tar
  • paddleocr-vl-api.tar
  • compose.yaml.envpipeline_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 接受的是 JSONfile 字段类型是 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 核心要点

  1. 配置文件必须用官方的:手写的缺胳膊少腿,很容易漏掉启动命令和流水线配置
  2. 端口冲突要提前规划:尤其是跑着其他服务的生产服务器
  3. fileType 参数是调试 API 的关键:0 是文件路径,1 是 Base64 编码
  4. 健康检查很重要:vlm-server 加载模型需要时间,API 服务依赖它 healthy 后才能启动
  5. 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

八、下一步计划

  1. 性能优化:双卡 L20 可以尝试模型并行,提升推理吞吐量
  2. 监控告警:接入 Prometheus,监控 GPU 使用率和 API 调用量
  3. 批量处理:写个脚本支持批量 PDF 上传和结果导出
  4. docker-compose 优化:加入日志轮转、资源限制等生产化配置

如果你也在做类似的离线部署,欢迎评论区交流,我会尽力解答~


版权声明:本文基于真实的部署经历和与 AI 助手的交互记录整理而成,希望对你有所帮助。

相关推荐
岳来1 小时前
docker 容器文件hostconfig.json 文件内容学习
docker·hostconfig.json
SilentSamsara1 小时前
Python 并发基础:threading/GIL 与 multiprocessing 的选型逻辑
服务器·开发语言·数据库·vscode·python·pycharm
东北甜妹1 小时前
K8s -Daemonset,kube-proxy,service,statefulset
linux·运维·服务器
运维老郭1 小时前
【Kubernetes PDB 主动驱逐保护】3 个配置陷阱与正确避坑指南
docker·容器·kubernetes
DeepHacking1 小时前
在电脑 B 上通过局域网 SSH 直接从电脑 A 拉取文件,用 rsync 断点续传
运维·ssh
Season4501 小时前
论close()与signal(SIGPIPE,SIG_IGN)对服务器的重要性
运维·服务器
idolao1 小时前
CentOS 7 安装 xampp-linux-1.8.1.tar.gz 详细步骤(解压、启动、验证)
linux·运维·centos
码点1 小时前
Android 9休眠时任意键唤醒屏幕
android·linux·运维
杨云龙UP1 小时前
Docker 部署 MongoDB 6.0 数据库每日自动备份实践:本地 + 异地保留 7 天_20260429
linux·运维·数据库·mongodb·docker·容器·centos