☰
离线部署Docker:docker.rpm.tar包识别与rpm安装实战避坑
2026/10/8 15:07:36 网站建设 项目流程

简介:一套面向 CentOS 系统的离线 Docker 安装包,聚焦容器化部署场景,内含安装 Docker 所需的 docker、docker-client、docker-common 等核心 RPM 及 container-selinux、oci-systemd-hook 等关键依赖,同时打包了本地 Yum 仓库的 repodata 元数据,适合无法直接访问外网或需要批量部署 Docker 的运维人员。压缩包共 16 个文件,包含 9 个 rpm 二进制包、3 个 gz 压缩的仓库索引、3 个 bz2 压缩的数据库文件以及 1 个 xml 仓库描述文件,整体约 19.27MB,小巧便于内网分发。目前已有 381 人学习下载,适合容器初学者了解 Docker 组件组成,也适合运维人员在隔离网络中快速搭建基础容器运行时。使用时,既可手动执行 rpm 安装,也可将 repodata 作为本地 Yum 源自动处理依赖关系,大幅减少离线安装时逐个寻找依赖包的时间,让 CentOS 环境下的 Docker 部署更加顺畅。

1. docker.rpm.tar 软件包:没外网时,这才是把 Docker 跑起来的唯一家当

很多内网服务器、工控机、比赛现场的电脑上,交付方丢过来的就是一个叫 docker.rpm.tar 的文件夹。打开一看,里面散着十几个 .rpm,外加一两个 .tar 或 .tar.gz,没拆过的人容易以为这是个万能安装包,双击就能装完。实际它的含义很清楚:rpm 是给 RHEL 系系统离线安装 Docker 用的软件包,tar 要么是打包好的 Docker 二进制,要么是 docker save 导出的镜像文件。没有 yum 源、镜像仓库也连不通的时候,想从零把 Docker 跑起来,就得靠这一包东西。这篇按我自己的实操顺序写清楚:怎么区分里面每样东西、离线装 rpm 的正确姿势、tar 里的镜像怎么导入,以及我会在这里翻车的每个细节。适合运维、部署工程师,也适合那些明天就要交环境、今晚还在对着文件夹发愁的人。

2. 认清 docker.rpm.tar 软件包:rpm、tar.gz、tar 是三种不同的东西

先花十分钟认清这一包文件里到底是什么,比急着敲三条命令省半天时间。这个文件夹的名字其实是个“打包形态”,不是单一软件,我每次拿到手的第一件事就是列目录、看体积、抽查文件类型,确认之后再决定走哪条安装路线。

2.1 官方 rpm 分发包:docker-ce 全家桶不止一个文件

如果你看到的是一组 .rpm,基本就是 Docker 官方给 RHEL/CentOS 系准备的离线安装包。不同版本的 Docker 对应不同文件集合,常见的是这么几个:

文件作用版本要求
docker-ce.rpmDocker 守护进程主体主版本号决定整套版本
docker-ce-cli.rpmdocker 命令行工具必须与 docker-ce 同主版本
containerd.io.rpm容器运行时24.0 之前必须单独装,之后随 docker-ce 拉依赖
docker-buildx-plugin.rpmbuildx 构建插件随 24.0+ 发布
docker-compose-plugin.rpm新版 compose 子命令随 24.0+ 发布
# 拿到手先看目录内容,别急着解压 ls -lh /opt/docker-rpm/ # 输出类似这样: # -rw-r--r-- 1 root root 25M docker-ce-24.0.7-1.el7.x86_64.rpm # -rw-r--r-- 1 root root 37M docker-ce-cli-24.0.7-1.el7.x86_64.rpm # -rw-r--r-- 1 root root 96M containerd.io-1.6.28-3.1.el7.x86_64.rpm

这段命令没有实际安装动作,只是把家底清点清楚。重点看两个信息:一是文件名里的 el7 / el8 / el9,它告诉你这套 rpm 是针对哪个系统大版本编译的,el7 的包硬装到 el8 上大概率起不来;二是 containerd.io 的存在,Docker 依赖它来管理容器生命周期,离线环境最容易漏的就是这个。

