简介:在公安、医疗、教育等多种内网或隔离网络环境中,ZLMediaKit(ZLM)流媒体服务常因无法访问外网镜像仓库而难以部署。针对这一问题,这份 Docker 离线安装包提供了从镜像到容器的一站式资源,面向需要快速交付监控、直播、WebRTC 场景的运维和开发人员。包内仅有2个文件,整体约208.73MB,核心为 tar 格式的 Docker 镜像离线包,可直接 docker load 导入;配套的 sh 安装脚本则自动处理导入、建容器、端口映射和基础配置流程,降低手工干预与出错概率。已有375人学习下载,说明在内网部署需求中具有较高的实用参考价值。通过该资源,读者能绕开在线拉取镜像的限制,在多台内网主机间批量复用,并可根据实际需求调整脚本中的配置参数,灵活适配不同业务规模,有效提升流媒体平台的离线交付与维护效率。
1. 为什么要在内网环境用 Docker 离线装 ZLM:从拉镜像到搬镜像的真实痛点
做流媒体服务的同行应该都有这种经历:一台不能出网的机器,需要部署 ZLMediaKit(简称 ZLM)来承担 RTSP、RTMP、HLS 的接入与分发,可“在线拉镜像、打包搬运、离线导入”这条链路里每一步都埋着坑。ZLM 是 C++ 写的流媒体服务,编译依赖多,手工在内网服务器上编译一次很可能耗掉一两天,还未必编得过;用 Docker 做离线部署,核心套路就是“在线机器 docker pull -> docker save 导包 -> 拷贝 tar 包 -> 离线机器 docker load -> 配置启动”。这篇文章从选型理由讲到端口映射,把镜像搬运、离线导入、容器配置和常见故障一次说透,让你在断网环境也能把 ZLM 稳稳跑起来。
2. 离线安装前的架构决策:ZLM 的 Docker 镜像形态与部署选型
先说结论:ZLM 用 Docker 部署,不是因为它比裸机跑得快,而是图省心——编译链、运行时依赖、可执行文件都被镜像封好了。离线部署关注的第一件事,不是 docker run 怎么写,而是你手上有没有一个能用的镜像 tar 包。
2.1 ZLM 为什么适合 Docker 部署:流媒体服务的依赖与端口画像
ZLMediaKit 的核心是一个 C++ 流媒体服务器,处理 RTSP、RTMP、HLS、HTTP-FLV 等协议。官方提供源码和编译产物,但编译要求 gcc、cmake、libavcodec-dev、libssl-dev 等一批第三方库,内网机器往往装不全。Docker 镜像则把二进制、配置文件以及运行所需的系统库全部封装好,在离线机器上只要满足一个前置条件:docker 守护进程能跑起来。
端口画像也很关键。ZLM 默认会监听多个 TCP 端口:RTSP 默认 554,RTMP 默认 1935,HTTP 默认 80(不同发行版和配置会有差异)。此外,RTP/RTCP 传输默认还会用一段范围的 UDP 端口(常见是 10000-20000,具体看 config.ini 里 [rtp] 相关配置)。这意味着用 bridge 网络时,你不能只映射一个端口,而是按实际业务把 TCP 端口和 UDP 端口段都映射到宿主机。很多人在离线部署后说“容器起了但拉流不通”,一半以上的原因出在端口映射不完整。
另外一个容易忽略的点是,ZLM 容器里跑的进程可能不是 root 用户。如果挂载了宿主机目录作为录像或日志目录,容器内用户要能写进去;否则启动脚本会尝试创建目录但失败,轻则日志写不进去,重则容器直接退出。这在后面的避坑章节会具体展开。
2.2 在线环境用 docker pull 拉取镜像:找对镜像名的几个来源
离线安装的前提是先有一台能联网的机器。这台机器可以是开发机、办公网服务器,甚至是装过 Docker Desktop 的 Windows 笔记本。操作是在线机器上把镜像拉到本地,再 docker save 导出。
怎么找镜像名?ZLM 在 Docker Hub 上有社区发布的镜像,常见仓库名是 zlmediakit/zlmediakit。可以这样确认:
docker search zlmediakit搜索结果里下载量高、更新频繁的优先看,点进仓库页面能看到 tag 列表和镜像说明。要提醒一句:流媒体服务涉及接入层安全,不要用那些和个人博客绑定的镜像,因为你不知道镜像里被塞了什么东西。优先选仓库描述里写明了 ZLM 版本的镜像。如果你在内网有私有仓库(比如 Harbor),也可以在有网机器上 pull 后 push 到内网仓库,再由离线机器从内网仓库 pull,这样连 tar 搬运都省了。
拉取命令本身不复杂:
docker pull zlmediakit/zlmediakit:master如果只写 docker pull zlmediakit/zlmediakit,默认 tag 是 latest。latest 在流媒体项目里往往不稳定,生产环境建议锁定你验证过的版本号。另一点是如果镜像来自第三方 registry,save 的时候要连 registry 名一起带,比如 registry.example.com/zlm/zlm:1.0,否则离线 load 之后 tag 会丢,还得手动补。这个“tag 丢失”的问题,在离线部署里非常常见,后面也会讲到。
2.3 用 docker save 导出镜像:Tag 规范和三层镜像结构的理解
镜像在 Docker 里的存储视图是分层的。docker save 做的事情是把镜像的元数据加每一层的内容打成一个 tar 包。与之容易搞混的是 docker export,它导出的是容器的文件系统快照,不含历史层和 tag 信息——用 docker export 导出的包在离线机器上 docker load 不进去。一句话记住:save 镜像,export 容器。
导出的命令很简单:
docker save -o zlm-offline.tar zlmediakit/zlmediakit:master参数说明:-o 指定输出文件路径,后面跟的是镜像的 repo:tag。这里有一个细节:save 时镜像定位用的是 repo:tag,不是镜像 ID。如果你用镜像 ID 导出,load 出来的镜像会没有 tag,还得手动 docker tag 补上,在内网环境容易造成混乱。另外,tar 包默认不压缩,几百 MB 的镜像导出来就是几百 MB。要缩小体积可以再加一层 gzip:
docker save zlmediakit/zlmediakit:master | gzip > zlm-offline.tar.gzload 的时候用 docker load -i zlm-offline.tar.gz,docker 能识别 gzip 压缩包,不需要先手动解压。需要注意的是,save 之后别急着把 tar 拷走,先看一眼 tar 包大小。ZLM 镜像带着 ffmpeg 相关库,体积一般都在几百 MB,如果导出来只有几十 MB,要么是镜像本身瘦身过,要么是 save 命令没打全。拷贝到离线机器后,最好用 md5sum 或 sha256sum 比对两边文件校验值,这一步能排除掉“拷着拷着包坏了”的低级问题。
3. 离线环境镜像搬运:docker save / load 的完整操作与参数说明
这一章把离线机器上的 docker load 操作讲透,同时把多架构镜像的处理方法单独拎出来。离线部署里“load 报错”这件事,很多不是 docker 命令本身的问题,而是前面哪一步没做对。
3.1 离线机器上 docker load 导入镜像:命令参数与成功标志
离线机器上,假设你已经把 zlm-offline.tar 放到了 /opt/zlm/ 下,导入操作是:
docker load -i /opt/zlm/zlm-offline.tar命令执行完,窗口会打印一行,形如Loaded image: zlmediakit/zlmediakit:master这样。看到这一行才算导入成功。如果你执行完只看到 “Openstdin” 或者没有任何输出,说明命令没走对。常见原因有三个:-i 后面跟的路径不对;tar 包损坏;当前用户没有权限访问 /opt/zlm 目录。
导入之后用 docker images 验证镜像是否真的在本地列表里:
docker images | grep zlmediakit如果 grep 不到,而 load 又提示成功,多半是镜像 tag 和你 grep 的不一致。这时用 docker images --no-trunc 看完整列表,再决定是改 grep 还是补 docker tag。这里有一个经验做法:导入完不要马上 docker run,先 docker inspect 确认镜像是哪个架构的:
docker inspect zlmediakit/zlmediakit:master | grep Architecture如果离线机器是 ARM 架构(比如飞腾、鲲鹏的机器),而镜像是在 x86 开发机上导出的,这里会显示 amd64,run 的时候容器会直接报 exec format error。这个问题在国产化机器上特别常见,很多人折腾半天以为是镜像坏了,其实是架构不匹配。
3.2 多架构镜像的离线搬运:ARM 和 x86 的导出差异
多架构镜像的坑越早讲越好。docker pull 默认拉取与当前机器架构匹配的镜像。你在 x86 开发机上 pull 下来的是 amd64 版本,不能拿到 ARM 机器上跑。反过来也一样。
怎么处理多架构?有两种方式。一种是在目标架构的机器上直接 pull 后 save——这要求该机器能联网,和离线前提冲突。另一种是在 x86 机器上用 docker manifest 查看镜像支持哪些架构,再用 --platform 参数拉取指定平台:
docker manifest inspect zlmediakit/zlmediakit:master docker pull --platform=linux/arm64 zlmediakit/zlmediakit:master然后 save 出来的 tar 包就是 arm64 架构的,可以拷到 ARM 机器上 load。需要注意:--platform 参数要求 docker 客户端和守护进程都支持 manifest 列表,Docker 20.10 之后通常没问题,老版本会直接忽略这个参数,把当前机器架构的镜像再拉一遍——这个行为很隐蔽,因为命令不报错,只是拉下来的不是你要的架构。判断方法还是 docker inspect 看 Architecture。
还要提醒一点:如果你的离线机器是多位 CPU 的虚拟化环境,比如 VMware 虚拟机里跑的 Linux,架构一般跟随虚拟机的 CPU,不会自动变成别的架构。镜像架构和宿主机架构不一致时,容器启动会立刻报错退出。所以离线包准备前,先确认目标机器的架构是 x86_64 还是 aarch64,不要想当然。
3.3 离线机器本身没装 Docker:前置条件与最小准备
离线装 ZLM 有个前置条件:目标机器上 docker 守护进程必须能正常工作。如果你连 docker 都没装,镜像层面的搬运就没有意义。离线装 docker 的常见做法是:在有网的机器上下载 docker 的 rpm/deb 包和所有依赖,拷贝到离线机器上本地安装。
以 CentOS 7 为例:
# 有网机器上,用 yumdownloader 把 docker 及其依赖打到本地目录 yumdownloader --resolve docker-ce docker-ce-cli containerd.io # 拷贝 rpm 目录到离线机器,rpm -ivh 安装 rpm -ivh *.rpm装完后启动 docker 服务并设开机自启:
systemctl start docker systemctl enable docker这里有两个常见问题。第一,docker 服务起不来时,journalctl -u docker 里通常会有明确报错,比如 iptables 相关、网络驱动相关,先按报错修再继续。第二,装好之后 docker 命令可能需要 sudo。当前用户要执行 docker 命令,要么加入 docker 组:
usermod -aG docker $USER newgrp docker要么直接用 sudo 执行。离线环境里最容易忽略的是这个权限问题,一旦没配好,docker load 和 docker run 都会报 permission denied,看起来像是 docker 没装好,实际上只是用户没权限。
4. ZLM 容器启动的必填配置:端口映射、配置文件与日志挂载
镜像加载成功后,docker run 的命令和参数直接决定了 ZLM 能不能用。这一章给出最小启动命令、配置文件必改项,以及如何用 docker-compose 固化整个部署流程。
4.1 最小可用启动命令:端口映射和 Volume 挂载
ZLM 容器的最小启动命令,我一般这么写:
docker run -d --name zlm \ -p 1935:1935 \ -p 554:554 \ -p 8080:8080 \ -p 10000-20000:10000-20000/udp \ -v /opt/zlm/config.ini:/opt/media/conf/config.ini \ -v /opt/zlm/record:/opt/media/bin/record \ zlmediakit/zlmediakit:master逐段解释:-d 表示后台运行,--name zlm 固定容器名方便 docker logs 和 docker stop。-p 做了四组映射:RTMP 的 1935、RTSP 的 554、HTTP 的 8080,以及 RTP/RTCP 的 UDP 端口段。UDP 端口段范围要和你 config.ini 里 RTP 配置保持一致,如果不一致,收流时会出现“能注册但拉流黑屏”的怪问题。
-v 挂载了配置文件和对外录像目录。注意容器内配置文件路径在不同版本的 ZLM 镜像里不一样,我这里是按常见镜像的 /opt/media/conf/config.ini 写的;如果你的镜像里不是这个路径,先起一个临时容器进去找:
docker run --rm -it zlmediakit/zlmediakit:master find / -name "config.ini"找到实际路径再改挂载。这个“先探路径再挂载”的做法能省掉很多试错时间。还有一点:如果先用默认配置跑通了,后面再去改挂载,容器会重启,但镜像里的 config.ini 已经被覆盖成默认值,你之前对容器内文件的修改会丢。所以最稳的做法是第一次 run 就把宿主机里的 config.ini 准备好、挂载进去,不要依赖容器内编辑。
4.2 配置文件 config.ini 的关键项:端口、HTTP 端口与鉴权
ZLM 的配置项很多,离线部署时不需要全懂,但有三处必须确认。
第一处是 [api] 段的 secret。ZLM 提供一套 HTTP API,用于查询流状态、主动断开流。secret 是访问 API 的密钥,按默认值跑完全没有防护。离线环境虽然是内网,但内网不等于安全,把 secret 改成随机字符串是部署时顺手要做的事。这个字段改完,调用 API 时带上对应参数就行,文档里一般叫 apic 或类似名字,以你手上版本的为准。
第二处是协议端口。config.ini 里通常有分别控制 RTSP 监听端口和 RTMP 监听端口的配置项。如果你的容器启动命令映射的是 554 和 1935,配置里的监听端口也要改成对应值,不然端口映射目标和容器内实际监听对不上。一个导致“端口已映射但拉流失败”的高频原因就是两边端口不一致。
第三处是日志和录像目录。ZLM 默认可能把录像写到容器内的某个目录,离线部署时建议挂载出来,方便以后排查流量问题时翻录像。日志级别也可以顺手调整,比如把默认的 info 调成 warn,减少磁盘写入。
改完配置怎么验证?直接在宿主机上 curl 一下容器内 HTTP 服务:
curl http://127.0.0.1:8080/index/api/getServerInfo如果返回一段 JSON 且包含 serverId 字段,说明容器内 HTTP 服务和配置都正常。如果 curl 提示连接被拒绝,先看 docker logs zlm 有没有报错,再看端口映射和容器内实际监听端口是否一致。这个问题在离线环境里特别常见——因为你在宿主机上测 8080,容器内部可能因为配置没生效而根本没有监听 8080。
4.3 用 docker-compose.yml 固化离线部署流程
docker run 命令可以跑通一次,但离线部署要可复制,最好把参数写进 docker-compose.yml。离线机器上如果没有 docker-compose 命令,可以用 docker compose(Docker 内置的子命令)或单独拷贝 docker-compose 二进制。compose 文件的常见写法:
version: "3.3" services: zlm: image: zlmediakit/zlmediakit:master container_name: zlm restart: always ports: - "1935:1935" - "554:554" - "8080:8080" - "10000-20000:10000-20000/udp" volumes: - /opt/zlm/config.ini:/opt/media/conf/config.ini - /opt/zlm/record:/opt/media/bin/record注意 image 字段的 tag 必须和 docker load 进来的镜像 tag 完全一致,否则 compose 会尝试重新拉取镜像,这在离线环境会直接卡死在拉取阶段。这是离线部署最常见的“假故障”之一。compose 里 restart: always 建议保留,流媒体服务挂了自动拉起很重要。
如果用 docker compose 而不是 docker-compose,命令是 docker compose up -d,后面没有横杠。两种工具的命令参数略有差异,离线环境里你装的是哪个就用哪个,别混用。compose 文件写好后,在文件所在目录执行:
docker compose up -d之后用 docker compose logs -f zlm 看实时日志,和 docker logs -f zlm 效果类似。把部署参数沉淀成 compose 文件,重新部署时只需要拷 tar 包、配一个 config.ini、跑一条命令,这是离线运维该有的状态。
5. 离线安装 ZLM 的避坑记录:镜像、依赖与容器运行五类常见问题
这一章专门记录我在这条路上踩过的坑,按现象、原因、解决来整理。每一条都来自真实部署场景,值得在动手前先看一遍。
5.1 现象:docker load 报 “invalid tar” 或提示找不到镜像
原因:docker save 导出的 tar 包损坏,或者别人给你的 tar 不是 docker save 产物,而是 docker export 出来的容器快照。我遇到过一次,运维同学从网上下了一个 ZLM docker 包,docker load 时直接报 invalid tar format。检查之后发现那是容器 export 的打包,不是镜像 save 的包。docker export 导出的 tar 解压后能看到一堆文件系统和目录,而 docker save 的 tar 解压后是层目录和 json 元数据。
解决:重新用 docker save 打包,拷贝时比对文件 MD5 或 sha256 校验值。如果只有 export 包,可以先用 docker import 把它变成镜像,再 docker save 导出,但这样会丢失镜像的构建历史和 tag,不推荐作为长期方案。
5.2 现象:容器起来了,推流拉流不通,telnet 端口却在
原因:大多数情况是 UDP 端口段没有映射,或者映射范围和 config.ini 里 RTP 配置不一致。ZLM 的媒体流转发依赖 RTP/RTCP,和 HTTP 这类 TCP 服务不一样,端口没监听直接表现为拉流超时。TCP 端口映射上了,telnet 通,给人感觉服务是好的。
解决:检查 docker run 命令里是否包含完整的 UDP 范围映射。写成10000-20000:10000-20000/udp没错,但还要确认宿主机防火墙放行了 UDP。离线环境通常没有 iptables 规则管理习惯,遇到就先临时关防火墙验证:
systemctl stop firewalld如果关掉后立即能拉流,就去加永久规则,而不是保持防火墙关闭状态。另外要确认 config.ini 里的 RTP 端口范围和映射的 UDP 端口段一致。这个不一致的问题很隐蔽,因为 ZLM 会正常启动,端口也监听了,但数据包不往这个口走。
5.3 现象:docker load 成功,docker compose up 却提示拉取镜像
原因:compose 文件里 image 字段的 tag 和本地镜像 tag 不一致。Docker Compose 找镜像的逻辑是精确匹配 repo:tag,本地有 zlmediakit/zlmediakit:master,但 compose 里写的是 zlmediakit/zlmediakit:latest,它会尝试去 registry 拉取,而不会自动用本地另一个 tag。
解决:先 docker images 列出本地镜像 tag,把 compose 文件改成一致。如果你希望两个 tag 都能用,可以补一个 tag:
docker tag zlmediakit/zlmediakit:master zlmediakit/zlmediakit:latest这个操作在离线环境非常有用,尤其当配置文件里写的是 latest,而实际 load 进来的 tag 是其他版本时。
5.4 现象:容器几秒后自动退出,docker logs 看不到有效日志
原因:常见为镜像入口脚本需要写配置文件或录像目录,而挂载目录权限不对。ZLM 的容器一般以非 root 用户运行,挂载到宿主机的 /opt/zlm/record 如果属主是 root,容器内写不进去,启动逻辑直接退出。
解决:先把宿主机目录权限放宽,或者指定用户运行:
mkdir -p /opt/zlm/record chown -R 1000:1000 /opt/zlm/record这里的 1000 是容器内用户的 UID。更省事的做法是用 docker run -u 0 强制 root 运行,但生产环境不建议,还是把权限对齐比较稳妥。如果容器秒退后 docker logs 里完全没有输出,优先检查权限和入口脚本路径,不要在 ZLM 配置上反复找问题。
5.5 现象:镜像 load 成功,run 时报 exec format error
原因:镜像架构和宿主机架构不一致。x86 开发机上 save 出来的镜像,拿到 ARM 机器上 run,内核直接返回 exec format error。这个问题在国产化平台上遇到得特别多,尤其是飞腾、鲲鹏机器上跑 amd64 镜像。
解决:回到有网机器上用 --platform 参数重新拉取目标架构镜像,save 后重传。如果已经传到内网,可以把内网机器的架构信息报给负责打包的人,由他对应导出。别试图在 ARM 机器上运行 amd64 镜像,qemu 模拟可以做但性能差,且 Docker 官方的 binfmt 支持在离线环境安装起来很麻烦,不值得。
6. 离线安装后验证与进阶:用 RESTful API 自检流媒体服务
镜像通了、容器起了、端口映射了,怎么确认 ZLM 真的在干活?我的习惯是用它自带的 HTTP API 做一轮链路验证,而不是只看进程还在不在。
第一件事是把 API 调通。ZLM 的 HTTP API 默认路径是 /index/api/xxx,比如获取服务器信息:
curl http://127.0.0.1:8080/index/api/getServerInfo如果配置了 secret,请求要带上鉴权参数,具体字段名以你手上版本的接口文档为准。返回 JSON 能看到 serverId、buildTimestamp 这些字段,说明 API 服务正常。
第二件事是拿真实流测一遍。最简单的方式是用 ffmpeg 在宿主机上推一路 RTMP:
ffmpeg -re -i test.mp4 -c copy -f flv rtmp://127.0.0.1:1935/live/test接着在另一台机器上用 ffplay 拉流:
ffplay rtmp://127.0.0.1:1935/live/test如果播放器能出画面,说明从入流到出流的完整转发链路通了。这一步必须做,因为端口通和媒体链路通是两回事,不少人在端口通之后就宣布部署完成,结果实际业务接入时才暴露协议处理问题。拉流验证时如果画面卡顿或者黑屏,先看 docker logs zlm 里有没有关于丢包或解码失败的日志,再做针对性调整。
第三件事是把验证固化成脚本。离线环境的镜像 tar 包不会频繁更换,完全可以写一个 deploy.sh,把 load、tag、compose up、curl 自检四个步骤串起来。这也符合离线部署的长期维护习惯:每次更新镜像包时,跑一遍脚本就能确认新版本没有引入配置兼容问题。脚本里可以加一句判断,curl 返回的 HTTP 状态码不是 200 就退出并打印错误信息,这样排错都在一个地方看。
我的一个习惯是每次离线部署完,先把 config.ini 原文件备份一份再改,防止修改出错后没有后悔药。另外,容器日志要认真看一眼:docker logs zlm 里如果持续出现 socket bind 失败,多半是端口被占用或映射配置冲突,这不是玄学,基本就是端口问题。希望这篇对你有帮助。
本文还有配套的精品资源,点击获取