内网离线部署ZLMediaKit:Docker镜像导入与避坑实践

发布时间:2026/9/29 15:38:02
内网离线部署ZLMediaKit:Docker镜像导入与避坑实践 简介ZLM Docker离线安装资源包面向需要在内网或离线环境部署ZLMediaKit流媒体服务的运维人员与Docker使用者解决公网镜像仓库无法访问导致安装受阻的问题适合已具备基础Docker操作能力的中级用户。作为开源流媒体服务框架ZLMediaKit在直播、音视频转发等场景中被广泛使用而离线环境下的镜像分发正是许多内网项目的常见需求。资源共2个文件包含1个tar格式的Docker镜像文件和1个sh安装脚本压缩后整体约209MB。tar文件用于直接导入离线镜像sh脚本则封装了从镜像加载到容器启动的关键流程可显著降低手工操作和排错成本。目前已有375人学习整体精简实用适合离线机房、私有化部署或临时演示环境下的快速上线。通过该资源读者可获得一套完整的离线安装方案不仅免去公网拉取依赖也为后续容器化配置、迁移与维护留下稳定可靠的基础操作方法。1. 离线装 zlm内网机器为什么卡在 docker pull 这一步在一台没有外网出口的服务器上安装 zlmZLMediaKit 流媒体服务器大部分人第一反应是执行docker pull zlmediakit/zlmediakit:master然后眼睁睁看着超时、重试、再超时。zlm 本身不是一个复杂的服务难点从来不在docker run那条命令上而是镜像怎么从有网环境安全送进内网以及送进去之后架构是否匹配、端口是否放通、配置文件是否挂对。这篇文章就按这条链路来讲有网机器上打包镜像、内网导入、启动 zlm、多机分发、最后把坑挨个踩一遍。适合内网机房、国产化服务器、安防国标平台这类没有公网访问能力的部署场景。2. 准备 zlm 离线镜像包选 tag、锁平台、打包压缩一条龙2.1 先查目标机架构把 docker pull 的 --platform 参数钉死离线安装的第一步不是下载镜像而是确认目标服务器的 CPU 架构。zlm 官方镜像在 Docker Hub 上同时发布多个平台版本最常见的是linux/amd64和linux/arm64。x86_64 的 Intel/AMD 服务器自然不用说国产化环境里飞腾、鲲鹏、麒麟这类 ARM 架构的机器也大量存在。如果不管架构直接拉默认镜像离线导入后启动容器大概率会报exec format error后面避坑章节会专门讲。# 在目标内网机器上执行记住输出结果 uname -m输出x86_64对应 amd64输出aarch64对应 arm64。确认之后在有外网的开发机上拉镜像时用--platform参数把平台锁死。千万不要图省事直接docker pull默认 tag因为默认 tag 会根据执行命令的机器自动选择平台你的开发机是 x86_64拉下来的镜像到鲲鹏服务器上就用不了。# 在有外网的机器上执行指定目标平台拉取 docker pull --platform linux/arm64 zlmediakit/zlmediakit:master参数说明--platform告诉 Docker 客户端去 registry 拉取指定平台的镜像清单前提是镜像仓库发布了多架构 manifest。拉取成功后可以用docker image inspect再次确认实际架构避免打包半天才发现拉错了平台。如果你的开发机是 Windows Docker Desktop命令完全一样但要注意 Docker Desktop 必须处于 Linux 容器模式Windows 容器模式下无法拉取 Linux 镜像这属于环境前提不满足的话会直接拉到不存在的平台镜像并报错。2.2 docker save 加 gzip离线导出 zlm 镜像包的标准打包法镜像拉下来之后要把镜像从 Docker 的存储层里导出来变成一个可以拷走的文件。常见做法是docker save接gzip压缩。这一步和离线装 mysql、离线装 node 的思路完全一致先在能联网的机器上把安装包缓存下来再通过移动介质或内网传输运到目标机器。docker save zlmediakit/zlmediakit:master | gzip zlm-master-arm64.tar.gz # 导出完成后立刻做完整性校验 gzip -t zlm-master-arm64.tar.gz echo archive ok逻辑说明docker save会把镜像的所有分层、元数据、tag 信息打包成一个 tar 流这里直接管道给gzip做压缩避免先生成一个大 tar 再压缩占用双倍磁盘。gzip -t是校验命令测试压缩文件是否完整这一步不能省U 盘拷贝中断、网传丢包都会导致 tar 包损坏不校验直接拿去内网 load失败了还要回头排查是文件问题还是 Docker 问题。参数说明docker save的输入是镜像名或镜像 ID输出默认写标准输出所以必须重定向到一个文件。tar 包的文件名建议带上平台标识和镜像 tag我习惯用zlm-master-arm64.tar.gz这种格式因为同一个镜像在不同平台下会有多个包混在一起很容易拿错。这个 tar 包要妥善留档后续分发给其他内网机器可以反复使用不需要每台机器都重新走一遍有网环境。2.3 三种离线搬运方式U 盘、scp、内网 HTTP 下载站tar 包打好之后要进内网。根据环境限制有三种常见做法U 盘或移动硬盘物理拷贝适合没有管理网络、也不方便开 SSH 的隔离机器scp 走内网管理网传输适合运维可以登录的服务器临时起一个 HTTP 服务批量分发适合同时要给多台机器下发的情况。# 在有网机器上执行临时起 HTTP 下载服务 cd /data/zlm python3 -m http.server 8000 # 在内网机器上执行用 curl 拉取 curl -O http://192.168.1.10:8000/zlm-master-arm64.tar.gz逻辑说明python3 -m http.server是 Python 自带的静态文件服务零依赖、起服务只需一条命令把当前目录里的文件暴露到指定端口。内网机器用curl -O直接下载-O表示按远端文件名保存到本地。传输完成后一定要对比文件大小或者重新算一遍 md5HTTP 传输本身有 TCP 校验但大文件在弱网环境也会出现超时截断稳妥的办法是下载后立即gzip -t校验。参数说明8000 端口可以改成任意未被占用的端口但要确保内网机器的防火墙放通了 TCP 8000 入站。HTTP 临时服务用完立即停掉不要长期开着否则内网其他机器都可以访问/data/zlm目录下的文件存在文件泄露风险。如果内网机器和目标服务器之间存在跳板机scp是更安全的选择scp zlm-master-arm64.tar.gz usertarget:/data/zlm/一条命令完成但需要目标机器开放 SSH 端口。3. 内网导入并启动 zlm从 docker load 到第一条 RTSP 流3.1 docker load 导入后用 docker images 和 image inspect 双重确认tar 包到达内网机器后第一件事是导入到 Docker 引擎。docker load支持直接读取 gzip 压缩包不需要预先解压这一点有经验的工程师也会偶尔忘记。docker load -i zlm-master-arm64.tar.gz docker images | grep -i zlmediakit docker image inspect zlmediakit/zlmediakit:master --format {{.Os}}/{{.Architecture}}逻辑说明docker load -i从文件导入镜像-i指定输入文件路径。导入完成后再用docker images确认 tag 是否正常显示。如果镜像名和 tag 显示为none:none说明 tar 包导出时没带 tag 元数据需要用docker tag手动补上镜像名和版本。最后一条命令是验证架构输出应该是linux/arm64或linux/amd64这一步不能跳过。参数说明--format是 Go template 语法{{.Os}}输出操作系统{{.Architecture}}输出 CPU 架构。如果输出和uname -m的结果对不上比如目标是 aarch64 但镜像显示 amd64这台机器上跑容器必然失败不用继续往下走回头重新拉平台正确的镜像再打包。3.2 最小 docker run 启动 zlm只映射三个入口端口也能出流镜像导入成功先不要急着挂配置文件、调 WebRTC用最小命令把 zlm 跑起来。zlm 默认同时监听 HTTP、RTSP、RTMP 三类端口80 是 HTTP 管理入口和 HTTP-FLV 拉流入口554 是 RTSP 端口1935 是 RTMP 端口。跑通这三个端口zlm 的核心能力已经可用。docker run -d --name zlm \ --restart unless-stopped \ -p 80:80 \ -p 554:554 \ -p 1935:1935 \ zlmediakit/zlmediakit:master逻辑说明-d让容器以后台守护态运行--name zlm指定容器名方便后面docker logs zlm查日志。--restart unless-stopped是容器重启策略Docker 服务重启时容器自动拉起手动 stop 的容器除外。端口映射格式是宿主机端口:容器端口宿主机上的 80 端口如果被 Nginx 或别的服务占用把左边改成 8080 即可右边的 80 是 zlm 在容器内部实际监听端口不能改。参数说明zlm 的默认端口是镜像内置的不同版本可能有差异启动后立即通过日志确认实际监听情况。docker logs zlm | tail -50 curl http://127.0.0.1:80/docker logs会输出 zlm 启动时的监听日志正常情况下能看到 RTSP、RTMP、HTTP 分别监听的端口。curl http://127.0.0.1:80/如果返回一段 JSON 或 HTML 内容说明 HTTP 服务已经在跑。如果 curl 没有任何响应不要急着怀疑 Docker 网络先看日志里监听端口有没有和-p映射的容器端口对上这是 zlm 容器部署最常见的翻车点。3.3 挂载 config.ini 前先把镜像内的配置模板拷到宿主机zlm 的监听端口、RTC 端口范围、鉴权、事件回调都写在config.ini里。容器跑通之后如果只是加端口映射改变不了容器内部 zlm 的实际监听行为必须把配置文件挂载出来才能调整。难点在于不同版本的 zlm 镜像配置文件路径不一致有的放在/opt/media/conf/有的放在/opt/media/bin/直接把宿主机路径挂载进去非常容易踩坑。# 用 docker create 创建一个临时容器不启动它 docker create --name zlm-tmp zlmediakit/zlmediakit:master # 把镜像里的配置文件拷到宿主机 docker cp zlm-tmp:/opt/media/conf/config.ini /data/zlm/config.ini # 如果上面 docker cp 报错先看镜像内目录结构 docker run --rm zlmediakit/zlmediakit:master ls /opt/media/ # 确认拷出后删除临时容器 docker rm zlm-tmp逻辑说明docker create只创建不启动容器文件系统已经生成但进程没有运行正好用来docker cp提取文件。docker cp支持从容器路径拷到宿主机路径第一次执行如果路径不对会报no such file or directory那就用ls命令查看镜像内实际目录结构再调整路径。配置模板拷出来后zlm 每次启动前会先读宿主机的配置再启动监听服务。参数说明/data/zlm/是宿主机目录按项目实际情况自己定建议放在独立数据盘而不是系统盘。config.ini是 zlm 唯一需要持久化的配置文件日志目录如果后续要排错也可以挂出来。挂载配置文件启动时注意加:ro只读挂载避免容器运行时有逻辑回写配置文件导致宿主机文件被污染。docker run -d --name zlm \ --restart unless-stopped \ -p 80:80 \ -p 554:554 \ -p 1935:1935 \ -v /data/zlm/config.ini:/opt/media/conf/config.ini:ro \ zlmediakit/zlmediakit:master挂载启动后用vim修改宿主机的/data/zlm/config.ini再docker restart zlm重启容器端口调整就能生效。不同镜像版本配置字段有差异改之前先grep -n port /data/zlm/config.ini找到对应字段。字段和镜像版本不匹配时zlm 启动会直接失败这个坑在避坑章节详细说。4. 内网 registry 私有仓库zlm 镜像多机分发的正规军打法4.1 什么场景该上私有仓库save/load 在多机场景的痛点如果内网只有一台机器要装 zlmsave/load 完全够用。但内网有 5 台、10 台服务器都要部署 zlm而且镜像每两周升一次级你就要开始思考更高效的分发方式。save/load 的痛点是每台机器都要手动拷 tar 包、手动执行 load机器多了之后会搞混版本这台机器是 master 分支那台机器是上一个 tag排查起来非常头疼。在内网起一个 Docker registry 私有仓库让所有内网机器统一从私有仓库拉镜像是解决这个问题的正规做法。装上第一个 zlm 之后后面再离线装 mysql、redis只需要统一推送到私有仓库各机器docker pull即可。这个投入一次性的但省下来的是后面每次扩容和升级的重复劳动。4.2 离线把 registry:2 容器跑起来并配置 insecure-registries搭建私有仓库的前提是registry:2镜像本身也要先离线导入。这一点经常被忽略内网机器没有任何公网访问能力registry 镜像靠什么来靠的就是和有网环境一样的流程先在有网机器docker pull registry:2save 成 tar 包再 load 进内网服务器。# 内网服务器上导入 registry 镜像后启动私有仓库 docker run -d --name registry \ --restart always \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ registry:2逻辑说明-p 5000:5000把 registry 默认端口暴露到宿主机私有仓库地址就是这个 IP 加 5000 端口。-v /data/registry:/var/lib/registry是把镜像仓库数据持久化到宿主机/data/registry目录registry 容器本身是无状态的所有镜像数据都写在这个目录里系统盘空间小的话一定要挂到独立数据盘。参数说明--restart always让 registry 随着 Docker 服务一起启动私有仓库是基础设施挂了会影响所有机器的部署重启策略必须拉满。启动后 registry 默认只监听 HTTP但 Docker 客户端默认用 HTTPS 连接到非 localhost 的仓库地址直接 push 必然报错。解决方式是修改每台内网机器的/etc/docker/daemon.json{ insecure-registries: [192.168.1.10:5000] }修改完成后重启 Docker 服务让配置生效systemctl restart docker注意这句systemctl restart docker会先停掉所有容器再拉起来内网机器上如果已经跑了 zlm 或其他服务要挑维护窗口执行不要在业务高峰期重启。insecure-registries的意思是告诉 Docker 客户端这个地址允许走 HTTP 明文访问仅适用于内网可信环境不要把公网地址也这样配。4.3 push / pull 全流程与两种分发方式的选择表私有仓库起来之后把 zlm 镜像推上去后面所有机器直接从仓库拉取。内网首次部署时先把 tar 包 load 进任意一台机器之后走向就是标准的 tag push pull 流程。# 在已导入 zlm 镜像的机器上打 tag 并推送 docker tag zlmediakit/zlmediakit:master 192.168.1.10:5000/zlm:master docker push 192.168.1.10:5000/zlm:master # 在另一台内网机器上直接拉取 docker pull 192.168.1.10:5000/zlm:master逻辑说明docker tag给本地镜像打一个新 tagtag 前缀包含仓库地址这样docker push才知道推到哪里。推送成功后任意一台配置了insecure-registries的内网机器都可以直接docker pull私有仓库地址。pulling 之后启动容器的镜像名要改成192.168.1.10:5000/zlm:master不要再用原来的zlmediakit/zlmediakit:master后者在内网机器上不存在。两种分发方式各有适用场景。save/load 更简单直接不依赖任何基础设施适合一次性部署和单机环境。私有仓库需要额外维护一个 registry 容器和镜像存储空间但适合多机长期运维镜像主副本只有一个版本管理更清晰。选择建议机器少于 3 台用 save/load超过 3 台或者有持续升级计划就上 registry。对比项save/load内网 registry适合规模单机或少量服务器3 台以上有多机部署需求镜像版本管理手工维护 tar 包容易混版本tag 统一管理pull 即用首次部署成本低一条 load 命令需要额外部署 registry 容器镜像更新每台机器重新拷 tar 包推送一次各机器自行 pull依赖组件无registry 镜像和存储磁盘5. zlm 离线安装避坑架构、端口、配置映射五个真实翻车点5.1 docker run 报 exec format error镜像架构和目标机不匹配现象tar 包正常 loaddocker images能看到镜像但docker run容器立刻退出日志里出现exec user process caused: exec format error。原因镜像平台和目标机器 CPU 架构不一致。最常见的是在有网 x86 开发机上拉取了 amd64 镜像传到鲲鹏、飞腾这类 arm64 服务器上执行。docker load只负责把镜像层解压出来不检查能否在本机运行所以错误要到docker run阶段才暴露。解决回到有网机器用docker pull --platform linux/arm64重新拉取重新 save/load。在目标机器上用uname -m确认架构再与docker image inspect 镜像名 --format {{.Architecture}}的结果对比。两个值对不上直接重新打包。5.2 管理页面打不开端口映射的容器端口和实际监听端口对不上现象容器启动成功docker ps能看到端口映射但curl http://127.0.0.1:80/超时或拒绝连接。原因-p 8080:80只做了宿主机 8080 到容器 80 的转发但容器内 zlm 实际监听的不是 80或者镜像内 zlm 默认把 HTTP 端口改成了其他值。端口映射的目标端口是写死的不会自动跟随 zlm 内部配置变化。解决先docker logs zlm看启动日志里 zlm 监听了哪些端口再docker exec zlm netstat -lntp查看容器内部实际监听端口最后调整-p映射让宿主机端口指向正确的容器端口。如果 zlm 内部默认监听 10080就要写-p 8080:10080外面访问8080才能到达容器内的10080。这是新手最容易忽略的地方端口映射不是配了就能通两边端口必须一致。5.3 挂载 config.ini 后 zlm 启动异常配置模板与镜像版本不匹配现象不挂载配置文件时容器跑得很好挂载宿主机config.ini后启动失败或者启动成功但不监听预期端口。原因宿主机上的config.ini是从另一个版本镜像里拷贝出来的字段名和当前镜像的期望不匹配。zlm 不同版本对配置字段的兼容性并不完美老配置里的某个字段在当前版本已经改名或废弃解析失败后 zlm 回退到默认值甚至直接退出。另一个常见原因是配置文件是 Windows 编辑的换行符是 CRLFLinux 下解析乱掉。解决配置文件一定要从当前要运行的镜像里提取不要从别的机器拷贝。如果已经挂载错了先去掉挂载把容器跑起来用docker cp从当前镜像重新提取config.ini再挂载。宿主机上的配置文件用vim或sed修改不要在 Windows 记事本里编辑再传上去。另外挂载方式用单个文件挂载不要整个目录挂载整个目录挂载会把你想挂载的配置目录里的其他文件也覆盖掉。5.4 向私有仓库 push 报 http: server gave HTTP response to HTTPS client现象docker push 192.168.1.10:5000/zlm:master时报错提示http: server gave HTTP response to HTTPS client。原因Docker 客户端默认对非 localhost 的 registry 地址使用 HTTPS 连接而内网的 registry 容器只提供 HTTP 服务。客户端收到 HTTP 响应后发现证书不对拒绝继续。解决在客户端机器的/etc/docker/daemon.json里配置insecure-registries把仓库地址写进去然后systemctl restart docker。注意所有需要从这个仓库拉镜像的内网机器都要配不只是 push 那一台。配置完成后可以用docker pull 192.168.1.10:5000/zlm:master验证连通性。如果重启 Docker 会中断现有容器提前规划维护窗口。生产环境如果对安全有要求给 registry 配置 TLS 证书是最终方案但内网离线环境大多数场景用 insecure 模式就够了。5.5 WebRTC 推拉流建连失败UDP 端口段没有映射出来现象RTSP、RTMP、HTTP-FLV 都能正常推拉流但是 WebRTC 页面一直黑屏或者建立连接要等很久最后失败。浏览器控制台能看到 ICE 候选失败服务器日志显示打洞失败和超时。原因WebRTC 走的是 UDPzlm 在配置文件里有一组 RTC 媒体端口范围默认是10000-20000。容器启动时如果只映射了 80/554/1935 这组 TCP 端口UDP 媒体流进不了容器WebRTC 就建立不起来。另外即使映射了 UDP 端口宿主机防火墙没有放行对应 UDP 段同样会失败。解决启动容器时补上 UDP 端口段映射docker run -d --name zlm \ --restart unless-stopped \ -p 80:80 \ -p 554:554 \ -p 1935:1935 \ -p 10000-20000:10000-20000/udp \ zlmediakit/zlmediakit:master参数说明/udp后缀指定这个端口段走 UDP 协议TCP 和 UDP 的端口映射是分开配置的只写数字默认是 TCP。10000-20000 这个范围要和容器内 zlm 的配置文件一致不同镜像版本可能不同先在自己的镜像里确认范围再写。宿主机防火墙同样要放行 UDP 10000-20000 入站两者缺一不可。6. 用 docker compose 固化 zlm 部署再用 ffmpeg 做冒烟验证6.1 docker compose 固化端口与挂载离线机器小团队友好离线部署还有个隐藏麻烦下次扩容要重新敲一遍 docker run漏一个参数就要对比半天。我把端口映射、配置文件挂载、重启策略统一写进 compose 文件镜像用私有仓库地址这样任何一台内网机器上都能一键拉起。services: zlm: image: 192.168.1.10:5000/zlm:master container_name: zlm restart: unless-stopped ports: - 80:80 - 554:554 - 1935:1935 - 10000-20000:10000-20000/udp volumes: - /data/zlm/config.ini:/opt/media/conf/config.ini:ro - /data/zlm/logs:/opt/media/logs逻辑说明compose 文件里image用私有仓库地址机器上只需预先docker pull一次后面每次docker compose up -d都会检查本地有没有这个镜像不会去公网拉取。volumes里第一个是配置文件只读挂载第二个是日志目录挂载方便日志持久化查看。6.2 ffmpeg 冒烟验证rtmp 推流、http-flv 拉流一条命令闭环部署完成不能只看端口在监听就交付真正要验证的是推流和拉流链路。我习惯准备一个几十秒的测试视频文件用 ffmpeg 推 RTMP 流再用 ffprobe 拉 HTTP-FLV 确认流能读出来。# 启动容器 docker compose up -d docker compose logs -f zlm # 推一路 RTMP 流到 zlm ffmpeg -re -i test.mp4 -c copy -f flv rtmp://127.0.0.1:1935/live/test # 用 ffprobe 验证 HTTP-FLV 拉流是否能读到流信息 ffprobe http://127.0.0.1/live/test.flv逻辑说明ffmpeg -re按视频原始帧率读取本地文件-c copy不做转码直接复制编码流-f flv推流封装格式是 FLV。推流地址里/live/是 zlm 默认的应用名test是流 ID。拉流验证用ffprobe走 HTTP-FLV 协议如果命令能解析出视频流的分辨率、编码格式信息说明从推流到存储到拉流整条链路都是通的。RTSP 链路也可以顺手验证把地址换成rtsp://127.0.0.1/live/test。我现在的习惯是每装一台内网机器交付前必须做一遍推和拉的冒烟验证确认 RTMP、HTTP-FLV、WebRTC 三条链路都通才收工顺手把 tar 包和 compose 文件统一留档。zlm 做版本升级时也先在测试机上跑一遍推拉流再批量分发这个习惯帮我少加了很多次班。希望帮到你。本文还有配套的精品资源点击获取