很多人以为“docker 装不上是镜像问题”,其实离线环境 80% 的问题是这套 rpm 本身缺依赖,或者系统版本和包版本对不上。记住一个原则:docker-ce 和 docker-ce-cli 必须来自同一套离线包,别混搭,否则 docker 命令连守护进程都找不到。

2.2 tar/tgz 里装的可能是二进制、可能是镜像、也可能只是打包的 rpm

同一个文件夹里的 .tar.gz 才是最容易让人疑惑的部分。我在实际项目里见过三种完全不同的情况,操作方式也完全不同:

第一种是官方二进制发行包,解出来是 usr/bin/docker 这样的目录结构,需要你自己把二进制丢到 /usr/local/bin,再写 systemd unit 文件。第二种是 docker save 导出的镜像文件,解压后能看到 manifest.json 和一串 layer 目录,这种是要用 docker load 导入的。第三种只是把一堆 rpm 包打了个包方便传输,本质还是 rpm 安装,tar 只是运输工具。

# 用列表模式看 tar 包内部结构,不实际解压 tar -tzf docker-bin-24.0.7.tgz | head -20 # usr/bin/docker # usr/bin/dockerd # usr/bin/containerd-shim-runc-v2 # 看到这种路径,是二进制包 tar -tzf mysql-8.0.tar | head -5 # manifest.json # repositories # 1234567890abcdef/layer.tar # 看到这种路径,是 docker save 的镜像包

这条命令帮你在解压前就判断出后续路线。tar -t 是测试列表,z 代表 gzip 压缩,f 指定文件名,head 只取前几行避免大镜像文件刷屏。二进制包和镜像包的区分是离线部署的第一道分岔路,走错了后面全乱。

2.3 判断三连问:体积、结构、时间戳

不需要逆向分析,也不用打开文件看十六进制,我一般就问三个问题:

第一问体积。几百 MB 到一个多 GB 的 tar 大概率是镜像文件,因为镜像里带着完整的用户态依赖;几十 MB 的是 Docker 二进制;几 MB 到十几 MB 的基本是脚本或源码包。第二问结构。镜像 tar 的解压结果必然有 manifest.json 和一堆 layer 目录,二进制包解出来是 usr/bin 结构,rpm 集合解出来是清一色的 .rpm 文件。第三问时间戳。官方二进制包的时间戳是构建当天,镜像 tar 则是你 docker save 那天,如果你发现某个 tar 里全是时间戳异常的文件,先怀疑传输损坏。

# 看文件类型,二进制会显示 ELF,tar 归档会显示 POSIX tar archive file /opt/docker-rpm/docker-24.0.7.tgz file /opt/docker-rpm/mysql-8.0.tar # 第一条输出:gzip compressed data, from Unix # 第二条输出:POSIX tar archive

file 命令是成本最低的判断工具,任何 Linux 发行版都有。它读文件头部的魔数,比后缀名可靠得多,因为很多人会手滑把保存的文件名后缀搞错。识别完这三类东西,下一步就能选安装路线了。

3. rpm 离线安装 Docker:本地仓库 + yum 是最不容易翻车的路径

认清文件之后,进入正题——把这组 rpm 真正装进系统里。我最推荐的不是 rpm -ivh 一个个怼,而是先在本地搭一个 yum 源,让 yum 去处理依赖关系。原因很简单:Docker 全家桶之间有明确的依赖顺序,而且 containerd 和 docker-ce 互相有版本要求,人工按顺序敲指令,敲到第三个包发现前面装错版本,重来一遍的成本比搭源高多了。

3.1 搭建本地源:createrepo 生成 repodata

yum 安装包时不是直接扫描目录里的 rpm,它需要读一个保存依赖索引的 repodata 目录。rpm 文件散落在一起并不构成 yum 源,必须用 createrepo 工具生成这个索引。

