Docker离线安装完整指南:从rpm打包到私有仓库搭建
1. 离线安装前先想明白这三件事不少同学第一次接触“离线安装docker”这个概念第一反应是找一个 docker 的安装包丢到服务器上解压就能用。如果你也这么想建议先停一下——离线安装这件事真正麻烦的不是“怎么拷文件”而是“怎么把一套完整可用的运行时环境从零开始在一台没有外网的机器上搭起来”。这里说的“完整可用”至少包含三层含义一是 docker 引擎本身能启动二是 docker-compose 能配合工作三是容器跑起来之后镜像从哪儿来。第三点往往是被忽略的后面我会单独拿一小节来聊。开始动手之前请确认你的目标机器是什么操作系统和 CPU 架构这是所有离线方案的地基。最简单的确认命令cat /etc/os-release uname -m拿最典型的 CentOS 7 来说我需要知道它的内核是不是 3.10CentOS 7 默认内核因为内核版本直接决定后面存储驱动怎么选也决定容器运行是否有隐患同时确认是 x86_64 还是 arm64 架构因为不同架构的 rpm 包、二进制包完全不能混用。还有一件事要先掂量清楚你手头是否有一台可以上网、且和目标机器操作系统版本完全一致的机器。这是做离线包的最佳“加工车间”。如果没有就得走纯二进制包路线我把两条路都列出来你按条件选。另外提醒一下离线安装不是装完就结束的事。Docker 的版本更新很快离线环境尤其要提前规划好版本。我个人的习惯是尽量选稳定的主版本里最新的小版本比如 docker-ce 24.x 或 26.x而不是随便抓一个版本就装。版本太老后面拉新镜像、跑新 compose 文件比如用到 depends_on 新语法、profiles 功能很可能遇到兼容问题那时候再升级在内网环境下会非常痛苦。2. 最顺手的路用 yumdownloader 在联网机器打包 rpm如果你的条件是“有一台同系统的联网机器”那这条路是最省心的。核心思路是在联网机器上用包管理器把 docker 及其所有依赖包下载到本地再把它们打包传到内网机器上离线安装。2.1 在联网机器上准备打包环境我先准备一台 CentOS 7.9 的联网机器然后安装 yum-utils 工具集里面包含了接下来要用的 yumdownloader 命令yum install -y yum-utils然后把 Docker 官方仓库配置好yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo这里解释一下为什么要配官方仓库而不是直接用系统自带的源CentOS 自带的源里虽然也有 docker老版本的 docker 1.13但版本过于陈旧而且 docker-compose 插件的支持方式完全不同。官方仓库能拿到当前最新的 docker-ce、docker-ce-cli、containerd.io 等组件并且会处理好版本配套关系。2.2 下载指定版本及全部依赖如果你对版本没有特殊要求直接下载最新稳定版就可以。但我建议先看下仓库里有哪几个大版本可选yum list docker-ce --showduplicates | sort -r确定版本号之后用下面的命令下载 docker 引擎和命令行工具。注意我用了--resolve参数它的作用是分析依赖关系并把所有依赖包一起下载下来。这是离线安装中最容易遗漏的环节——很多人只下载了 docker-ce 一个包拷到内网一装才发现缺一堆依赖比如 container-selinux、libcgroup、iptables 等。cd /root/docker-offline yumdownloader --resolve docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin如果你需要指定版本下载时要带上完整的版本号yumdownloader --resolve docker-ce-24.0.9-1.el7 docker-ce-cli-24.0.9-1.el7 containerd.io docker-buildx-plugin docker-compose-plugin关于docker-buildx-plugin和docker-compose-plugin这两个是 docker 官方推荐的新一代构建与编排插件。docker-compose-plugin 对应的是docker compose子命令带空格而老的独立版 docker-compose 以后面章节提到的二进制方式安装。如果你只需要docker compose插件那这里下载它就够了不需要再额外下载独立的 docker-compose。下载完成后确认一下目录里的文件ls -lh /root/docker-offline你会看到类似这样的输出版本号可能不同-rw-r--r-- 1 root root 25M 6月 18 10:00 containerd.io-1.6.28-3.1.el7.x86_64.rpm -rw-r--r-- 1 root root 21M 6月 18 10:00 docker-buildx-plugin-0.13.1-1.el7.x86_64.rpm -rw-r--r-- 1 root root 40M 6月 18 10:00 docker-ce-24.0.7-1.el7.x86_64.rpm -rw-r--r-- 1 root root 14M 6月 18 10:00 docker-ce-cli-24.0.7-1.el7.x86_64.rpm -rw-r--r-- 1 root root 14M 6月 18 10:00 docker-compose-plugin-2.24.2-1.el7.x86_64.rpm2.3 把 rpm 拷进内网并离线安装打包传到内网机器上之后安装不要用rpm -ivh *.rpm因为 rpm 命令不处理依赖顺序遇到依赖问题只会报错退出而不会自动按顺序装。建议用yum localinstallcd /root/docker-offline yum localinstall -y *.rpmyum localinstall会读取当前目录下的 rpm 包自动分析并解决包与包之间的依赖关系。对于离线环境来说只要目录里的包是全的它就一定能装成功。这里有个经验性建议装完后先不要急着启动 docker先查看一下生成的配置目录和文件ls -l /etc/docker/ cat /etc/docker/daemon.json如果你的系统里没有 /etc/docker/daemon.json这是正常的。启动 docker 之前我建议先手动创建这个配置文件把基础参数定下来。具体配置我在第 3 章和第 4 章里结合场景详细说明。所有 rpm 装完、配置写好后再执行systemctl daemon-reload systemctl enable --now docker systemctl status docker到这里docker 引擎就起来了。如果systemctl status docker显示 active (running)可以顺手跑一条验证命令docker info --format {{.ServerVersion}} docker compose version后面这条能顺便确认 docker compose 插件是否装好。2.4 这个方案里最容易踩的三个坑坑一联网机和内网机的操作系统小版本不一致。比如联网机是 CentOS 7.9内网机是 CentOS 7.6依赖包的兼容性通常没有问题但是内核版本差异会影响容器运行时的行为比如 overlay 驱动的兼容性判断。建议把内网机的内核和系统补丁级别尽量向联网机靠拢或者干脆在做离线包的内网机上继续后续所有操作。坑二下载时没有加--resolve或--downloadonly导致缺依赖。如果你已经因为缺依赖装到一半失败了也不用慌回联网机上执行提示里的 yum download 补充下载对应包重新拷过来再yum localinstall即可。坑三docker-ce 的 repo 在 CentOS 7 上默认启用的是 stable 源但 SELinux 策略包container-selinux不在官方源里。容器启动时报 SELinux 拒绝的错很容易误判成 Docker 问题。解决办法是提前把 container-selinux 也下载过来yumdownloader --resolve container-selinux或者更省事的方式如果内网机器尤其是生产环境对 SELinux 依赖不严格可以在离线机器上临时把 SELinux 设为 permissive 模式先把 Docker 跑通后续再根据安全策略进行调整。但这是取舍问题如果你所在的安全团队要求 SELinux 强制开启那就必须把 container-selinux 一并装好。3. 另一条路官方静态二进制包直接部署没有同系统联网机器怎么办比如只有一台 Windows 电脑或者联网机器是 Ubuntu但内网机器是 CentOS 7——这时候 rpm 路线就断了因为跨发行版下载的 rpm 包不能相互安装。此时我会选择 Docker 官方发布的静态二进制包static binary这条路不依赖任何包管理器把压缩包解开就能用。3.1 下载 docker 静态包并解压访问 Docker 官方 release 页面下载docker-版本.tgz即可。比如下载 24.0.9 版本wget https://download.docker.com/linux/static/stable/x86_64/docker-24.0.9.tgz里面有这些组件containerd/ containerd-shim-runc-v2 ctr docker dockerd docker-init docker-proxy runc注意这个静态包里不包含 docker compose 插件需要单独下载 docker-compose独立二进制我后面会讲。解压并放到 /usr/bin 目录tar -xzf docker-24.0.9.tgz cp docker/* /usr/bin/把这些二进制放在 /usr/bin 里可以保证任何用户都能直接执行 docker 命令也方便 systemd 服务调用。如果你想保持目录整洁也可以放在 /usr/local/bin但要注意 PATH 环境变量里是否包含它。3.2 手工编写 systemd 服务单元静态包方式不会自动注册 systemd 服务需要自己写。创建/etc/systemd/system/docker.service[Unit] DescriptionDocker Application Container Engine Documentationhttps://docs.docker.com Afternetwork-online.target firewalld.service containerd.service Wantsnetwork-online.target [Service] Typenotify ExecStart/usr/bin/dockerd ExecReload/bin/kill -s HUP $MAINPID LimitNOFILEinfinity LimitNPROCinfinity LimitCOREinfinity TimeoutStartSec0 Delegateyes KillModeprocess Restarton-failure StartLimitBurst3 StartLimitInterval60s [Install] WantedBymulti-user.target这个文件里最值得注意的有两处一是Typenotify这是 dockerd 通过 sd_notify 协议通知 systemd “我已经准备好”的方式如果 Type 写错比如写成 simple会导致 systemctl start docker 之后长时间卡住不动二是KillModeprocess它保证在重启 docker 服务时不会把已经运行的容器进程一并杀掉这一点在生产环境尤其重要。再创建/etc/systemd/system/docker.socket[Unit] DescriptionDocker Socket for the API [Socket] ListenStream/var/run/docker.sock SocketMode0660 SocketUserroot SocketGroupdocker [Install] WantedBysockets.target创建好这两个文件后重载并启用服务systemctl daemon-reload systemctl enable --now docker.socket docker.service3.3 没有 docker 用户组先建一个注意到上面 socket 文件里写了SocketGroupdocker如果系统里还没有 docker 用户组docker.socket 会启动失败。需要先执行groupadd docker把需要免 sudo 使用 docker 的普通用户加入这个组usermod -aG docker yourusername这一步很多人容易漏。漏掉的结果是docker 服务虽然能启动但非 root 用户执行 docker 命令会报Got permission denied while trying to connect to the Docker daemon socket排查时容易绕弯路。3.4 初始化 daemon.json避免根目录写满不管用哪种方式安装我都建议启动 docker 之前先创建好/etc/docker/daemon.json。一个适合离线服务器场景的初始配置如下{ data-root: /data/docker, exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 }, storage-driver: overlay2, iptables: true, ip-masq: true, registry-mirrors: [] }说一下每个配置项的意图>docker info --format {{.DockerRootDir}}输出应该是 /data/docker 而不是默认的 /var/lib/docker那就对了。4. docker-compose 的离线安装别搞混两个版本接下来是 docker-compose 的处理。这里有一个非常常见的混淆点docker-compose带横线独立的二进制工具和docker compose子命令Docker CLI 的插件是两种不同的东西。离线安装时要先明确自己需要哪种不要装混了。4.1 新一代docker compose插件如何离线部署如果你的 docker 版本是 20.10 以上我强烈推荐直接用 docker compose 插件。它的最大好处是随 docker 一起管理不需要额外维护一个独立二进制而且对 compose 文件的语法支持更全面。rpm 方式安装时上面的yumdownloader命令里只要包含docker-compose-plugin包装完就有了。二进制方式的话需要单独下载 compose 插件二进制。注意它必须放在指定目录里才会被 docker 识别mkdir -p /usr/libexec/docker/cli-plugins cp docker-compose-linux-x86_64 /usr/libexec/docker/cli-plugins/docker-compose chmod x /usr/libexec/docker/cli-plugins/docker-compose验证docker compose version能输出类似Docker Compose version v2.24.2就说明配置正确。这里补充一下插件查找目录的细节docker cli 会依次查找/usr/local/lib/docker/cli-plugins、/usr/libexec/docker/cli-plugins、~/.docker/cli-plugins等路径。如果你把插件放到了用户目录~/.docker/cli-plugins那只有该用户执行 docker 命令时才能用切换到其他用户或 root 就找不到了。部署到/usr/libexec/docker/cli-plugins是全局生效、最稳妥的路径。4.2 老的独立版docker-compose二进制安装法有些老项目或者旧脚本里还会调用docker-compose命令带横线这种情况就得安装独立版。下载地址是 GitHub Releases 页面的 docker-compose 仓库离线环境下需要先从能上网的机器下载好再拷贝进去wget https://github.com/docker/compose/releases/download/v2.24.2/docker-compose-linux-x86_64 cp docker-compose-linux-x86_64 /usr/local/bin/docker-compose chmod x /usr/local/bin/docker-compose注意GitHub 国内网络环境下载可能时断时续建议用能稳定上网的机器下载或者找内网已有资源同步。这类工程文件的校验也很重要sha256sum docker-compose-linux-x86_64把输出和官方发布页提供的 checksum 对比一致后再拷入内网防止文件损坏或被人替换。验证安装docker-compose version4.3 两个版本能不能共存可以共存我实际也这么干过。docker compose插件不影响独立的docker-compose二进制。但有一个容易踩坑的地方当你执行docker-compose带横线时它不会读取/etc/docker/daemon.json里的某些配置比如 registry 地址它直接调用 docker CLI 的能力。如果你的离线环境配置了私有仓库地址独立版 docker-compose 可能提示认证失败这时候优先检查 docker CLI 的 auth 配置而不是怀疑 compose 文件写错了。还有一个小建议写脚本或 CI/CD 配置时统一用docker compose命令空格避免两种风格混用导致维护混淆。我在一些客户环境里见过 docker-compose.yml 里同时被两种命令调用后面排查问题时非常费劲。5. 启动 docker 后镜像怎么办——离线环境最容易被忽略的最后一公里安装完 docker 和 compose你可能会觉得大功告成了。但离线环境的第二个硬骨头马上就会出现内网机器上一条docker pull命令会卡住不动因为根本连不上互联网。5.1 用 docker save 和 docker load 搬运镜像最简单的镜像是通过 tar 文件搬运。在一台有网机器上拉取需要的镜像然后打包docker pull mysql:8.0 docker save mysql:8.0 | gzip mysql-8.0.tar.gz拷贝到内网机器后导入docker load -i mysql-8.0.tar.gzdocker save保留镜像的全部层和元数据docker load则会把层还原出来。要注意一点save 时如果镜像带 tagload 之后 tag 也会完整保留但假如源镜像没有打 tag显示为noneload 之后要手动打 tag 才能正常使用docker tag image-id mysql:8.0这一块我想特别强调如果你要搬运多个镜像不要一个一个 save可以把多个镜像写在一个命令里一次性导出成一个 tar 文件docker save nginx:1.25 mysql:8.0 redis:7.0 | gzip app-images.tar.gz内网机器上一次 load 全部导入docker load -i app-images.tar.gz这种方式比循环 save/load 单个镜像要高效得多而且整体性更好。5.2 在内网搭建 registry让所有机器共享镜像如果要部署的服务器不止一台每次都手动 copy tar 包会非常低效。我的建议是在内网挑一台机器搭一个私有 registry其他机器直接从这台 registry 拉镜像体验和公网 Docker Hub 几乎一样。在内网这台机器上导入 registry 镜像并启动docker pull registry:2.8.3 docker run -d \ --name registry \ --restartalways \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ registry:2.8.3然后其他机器要改一下 docker 客户端配置因为默认走 HTTPS而内网 registry 通常只提供 HTTP在/etc/docker/daemon.json中加入{ insecure-registries: [192.168.1.100:5000] }配置完成后重启 dockersystemctl daemon-reload systemctl restart docker推拉镜像的方式和 Docker Hub 一样docker tag mysql:8.0 192.168.1.100:5000/mysql:8.0 docker push 192.168.1.100:5000/mysql:8.0 docker pull 192.168.1.100:5000/mysql:8.0如果你出于安全考虑不想走 HTTP可以在内网给 registry 配置自签证书。具体做法是用 openssl 生成自签证书挂载到 registry 容器的/certs目录并设置环境变量 REGISTRY_HTTP_TLS_CERTIFICATE 和 REGISTRY_HTTP_TLS_KEY。这样每台客户端的 docker 还要信任这个 CA 证书操作比 insecure-registry 要繁琐但更符合企业安全要求。我在实际项目里推荐的做法是小规模几台机器直接 tar 包搬运规模超过 5 台直接上 registry。5.3 离线环境使用 compose 文件的注意事项离线环境下用 docker compose 编排时compose 文件里所有镜像都必须存在于本地或者内网 registry 中。如果你直接拿公网教程里的 compose 文件跑大概率会因为镜像拉不下来而报错。解决办法是先在联网机器上把 compose 文件里需要的所有镜像一次性 pull 下来再 save 导出docker compose config --images images.txt while read img; do docker pull $img; done images.txt docker save $(cat images.txt) | gzip composed-images.tar.gz这会把你整个 compose 栈涉及的镜像全部拉下来、打包。到了内网机器上 load 完成后再docker compose up -d就能正常拉起服务。这个技巧还有个额外好处docker compose config --images这个命令会解析整个 compose 文件里所有 service 的镜像引用包括 build 块里用到的镜像依赖比手动一个个敲要准确得多。6. 常见故障离线装完之后的几个高频问题排查装完之后我见过太多人卡在启动和运行阶段这里把最常碰到的几个问题集中说一下按出现频率排序。6.1Cannot connect to the Docker daemon这条报错往往不是因为 docker 没安装而是服务没起来或 socket 文件异常。排查思路按顺序来systemctl status docker journalctl -u docker --no-pager -n 50 ls -l /var/run/docker.sock如果日志显示failed to start daemon: Error initializing network controller: error creating default bridge network: iptables failed通常是机器的 iptables 内核模块没加载或规则冲突。离线环境里常见原因是系统自带了 firewalld 但又被停用docker 起网桥时操作 iptables 被拒绝。可以先检查systemctl status firewalld iptables -L -n临时处理可重载 iptables 相关内核模块modprobe br_netfilter sysctl -w net.bridge.bridge-nf-call-iptables1然后重启 docker。6.2 网络地址转换和端口映射不通离线环境可能使用了比较严格的主机防火墙。容器启动成功但宿主机外部访问不到容器端口时先确认防火墙是否放行firewall-cmd --list-ports firewall-cmd --zonepublic --add-port8080/tcp --permanent firewall-cmd --reload如果用 iptables 服务则需要手动添加规则。很多人在这一步反复重启容器、反复检查 compose 文件实际上就是防火墙规则挡了。建议启动容器前先把 443/80 或业务端口在宿主机防火墙里放行再通过curl http://127.0.0.1:映射端口验证。6.3 compose 文件语法导致的启动失败离线环境经常用 Docker Compose 部署多服务应用如果docker compose up -d报错比如services.db.environment must be a mapping这种大多是 YAML 缩进问题或版本语法问题。建议先把文件在本地用docker compose config验证它能快速定位语法错误不需要真正启容器。还有一点compose 文件里如果指定了build:块docker compose 在老版本里会试图构建镜像但离线环境往往没有构建所需的镜像层和网络会导致卡住。如果你只是部署现成镜像确保 compose 文件里没有 build 指令或者提前把需要的镜像 load 好。6.4 存储驱动不被内核支持CentOS 7 上偶尔看到这样的报错failed to mount overlay: no such file or directory意思是内核不支持 overlay2 文件系统常见于默认内核版本较老的 CentOS 7.x。临时解法是改用 vfs 驱动不推荐性能和磁盘占用都很难接受或者在启动脚本层面分析长期解法是升级内核或用官方支持的新系统比如 Rocky Linux 9、Ubuntu 22.04重新部署。如果必须留在 CentOS 7 且内核版本支持 overlay但某些模块未加载可以先执行modprobe overlay再重启 docker。如果还是报错就查看是否要增加内核启动参数这属于系统层适配不是 docker 本身的问题但离线环境下确实会把你绊住。7. 最后的经验总结离线安装看起来是“能上网/不能上网”的问题其实是“交付物设计”的问题我做了这么多次离线环境部署最大的体感是离线安装的核心不是命令而是“在有限条件下把交付物最小化、最完整化”。rpm 方式也好、静态二进制方式也好都不是什么高深技术真正的难点在于你把什么算作“安装包”。如果你只是拷一个 docker 的 rpm内网环境大概率装不上因为缺依赖如果你做了一个包含所有依赖的离线仓库目录后续再有新的机器要从零安装你的交付物就非常可靠。我个人的习惯是一个完整的内网交付物包含四样东西docker 引擎相关 rpm 包目录或静态二进制 systemd 文件docker-compose 独立二进制 / 插件二进制一份 daemon.json 样例一个镜像 tar 包目录或者一台内网 registry 地址这四样东西放在一起才是真正意义上的“离线安装包”。只放一个安装文件那不叫离线安装包那叫给你自己挖坑。另外多说一句离线环境下不要轻易尝鲜最新版本。Docker 软件本身更新节奏快新版本可能对内核、systemd、iptables 行为有变化。我在长期维护的离线系统上倾向于固定一个大版本比如 24.0 系列或 26.1 系列然后把小版本也锁死。容器运行时不追求“最新”追求“经得起时间检验的稳定”。在整理镜像依赖时也可以用docker image inspect看镜像的配置信息确认它依赖的架构、运行用户、暴露的端口是否符合你的内网环境。这些检查在公网环境下可以不管但离线环境一旦部署下去改一次配置都要来回拷文件所以“上机前多花十分钟上线后少折腾一整天”这句话放在离线部署场景里再合适不过了。