
内网环境里折腾容器运行时最让人头疼的往往不是 Docker 本身而是装不上这三个字。我见过太多人在一台完全断网的机器上习惯性地敲下yum install docker-ce然后盯着超时提示发呆。Linux 离线安装 Docker 这件事说难不难说简单也踩坑无数——依赖包版本对不上、systemd 单元文件缺失、镜像加速配置无处可写、装完了docker info报一堆警告。这篇内容就是把我这些年在一堆无外网机器上反复装 Docker 的经验摊开讲清楚从离线包的准备逻辑、依赖链的梳理、安装顺序的取舍到装完之后怎么验证、怎么排错、怎么让这套流程可复现。适合手头有断网服务器、需要落地容器环境的朋友也适合那些能联网但不想让服务器联网的场景。看完你至少能做到拿到一台干净的离线 Linux心里有谱地把它变成一台能跑容器的机器。1. 离线安装这件事先想清楚包从哪来很多人一上来就找docker 离线安装包下载结果下回来一个几十兆的 tar.gz解压一看是静态二进制能用但和包管理器装出来的东西完全不是一回事。离线安装 Docker 的第一决策点其实是你打算用哪种安装形态这直接决定了后面所有步骤的走向。1.1 三种离线形态的取舍逻辑Docker 在 Linux 上的落地方式大致分三类各自的离线可行性和维护成本差别很大。形态来源离线可行性适用场景主要代价发行版仓库包deb/rpm官方或镜像站仓库需提前下载全套依赖生产环境、需 systemd 集成依赖链梳理麻烦静态二进制包官方提供的 tar 包最高单文件即可快速验证、特殊发行版无包管理、升级靠手工源码编译源码仓库低依赖复杂特殊定制需求耗时且易出错我个人的取舍原则很直接生产机器走仓库包临时验证或极简环境走静态二进制。原因在于仓库包会把 systemd 的 unit 文件、默认配置、用户组、日志轮转这些都替你安排好而静态二进制只给你一个可执行文件剩下的全靠自己补。离线环境下补这些东西的成本往往比想象中高。仓库包的核心难点是依赖。以常见的 RPM 系为例docker-ce依赖containerd.io、docker-ce-cli、docker-buildx-plugin、docker-compose-plugin而containerd.io又可能依赖特定版本的libseccomp、container-selinux等。这些依赖在联网机器上由包管理器自动解决离线时就得你自己一个个凑齐。1.2 用一台联网中转机把依赖链一次性拉全最稳的做法是找一台和目的一模一样的联网机器同发行版、同大版本号、同架构用它做依赖采集。RPM 系可以用downloadonly或dnf download --resolveDEB 系用apt-get install --download-only。关键点是必须带--resolve或等价参数否则只下主包不下依赖到了离线机上照样卡住。# RPM 系把 docker-ce 及其全部依赖下载到本地目录 mkdir -p /tmp/docker-offline dnf download --resolve --alldeps \ --destdir /tmp/docker-offline \ docker-ce docker-ce-cli containerd.io \ docker-buildx-plugin docker-compose-plugin这里有个容易忽略的细节--alldeps会把已经安装在采集机上的依赖也一并下载虽然多下了一些但能保证离线机不缺东西。我第一次做的时候没加这个参数结果离线机上缺了一个container-selinux排查了半天才发现是采集机本来就装过、所以没被算进待下载列表。DEB 系的做法类似但要注意apt的缓存目录和架构标记# DEB 系只下载不安装包会落在 /var/cache/apt/archives apt-get install --download-only -y \ docker-ce docker-ce-cli containerd.io \ docker-buildx-plugin docker-compose-plugin采集完成后把整个目录打包带走。我习惯在打包时附一个manifest.txt记录采集机的发行版版本、内核版本、包列表到了离线机上先对一遍避免版本错配这种低级但致命的错误。1.3 版本对齐比最新更重要的是匹配离线安装最容易翻车的点不是缺包而是版本不匹配。Docker 的各个组件之间有兼容矩阵docker-ce和containerd.io的版本如果跨得太多装上了也可能起不来。我的经验是采集机和离线机的发行版小版本尽量一致Docker 组件统一用同一个发布批次。举个实际例子某次我在采集机上拉的是较新的docker-ce离线机是稍老的系统结果containerd.io依赖的libseccomp版本不够装包时直接报依赖冲突。解决办法不是硬装而是回到采集机用--nogpgcheck之外的方式指定一个和离线机系统匹配的 Docker 版本重新采集。所以采集前先确认离线机的系统版本这一步不能省。提示采集机的内核版本不必和离线机完全一致但发行版大版本和 CPU 架构必须一致。x86_64 的包拿到 ARM 机器上是绝对装不上的这个错误听起来离谱但真有人犯过。2. 把包装进去顺序、校验与那些绕不开的报错包凑齐了接下来是安装。这一步看着机械但顺序错了、校验跳过了后面会以各种奇怪的方式报复你。2.1 安装顺序不是随便排的RPM 系用rpm -ivh或yum localinstall装本地包时依赖关系决定了顺序。手工rpm -ivh一个个装遇到依赖没满足会直接失败用yum localinstall *.rpm或dnf install ./*.rpm则会让包管理器自己算顺序明显更省心。# 推荐让包管理器处理本地目录里的依赖顺序 cd /tmp/docker-offline dnf install -y ./*.rpmDEB 系同理dpkg -i遇到依赖问题会中断而apt-get install -f或直接用apt install ./*.deb能自动补齐顺序。我一般先用dpkg -i装主包再用apt-get install -f修依赖这样出错时能看清是哪个包的问题。安装前还有一步值得做校验包的完整性。离线传输过程中包损坏不是小概率事件尤其是用 U 盘或跨网络拷贝时。RPM 可以用rpm -K校验签名DEB 用dpkg-deb --info看基本信息。花一分钟校验能省掉后面半小时的为什么装上了却起不来。2.2 依赖冲突的典型面貌与处理离线安装最常见的报错有三类我按出现频率排一下。第一类是依赖缺失报错里会明确写requires xxx。这种最好办回到采集机补下对应的包即可。麻烦的是第二类——版本冲突比如系统里已有一个旧版containerd新包要求更高版本两者打架。这时候要么升级旧包要么先卸载旧的再装新的取舍看业务是否依赖旧版本。第三类是文件冲突多见于之前装过 Docker 又没卸干净的情况。报错会指出某个文件已存在且属于另一个包。处理方式是先rpm -qf 文件路径查清楚归属确认可以覆盖后再用--replacefiles强制安装或者干脆先彻底卸载旧版本。# 查某个冲突文件属于哪个包 rpm -qf /usr/bin/docker # 彻底卸载旧版本注意会停掉容器 dnf remove -y docker-ce docker-ce-cli containerd.io注意卸载前务必确认机器上没有正在运行的重要容器dnf remove不会帮你保留容器数据数据卷目录需要自己备份。2.3 静态二进制这条捷径的正确用法如果离线环境极其简陋连包管理器都不好用静态二进制是条出路。官方提供的 tar 包解压后是一堆可执行文件把它们放到/usr/bin或/usr/local/bin再手工补一个 systemd unit 文件就能跑。# 解压静态包 tar -xzf docker-version.tgz # 拷贝二进制 cp docker/* /usr/local/bin/ # 手工启动 dockerd前台用于验证 dockerd 静态方式的好处是一个包搞定所有坏处是所有集成工作都得自己做systemd unit、docker用户组、/etc/docker/daemon.json、日志配置一个都不能少。我一般只在临时排障或极简环境用这条路长期运行的机器还是老老实实走仓库包。3. 装完不等于能用验证链路要一层层过包安装成功、systemctl start docker也返回了成功很多人到这就以为完事了。实际上离线环境里装上了但用不了的比例相当高必须逐层验证。3.1 从进程到守护进程的四层检查我习惯按进程 → 服务状态 → 客户端连通 → 实际运行容器四层来查任何一层断了都能快速定位。第一层看进程ps aux | grep dockerd有没有那个守护进程。第二层看服务systemctl status docker是否 active有没有反复重启。第三层看客户端docker version能否同时打印出 Client 和 Server 两段信息——只出 Client 不出 Server说明客户端连不上守护进程。第四层才是docker run hello-world真正跑一个容器。# 四层验证一条龙 ps aux | grep -v grep | grep dockerd systemctl status docker --no-pager docker version docker run --rm hello-world离线环境下docker run hello-world大概率会失败因为本地没有镜像、又拉不下来。这时候别慌这恰恰说明前面的安装是成功的问题出在镜像获取上属于下一节要解决的事。3.2 那些看起来是错误的警告docker info在离线机器上经常吐出一堆警告比如WARNING: No swap limit support、WARNING: bridge-nf-call-iptables is disabled。这些不一定是安装失败很多是内核参数或 cgroup 配置的问题。No swap limit support通常是内核没开 memory cgroup 的 swap 统计需要在启动参数里加cgroup_enablememory swapaccount1改完重启生效。bridge-nf-call-iptables相关的警告则是br_netfilter模块没加载或对应 sysctl 没开按提示设置即可。# 加载网桥过滤模块并持久化 modprobe br_netfilter echo br_netfilter /etc/modules-load.d/br_netfilter.conf # 开启相关 sysctl cat /etc/sysctl.d/99-docker.conf EOF net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOF sysctl --system这些警告不影响 Docker 启动但会影响容器网络和资源限制的正常工作生产环境建议逐条消掉。3.3 离线环境下的镜像从哪来这是离线安装 Docker 之后绕不过去的一环守护进程跑起来了但没有镜像可用。解决办法是在联网机器上docker save拷到离线机docker load。# 联网机把镜像导出成 tar docker pull nginx:1.25 docker save -o nginx-1.25.tar nginx:1.25 # 离线机导入镜像 docker load -i nginx-1.25.tar docker images导出时有个技巧多个镜像可以一起save到一个 tar 里减少传输次数。另外docker save保留的是镜像的完整层体积可能不小传输前用gzip压一下能省不少空间。我一般会在联网机上维护一个常用镜像清单定期批量导出离线机按需导入形成一套可循环的镜像供给流程。4. 让这套流程可复现脚本化与踩坑复盘单次装成功不算本事能在十台机器上稳定复现才算。离线安装尤其需要脚本化因为手工步骤越多出错概率越高。4.1 一个可复用的离线安装脚本骨架我把整个流程拆成校验环境 → 安装包 → 配置 → 启动 → 验证五段写成脚本。核心不是脚本多复杂而是每段都有明确的成功判据任何一段失败就停下并打印原因。#!/usr/bin/env bash set -euo pipefail PKG_DIR/tmp/docker-offline LOG/var/log/docker-offline-install.log log() { echo [$(date %F %T)] $* | tee -a $LOG; } # 1. 环境校验 log 检查系统架构与发行版 uname -m cat /etc/os-release | head -3 # 2. 安装本地包 log 开始安装本地包 if command -v dnf /dev/null 21; then dnf install -y $PKG_DIR/*.rpm elif command -v apt-get /dev/null 21; then dpkg -i $PKG_DIR/*.deb || apt-get install -f -y fi # 3. 启动并验证 log 启动 docker 服务 systemctl enable --now docker systemctl is-active docker log 验证客户端与守护进程连通 docker version log 安装流程结束脚本里set -euo pipefail这行很关键任何命令失败立即退出避免中间某步失败了但脚本继续跑造成的假成功。日志同时输出到屏幕和文件事后排查有据可查。4.2 我踩过的三个真实坑第一个坑是SELinux 拦截。某次在开启了 enforcing 模式的机器上装完容器死活起不来日志里全是 permission denied。根因是 SELinux 策略不允许容器访问某些路径。临时方案是setenforce 0验证确认后要么调整策略要么在daemon.json里配置合适的 SELinux 选项。离线环境下改策略比较麻烦我一般选择在安装前就把 SELinux 设为 permissive 并记录在案。第二个坑是磁盘空间。Docker 的默认数据目录在/var/lib/docker离线机器往往根分区不大装完跑几个镜像就满了。我的做法是安装前就规划好数据目录在daemon.json里用>{ data-root: /data/docker, log-driver: json-file, log-opts: { max-size: 50m, max-file: 3 } }第三个坑是时间不同步导致的证书问题。离线机器如果系统时间偏差太大导入的镜像或后续配置的私有仓库证书校验会失败。装完 Docker 顺手用timedatectl看一眼时间偏差大就手工校准这个细节很多人想不到。4.3 升级与回滚的离线策略离线环境的 Docker 升级比联网环境更需要谨慎因为没有一键回退。我的策略是升级前把旧版本的包全部保留一份同时用docker save把关键镜像导出备份。升级失败时卸载新包、装回旧包、导入镜像三步回滚。升级本身也建议小步走不要一次跨太多大版本。每次升级后跑一遍第 3 节的四层验证确认无误再继续。这套流程听起来笨但在没有外网兜底的环境里笨办法往往最可靠。提示保留旧版本包时连同当时的daemon.json和 systemd unit 文件一起归档回滚时能省去重新配置的麻烦。5. 收尾时想说的几句实在话离线安装 Docker 这件事技术门槛其实不高真正消耗人的是那些文档里不写、但每次都会遇到的细节依赖链要带--resolve、采集机和离线机版本要对齐、装完要四层验证、镜像得靠save/load搬运、SELinux 和磁盘空间要提前规划。这些点单独看都不难凑在一起就是一套需要经验沉淀的流程。我个人在实际操作中的体会是与其追求一次装对不如把流程做成装错了也能快速定位和回滚。脚本化、日志化、保留旧包这三件事做到位离线环境的 Docker 维护就从玄学变成了工程。另外采集机这个角色值得长期维护——把它当成一个固定的离线包工厂定期更新包和镜像清单后面每台新机器都能受益。最后分享一个小习惯每次装完把docker info的完整输出存一份到日志目录下次出问题时对比一下很多异常一眼就能看出来。