# 把所有 rpm 放到统一目录 mkdir -p /opt/docker-rpm cp /path/to/your/rpm/*.rpm /opt/docker-rpm/ # 生成 repodata 索引 yum install -y createrepo createrepo /opt/docker-rpm # 成功后目录里会多出 repodata 子目录,里面有 repomd.xml 和一堆 .gz 元数据

createrepo 的输入是一个目录,输出是这个目录的依赖索引。它扫描每个 rpm 的头部信息,把软件名、版本、依赖关系全部写进元数据。如果你在一台完全没网络的机器上,yum install createrepo 这步会失败,那就得在另一台同版本系统上把 createrepo 的 rpm 包也带进去一起装,或者跳过本地源方案,改用 3.3 的方式。这里我没写任何外部软件源,纯粹是内网自给。

索引生成后,写一个本地源配置文件。文件名可以随便起,后缀必须 .repo:

vi /etc/yum.repos.d/docker-local.repo # [docker-local] # name=Docker Local Repository # baseurl=file:///opt/docker-rpm # enabled=1 # gpgcheck=0

baseurl 的 file:// 协议告诉 yum 直接读本地路径,gpgcheck 设 0 是跳过 RPM 签名校验。离线包多数经过转手,签名校验可能因为丢失公钥而失败,内网环境也没有安全审计要求,关掉是常见做法,但如果你的项目有等保要求,建议保留公钥校验。

3.2 依赖解析优先:yum install docker-ce,而不是 rpm -ivh

本地源就绪后,安装动作比想象中简单:

yum clean all yum makecache yum install -y docker-ce docker-ce-cli containerd.io

这几条命令的逻辑:clean all 清掉旧缓存,防止 yum 拿上次的远程源数据配对本地源导致“找不到包”;makecache 重建缓存,让本地源真正进入 yum 的可见范围;install 一次把三个核心组件装齐,依赖交给 yum 解析。如果 install 时报缺依赖,说明你的离线目录里还缺包,回去找对应 rpm 再扔进 /opt/docker-rpm,重新跑 createrepo --update /opt/docker-rpm 增量更新索引即可。

我见过有人在这里连续踩坑:rpm 文件明明有,yum 却报无法定位软件包。大部分原因是 makecache 没跑,yum 还在拿旧的缓存列表找包。另一部分是 baseurl 路径写错或目录权限不对,yum 进程以 root 跑没问题,如果用到非 root 用户,记得 chmod +r 目录。

3.3 快速但激进的做法:rpm -Uvh --nodeps --force 的适用边界

如果你的离线包里只有两三个 rpm,而且你完全确认系统依赖已经满足(比如之前装过别的版本 Docker),可以用一条命令直接怼:

