
简介一套面向 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 里全是时间戳异常的文件先怀疑传输损坏。# 看文件类型二进制会显示 ELFtar 归档会显示 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 archivefile 命令是成本最低的判断工具任何 Linux 发行版都有。它读文件头部的魔数比后缀名可靠得多因为很多人会手滑把保存的文件名后缀搞错。识别完这三类东西下一步就能选安装路线了。3. rpm 离线安装 Docker本地仓库 yum 是最不容易翻车的路径认清文件之后进入正题——把这组 rpm 真正装进系统里。我最推荐的不是 rpm -ivh 一个个怼而是先在本地搭一个 yum 源让 yum 去处理依赖关系。原因很简单Docker 全家桶之间有明确的依赖顺序而且 containerd 和 docker-ce 互相有版本要求人工按顺序敲指令敲到第三个包发现前面装错版本重来一遍的成本比搭源高多了。3.1 搭建本地源createrepo 生成 repodatayum 安装包时不是直接扫描目录里的 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 的方式。这里我没写任何外部软件源纯粹是内网自给。索引生成后写一个本地源配置文件。文件名可以随便起后缀必须 .repovi /etc/yum.repos.d/docker-local.repo # [docker-local] # nameDocker Local Repository # baseurlfile:///opt/docker-rpm # enabled1 # gpgcheck0baseurl 的 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-pagerenable 是设置开机自启内网机器没人每天手动敲 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.25save 命令的参数结构是 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.0load 进来后也会带着这个前缀。# 批量导入目录下所有 tar 包 for f in /opt/docker-images/*.tar; do docker load -i $f donefor 循环是常见做法把目录下所有 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.0tag 命令先给同一个镜像 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_PASSWORDYourPass \ -v /data/mysql:/var/lib/mysql \ --restartalways \ mysql:8.0参数说明-d后台运行否则终端会一直挂着输出日志--name给容器起名后续 docker logs mysql8、docker exec -it mysql8 bash 都靠这个名字定位-p 宿主机端口:容器端口做端口映射外面访问机器的 3306 才能进到容器里的 MySQL-e传环境变量MySQL 镜像靠它初始化 root 密码-v挂载数据卷把容器里的数据目录映射到宿主机 /data/mysql不然容器一删数据全没这是最常被忽略的生产习惯--restartalways设置开机自启和异常重启内网机器不会有人每次开机手动 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 failedSELinux 和 cgroup 版本联手演的一场戏现象systemctl start docker 直接失败journalctl -xe 里既能看到 SELinux 的 denial也能看到 cgroup 版本不匹配的提示。最离谱的一次我同时看到两条错误以为是两个问题其实都是环境配置导致的。SELinux 这块RHEL/CentOS 7 默认 enforcingDocker 要写 /var/lib/docker 目录被 SELinux 拦下来解决方法是 setenforce 0 临时关闭验证确认是它的问题后在 /etc/selinux/config 里改成 permissivecgroup 这块内核开了 cgroup v2 而 containerd 默认跑 v1 的情况多见于 CentOS 8 和较新的内核解决方法是检查 GRUB 启动参数如果是统一 cgroup 层级导致 docker 无法初始化可以在内核启动参数里加 systemd.unified_cgroup_hierarchy0 并重启。这两类问题不解决重装多少次都没用因为它们发生在系统层而不是 Docker 层。5.5 docker images 里一排nonesave 前忘了打 tag 的代价现象load 完 tardocker 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 # 离线安装 dockerrpm 目录 镜像目录作为输入 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] nameDocker Local Repository baseurlfile://$RPM_DIR enabled1 gpgcheck0 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 后重启 dockerdocker info 输出里能看到 Docker Root Dir 已经变成新路径。这个动作必须在导入大量镜像之前做否则等于把已经落盘的数据再复制一遍纯浪费时间。最后做一遍完整验证docker ps 确认容器在跑curl 宿主机映射端口确认业务端口通然后重启一次机器确认 docker 和容器都能自启。离线环境里最常见的“装完没问题、重启全完蛋”就是少了这个重启验证。我做过两年内网部署最大的感受是离线装 Docker 从来不是技术难题而是流程问题——文件先认清、依赖先建源、镜像先打好 tag后面一路顺畅省下的时间够你喝好几杯茶。希望帮到你。本文还有配套的精品资源点击获取