rpm -Uvh --nodeps --force /opt/docker-rpm/*.rpm # --nodeps 跳过依赖检查,--force 强制覆盖旧版本

--nodeps等于告诉 rpm“别查我系统缺什么”,--force是解决版本冲突的后悔药,但它可能把系统已有的、与 Docker 共享运行时的组件覆盖掉。我的习惯是:只有本地源方案受客观限制走不通,而且这台机器是干净的测试机时才这么用。正式生产环境尤其要避免,因为你不知道系统里有没有程序依赖旧版本的 containerd。

无论哪种方式装完,都要先确认守护进程能起来:

systemctl daemon-reload systemctl enable docker systemctl start docker systemctl status docker --no-pager

enable 是设置开机自启,内网机器没人每天手动敲 start,这一步不能省。status 输出里看到 active (running) 说明 rpm 安装这步已经走通,可以进入镜像导入阶段。如果 status 显示 failed,先别急着卸载重装,看一眼 journalctl -xe 的输出,多半是 SELinux 或 cgroup 的问题,我在第 5 章专门写了排查路径。启动完再跑一下 docker version,确认 Client 和 Server 两个部分都输出版本号,才说明 CLI 能连到 daemon。

4. tar 包里的镜像是主角:save/load 与 run 的最小闭环

rpm 装好的只是发动机,真正的业务是镜像。内网环境没有 docker pull 的通道,镜像要怎么进去?答案就藏在那几个体积最大的 .tar 里。docker save 和 docker load 是一对镜像离线搬家命令,对应的场景是:在有网络的环境里把镜像抓下来,保存成 tar,再搬到内网导入。

4.1 在能联网的机器上提前准备好镜像 tar

所有离线部署的镜像准备工作,都应该在有网络环境的机器上完成。需要拉哪几个镜像,提前列清单,全部 pull 到本地,然后逐个 save。注意 save 和 export 是两个完全不同的命令,save 保留镜像的分层结构和 tag 历史,load 回来还能看到原仓库名;export 导出的是容器文件系统,不含历史,只能当普通文件系统用,离线部署的镜像传输必须用 save。

# 先把需要的镜像拉下来 docker pull mysql:8.0 docker pull redis:7.0 docker pull nginx:1.25 # 逐个保存为 tar,带上版本号 docker save -o mysql-8.0.tar mysql:8.0 docker save -o redis-7.0.tar redis:7.0 docker save -o nginx-1.25.tar nginx:1.25

save 命令的参数结构是 docker save -o 输出文件名 镜像名:标签。输出文件名我建议统一写成镜像名-版本.tar的格式,这样传到内网之后,光看名字就知道里面是什么,不用每次先 load 再去看。如果你想减小体积,可以 gzip 压缩:docker save mysql:8.0 | gzip > mysql-8.0.tar.gz,但内网 load 之前得先解压,多一道工序不说,压缩和解压还吃 CPU,小网络环境反而直接传 tar 更省事。还有人喜欢在 Windows 机器上用命令解压 tar 再拷贝,Win10 以上系统自带 tar 命令,直接tar -xf mysql-8.0.tar就能解开看内容,不必装额外软件。

4.2 内网导入:docker load 与 tag 修复

tar 文件传进内网后,导入动作很简单,但很多人会载入后才发现镜像名不对——save 的时候镜像带有完整仓库地址,比如registry.internal/library/mysql:8.0,load 进来后也会带着这个前缀。

# 批量导入目录下所有 tar 包 for f in /opt/docker-images/*.tar; do docker load -i "$f" done

for 循环是常见做法,把目录下所有 tar 依次 load,避免手工一条条敲。docker load -i 后面跟文件路径,它会自动解包并重建镜像分层。导入后用 docker images 检查:

docker images # REPOSITORY TAG IMAGE ID CREATED SIZE # mysql 8.0 a4fdf1b8e2d3 2 weeks ago 590MB # registry.internal/redis/redis 7.0 b2c1d9e5f0a6 3 days ago 140MB

看到 REPOSITORY 带着旧仓库前缀的,用 tag 命令改一下:

docker tag registry.internal/redis/redis:7.0 redis:7.0 docker rmi registry.internal/redis/redis:7.0

tag 命令先给同一个镜像 ID 再挂一个新名字,rmi 把旧名字删掉,镜像本身还在,只是仓库名干净了。这一步很多人忽略,导致 compose 文件里写的redis:7.0拉不到镜像,因为在本地它只存在另一个名字。如果你拿到的 tar 包本身就没有 tag,导入后 REPOSITORY 和 TAG 会显示<none>,这种情况我会单独讲,见 5.5。

利用 tar 列表加 xargs 批量解包也可以,比如把所有 tar 文件名先列出来再交给 xargs 调 load,但在现场我通常直接写 for 循环,更直观,方便中间加 echo 打印执行进度。

4.3 从镜像到容器:docker run 参数清单

镜像 load 完成,最后一步是把它跑成容器。这里我给一个生产可用的最小命令模板:

docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=YourPass \ -v /data/mysql:/var/lib/mysql \ --restart=always \ mysql:8.0

参数说明:-d后台运行,否则终端会一直挂着输出日志;--name给容器起名,后续 docker logs mysql8、docker exec -it mysql8 bash 都靠这个名字定位;-p 宿主机端口:容器端口做端口映射,外面访问机器的 3306 才能进到容器里的 MySQL;-e传环境变量,MySQL 镜像靠它初始化 root 密码;-v挂载数据卷,把容器里的数据目录映射到宿主机 /data/mysql,不然容器一删数据全没,这是最常被忽略的生产习惯;--restart=always设置开机自启和异常重启,内网机器不会有人每次开机手动 start 容器。

见过不少人在这里把-p漏了,然后访问不到容器内的 MySQL,又回来怀疑 load 的镜像有问题。其实镜像好好的,端口没映射出去而已。先跑通再调参,是我一贯的顺序:用最小命令把容器拉起来,确认 docker ps 里 status 是 Up,再去改端口、挂卷、环境变量,别第一步就把所有参数堆上去,出了问题反而分不清是哪条参数导致的。

5. 离线装 Docker 常见坑:现象、原因、解决一条条对

这一章全是实操中一次次翻车换来的清单。每一条我都按“现象 → 原因 → 解决”的顺序写,你可以当排查手册用,遇到问题先对号入座。

5.1 明明建了本地源,yum 却报“无法定位软件包”

现象:执行 yum install docker-ce 后,输出 No package docker-ce available 或者 Ubuntu 系的“无法定位软件包”,本地源文件明明存在,文件也在目录里。原因有三个:一是 yum 缓存没重建,还在拿旧索引找包;二是 repo 文件没配对,baseurl 指向的路径和 rpm 实际存放路径不一致;三是 rpm 目录权限不对,yum 进程读不到文件。解决方法是先执行 yum clean all && yum makecache,再 yum repolist enabled,确认 docker-local 出现在 enabled 源列表里;然后检查 baseurl 路径,用 ls 直接看那个目录能不能列出 rpm;最后 chmod -R 755 /opt/docker-rpm 排除权限问题。如果 repolist 里根本没有 docker-local,就是 repo 文件本身没生效,检查文件后缀是不是 .repo、内容是不是放到 /etc/yum.repos.d 下。

5.2 提示“没找到rpm命令”,但系统明明是 RHEL 系

现象:执行 rpm -ivh 直接报 command not found,而你又正好想用 rpm 安装包管理工具,看起来是个死循环。原因不一定是 rpm 没装,更常见的是最小化安装系统把 rpm 的路径排除在 PATH 之外,或者当前 bash 环境变量被改过。解决:先 which rpm 或 find / -name rpm -type f 2>/dev/null 找出它的实际位置,如果存在就 /usr/bin/rpm 用完整路径调用;如果你的系统自带 dnf,直接 dnf install -y rpm 补上;如果真的是精简系统没有 rpm 也没有 dnf,就不要死磕 rpm 路线了,改用第 2 章说的二进制 tarball 方案——解压 docker 二进制到 /usr/local/bin,写 systemd unit,绕开包管理器一切都好说。我有一台 CentOS 7 的最小化机器就是这样救回来的,死磕 rpm 一下午,换二进制十分钟解决。

5.3 permission denied while trying to connect to the docker daemon socket

现象:docker ps 报 permission denied while trying to connect to the docker api,但 sudo docker ps 又能正常列出容器。原因是当前用户不在 docker 用户组里,Docker 的 socket 文件 /var/run/docker.sock 属主是 root:docker,权限是 rw-rw----,普通用户没有写权限。解决:sudo usermod -aG docker $USER 把当前用户加入 docker 组,然后重新登录会话,或者执行 newgrp docker 让组权限在当前会话里立即生效。如果执行完 docker ps 还报错,先注销再重登,因为用户组信息在登录时加载,newgrp 的方式不是所有终端都可靠。临时验证可以 sudo docker ps,但这不是长期方案,每敲一条命令都要 sudo,生产环境里会很快让人崩溃。

5.4 Job for docker.service failed:SELinux 和 cgroup 版本联手演的一场戏

现象:systemctl start docker 直接失败,journalctl -xe 里既能看到 SELinux 的 denial,也能看到 cgroup 版本不匹配的提示。最离谱的一次,我同时看到两条错误以为是两个问题,其实都是环境配置导致的。SELinux 这块,RHEL/CentOS 7 默认 enforcing,Docker 要写 /var/lib/docker 目录,被 SELinux 拦下来,解决方法是 setenforce 0 临时关闭验证,确认是它的问题后在 /etc/selinux/config 里改成 permissive;cgroup 这块,内核开了 cgroup v2 而 containerd 默认跑 v1 的情况多见于 CentOS 8 和较新的内核,解决方法是检查 GRUB 启动参数,如果是统一 cgroup 层级导致 docker 无法初始化,可以在内核启动参数里加 systemd.unified_cgroup_hierarchy=0 并重启。这两类问题不解决,重装多少次都没用,因为它们发生在系统层而不是 Docker 层。

5.5 docker images 里一排<none>:save 前忘了打 tag 的代价

现象:load 完 tar,docker images 输出里 REPOSITORY 和 TAG 全是<none>,容器根本没法用正常名字启动,只能 docker run 后面跟一长串 IMAGE ID。原因是 save 的时候,源镜像本身就没有 tag,或者 save 时用的是镜像 ID 而不是仓库名:标签。解决:save 之前先检查 docker images,确认要导出的镜像有明确的 REPOSITORY 和 TAG,没有就先 docker tag,比如 docker tag a4fdf1b8e2d3 myapp:1.0.0 再 save;如果已经 load 进来了,用 docker tagmyapp:1.0.0 原地补一个 tag,镜像不用重新传输。这个坑在“复制自他人 tar 包”的场景里最常见——对方 save 的时候裸用镜像 ID,整个包传过来都是无主镜像,你只能自己手动命名。

6. 上线前多做两步:把离线包做成工具箱,再把数据目录迁走

前面几章解决了“能不能跑起来”,这章说说怎么让这套东西在长期使用中不折磨你。内网机器的特点是没人天天管,无人值守场景下,安装过程能脚本化就脚本化。

6.1 一个 40 行的离线安装脚本骨架

把整个流程写成脚本,下次换一台机器,一条命令跑完:

#!/bin/bash # 离线安装 docker:rpm 目录 + 镜像目录作为输入 set -e RPM_DIR=/opt/docker-rpm IMG_DIR=/opt/docker-images # 1. 生成本地源 createrepo "$RPM_DIR" # 2. 写入 repo 文件 cat > /etc/yum.repos.d/docker-local.repo <<EOF [docker-local] name=Docker Local Repository baseurl=file://$RPM_DIR enabled=1 gpgcheck=0 EOF # 3. 安装 docker 核心组件 yum clean all yum makecache yum install -y docker-ce docker-ce-cli containerd.io # 4. 启动并设置自启 systemctl enable docker systemctl start docker # 5. 导入镜像 for f in "$IMG_DIR"/*.tar; do docker load -i "$f" done echo "Docker installed and images loaded."

脚本里的 set -e 让任何一步出错就停止,避免装一半还在继续跑;createrepo 每次执行会更新索引,保证目录里新增的 rpm 也能被识别;repo 文件用 heredoc 写入,避免手动编辑。这个脚本没有处理 SELinux,也没有迁移数据目录,它们是“环境定制项”,我不建议写死在通用脚本里,每台机器情况不同。

6.2 迁移数据目录与最后的连通性验证

Docker 默认把所有数据放在 /var/lib/docker,系统盘小的时候很快会被镜像撑满。我一般提前写好 daemon.json:

{ "data-root": "/data/docker", "storage-driver": "overlay2" }

放到 /etc/docker/daemon.json 后重启 docker,docker info 输出里能看到 Docker Root Dir 已经变成新路径。这个动作必须在导入大量镜像之前做,否则等于把已经落盘的数据再复制一遍,纯浪费时间。

最后做一遍完整验证:docker ps 确认容器在跑,curl 宿主机映射端口确认业务端口通,然后重启一次机器确认 docker 和容器都能自启。离线环境里最常见的“装完没问题、重启全完蛋”,就是少了这个重启验证。我做过两年内网部署,最大的感受是:离线装 Docker 从来不是技术难题,而是流程问题——文件先认清、依赖先建源、镜像先打好 tag,后面一路顺畅,省下的时间够你喝好几杯茶。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询