
最近接了个内网部署的活目标机器只能进机房内网出不了公网客户还指定必须上Docker。放在以前一台能联网的Ubuntu上跑一句curl脚本装完就走这次没这个条件只能老老实实把.deb包准备好带进内网。折腾了两天踩了四五个坑最后在Ubuntu 20.04上成功离线装好了Docker 24.0.7顺手把卸载重装流程也完整验证了一遍。这篇文章不聊虚的就把“离线部署Docker”的完整套路写出来联网机器上怎么把依赖拉全内网机器上怎么按顺序dpkg -i以及不想用了之后怎么彻底清理。需要说明的是这篇针对的是Ubuntu 20.04/22.04这类基于apt的Debian系系统安装方式是官方docker-ce的.deb包不是Ubuntu自带仓库里的docker.io旧版本。内容覆盖联网下载包、离线安装、常见报错排查和卸载方法适合内网交付、项目现场部署、无外网/受限网络环境下的运维场景。1. 开始之前这种离线安装方式到底适不适合你1.1 先确认三个硬条件离线安装Docker的第一个前提是你手里必须有一台能联网的Ubuntu机器而且系统和目标内网机是同一个大版本。比如都是20.04focal或者都是22.04jammy。大版本一致的前提下小版本有点差距问题不大比如20.04.3攒的包放到20.04.6上一般能装但20.04的包拿到22.04上去装基本必出依赖问题。第二个条件是架构。绝大多数服务器是x86_64用uname -m看一下输出x86_64就是amd64如果是aarch64后面下载包和配置源的时候都要换成arm64。这一点很容易被忽略我见过有人拿着amd64的deb包往ARM开发板上砸结果自然是“package architecture does not match”。第三个条件是systemd和内核。Docker官方要求Linux内核不低于3.10实际20.04/22.04的内核都是5.x完全没问题。systemd这一项用一条命令确认ps -p 1 -o comm输出是systemd就没问题。现在Ubuntu服务器默认都是systemd但极个别定制过的环境可能是init或SysV那种情况Docker的service文件虽然也能放但systemctl控制不了建议先和系统管理员确认再用。1.2 明确要装哪些组件Docker不是单一一个包至少包含这几部分docker-ceEngine主程序、docker-ce-cli命令行工具、containerd.io容器运行时。如果还要用docker compose和buildx那就再带上docker-compose-plugin和docker-buildx-plugin。组件作用是否必装docker-ceDocker守护进程和核心功能必装docker-ce-clidocker命令行工具与daemon通信必装containerd.io底层容器运行时docker依赖它管理容器必装docker-compose-plugin提供docker compose子命令建议装docker-buildx-plugin提供docker buildx多架构构建能力建议装我的建议是五个组件一次性装齐。虽然离线环境下可能用不上外网拉镜像但buildx和compose在内网交付时经常被用到比如从私有仓库拉docker-compose编排文件。分开攒两次包很麻烦一次性拉全最省事。2. 攒包阶段如何把Docker及依赖的deb全拉下来2.1 在联网Ubuntu上添加Docker官方源先在联网机器上把Docker官方源加好。这里以Ubuntu 20.04为例22.04把下面的focal替换成jammy即可sudo apt update sudo apt install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod ar /etc/apt/keyrings/docker.gpg echo deb [archamd64 signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu focal stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null sudo apt update这一步的目的是让联网机器能识别docker-ce、docker-ce-cli、containerd.io这些包名。如果你下载Docker官方源慢可以在有外网的前提下把源换成国内镜像站这个属于常规操作不影响后续离线安装的可行性。换源的时候记得把gpg key的路径带上否则apt update会提示签名校验失败。2.2 用apt download把全套deb拉下来很多新手第一次攒包时喜欢直接去download.docker.com网页上手动点几个deb下载结果到了内网机器一装报依赖缺失。正确的做法是让apt帮我们计算依赖并下载不需要自己一个个点链接。先清空apt缓存确保下载目录干净sudo apt clean mkdir -p ~/docker-offline cd ~/docker-offline然后只下载不安装apt-get install --download-only -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin执行完之后所有下载的deb包都会出现在/var/cache/apt/archives/目录里。把它们统一拷贝到自己的工作目录cp /var/cache/apt/archives/*.deb ~/docker-offline/ ls -lh ~/docker-offline/你会看到除了docker主包还有一堆依赖库比如libseccomp2、iptables、libsystemd0等。这些就是离线安装能不能成的关键。实际攒包过程中不同Ubuntu版本拉下来的依赖清单会有差异属正常现象。2.3 用dpkg-deb二次核对依赖确认没漏下载完成不代表一定够这是离线安装里最容易翻车的一步。apt-get install --download-only有个隐藏陷阱如果联网机器上某些依赖已经安装且版本满足apt不会把对应的deb放进缓存目录但内网目标机上可能恰好没有这个依赖或者版本偏低。所以必须做一次依赖核对。核对方法很简单对每个docker相关的deb执行dpkg-deb -I docker-ce_*.deb | grep -i depends比如docker-ce输出的Depends行里可能包含containerd.io, docker-ce-cli, libseccomp2, iptables等。再dpkg -l查看目标机这些包是否已装、版本是否满足。如果不满足回到联网机器上用下面的命令单独下载缺的那个包apt download libseccomp2然后把下载到的deb一起带进内网。这一步虽然耗时但能省掉后续在内网机上对着报错干瞪眼的痛苦。2.4 打包、校验、传输攒好的deb目录建议打包成一个tar避免传输时漏文件cd ~ tar czf docker-offline-debs.tar.gz docker-offline md5sum docker-offline-debs.tar.gz传输方式看内网环境条件。有ssh端口开放就scp没有就U盘或带外管理通道。文件传到目标机后先解压再核对一下hashtar xzf docker-offline-debs.tar.gz cd ~/docker-offline md5sum -c (md5sum docker-offline-debs.tar.gz) # 如果不放心可以在源机上生成校验文件一并传输实际上更稳妥的做法是把校验文件一起带过去到了内网用md5sum -c对目录里的deb逐个校验确保传输过程中没有损坏。3. 内网机器上的安装动作一步步照着做3.1 先清理可能存在的旧Docker痕迹如果目标机之前用Ubuntu自带仓库装过docker.io或者安装过老版本docker需要先清理否则新旧切换容易出问题sudo systemctl stop docker 2/dev/null || true dpkg -l | grep -E docker|containerd如果看到docker.io相关包已安装离线环境下用dpkg直接移除sudo dpkg -r docker.io不需要联网dpkg本地就能卸载。这里不建议在目标机上反复执行apt-get remove因为apt在离线状态下解析依赖时可能会因为源不可达而卡住。3.2 按依赖顺序执行dpkg -i进入deb目录后不要随便dpkg -i *.deb。虽然一次给全dpkg能自动处理一部分依赖但为了减少不确定性我习惯按“containerd → cli → 插件 → 主包”的顺序一次性传给dpkgcd ~/docker-offline sudo dpkg -i ./containerd.io_*.deb ./docker-ce-cli_*.deb ./docker-buildx-plugin_*.deb ./docker-compose-plugin_*.deb ./docker-ce_*.debdpkg -i一次传多个包时dpkg会先解包再统一配置所以同批次内的依赖关系能自动满足。如果某一步报依赖缺失大概率就是2.3节核对时漏了包。这时候别急着用--force-depends硬装先看报错里缺的是哪个回到联网机器补包再传一次。3.3 写一个稳妥的daemon.json安装完成后Docker还没启动。在启动前先把/etc/docker/daemon.json写好。完全离线的环境里registry-mirrors这种外网镜像加速配置意义不大重点要配置的是数据目录、日志限制和私有仓库{ exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m, max-file: 5 }, data-root: /data/docker, insecure-registries: [harbor.internal.example.com], dns: [10.0.0.2, 223.5.5.5] }解释一下几个字段exec-opts改成systemd比较适合大多数Ubuntu服务器cgroup驱动对齐了容器编排环境才稳定>{ exec-opts: [native.cgroupdriversystemd], log-opts: { max-size: 100m, max-file: 5 } }3.4 启动服务并验证配置文件写好之后启动Dockersudo systemctl daemon-reload sudo systemctl enable docker sudo systemctl start docker systemctl status docker --no-pager -l状态里出现active (running)说明daemon起来了。然后执行sudo docker version sudo docker infodocker version能看到Client和Server两段信息Server段的Version是24.0.7就说明核心安装没问题。docker info里有几项值得留意Storage Driver最好是overlay2Cgroup Driver如果是systemd就说明daemon.json生效了Server Version和存储驱动都正确安装基本成功。3.5 离线镜像导入测试一下离线环境下没法直接docker pull hello-world所以需要把测试镜像用docker save带进内网# 在联网机器上 docker pull hello-world docker save hello-world:latest -o hello-world.tar把hello-world.tar传到内网机器后sudo docker load -i hello-world.tar sudo docker run --rm hello-world看到Hello from Docker!这段输出整个安装流程就跑通了。我在内网交付时还会顺手把常用基础镜像一起save进去比如ubuntu:20.04、nginx、mysql:8.0省得后面现场工程师到处找包。4. 离线安装最容易翻车的四个坑4.1 坑一联网机与内网机系统版本不一致依赖库版本错位这个坑我印象最深。第一次给客户装的时候客户内网机是Ubuntu 22.04我在自己笔记本的20.04上攒的包。结果内网机器执行dpkg -i时一路报libsystemd0版本过低的错docker-ce depends on libsystemd0 ( 249); however: Version of libsystemd0 on system is 245.4.排查过程其实不复杂用dpkg-deb -I docker-ce_*.deb | grep Depends看依赖要求再用dpkg -l libsystemd0看目标机实际版本二者一对比就知道是版本跨度太大。Ubuntu 20.04和22.04的系统库差异很大跨版本攒包基本必踩。解决办法也很简单找一台与目标机相同大版本的联网Ubuntu机器重新执行第2章的攒包流程不要在旧系统上硬凑高版本依赖包硬凑出来的环境不稳定装了也不知道以后docker起不起得来。4.2 坑二apt缓存目录看似齐全实则缺了目标机独缺的包前面2.3节已经提过这个隐患。我实际遇到的场景是联网机器上已经装过libseccomp2版本2.5.1满足docker-ce的 2.3.0要求所以apt没往缓存里放这个包。但内网目标机的libseccomp2是2.2.3版本不够安装时报出来的错误大概长这样dpkg: dependency problems prevent configuration of docker-ce: docker-ce depends on libseccomp2 ( 2.3.0); however: Version of libseccomp2 on system is 2.2.3.内网机没有apt源我第一反应是找离线包后来发现只能回联网机重新下载。正确的检查流程是拿到deb集合后先在本机对每个deb做dpkg-deb -I把Depends里出现的包名整理出来再到内网机用dpkg -l逐一确认版本。一个包一个包核对确实麻烦但这是离线安装最稳的逻辑没有捷径。4.3 坑三systemd启动时直接失败日志里是iptables或overlay问题还有一种情况是安装时一路顺利执行systemctl start docker时挂了。日志查看命令journalctl -xu docker --no-pager | tail -50常见两类报错一是overlay相关比如failed to mount overlay: operation not permitted多半是内核模块没加载可以先执行modprobe overlay再重启docker二是iptables相关比如Failed to program FILTER chain通常是机器上之前残留过其他容器或防火墙配置需要先清理旧链。另外如果daemon.json里的exec-opts写成了native.cgroupdrivercgroupfs但系统默认cgroup是systemd也可能导致启动异常改成一致即可。这类问题排查时不要反复盲试先看日志定位再对症处理。日志里没有明确报错时可以先手动执行dockerd --debug前台跑一下错误信息会直接打在终端上比看systemd日志更直观。4.4 坑四docker compose命令不可用插件没进CLI插件目录离线装完docker后执行docker compose version如果报docker: compose is not a docker command说明docker-compose-plugin没有正确安装或者插件没有放到Docker CLI的插件目录。正常安装docker-compose-plugin这个deb包后它会把插件放到/usr/libexec/docker/cli-plugins/docker-ce-cli会自动识别。如果还是找不到手动创建目录并建立软链sudo mkdir -p /usr/libexec/docker/cli-plugins sudo ln -sf /usr/libexec/docker/cli-plugins/docker-compose /usr/local/bin/docker-compose验证命令docker compose version输出Docker Compose version v2.x.x就正常了。buildx插件遇到类似问题时检查docker buildx version是否可用逻辑是一样的。5. 卸载方法连数据带残留完全清干净5.1 先停止服务再按顺序卸载包卸载之前先停服务和自启动sudo systemctl stop docker sudo systemctl disable docker如果目标机还有网络或apt本地状态可用可以执行sudo apt-get purge -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin但离线环境下apt不一定顺畅用dpkg更可控。移除顺序和安装顺序相反先移主包再移依赖sudo dpkg --purge docker-ce sudo dpkg --purge docker-buildx-plugin sudo dpkg --purge docker-compose-plugin sudo dpkg --purge docker-ce-cli sudo dpkg --purge containerd.io--purge会连包自带的配置文件一起删。如果dpkg因为依赖关系拒绝卸载containerd.io可以用--force-depends强制sudo dpkg --force-depends --purge containerd.io这属于最后手段正常情况下上面的顺序足够。5.2 数据目录、配置文件和源清单逐一清理这一步不能省。很多人卸载完包发现/var/lib/docker还在占着几个T的磁盘空间。Docker的数据目录、配置目录和运行文件需要手动清理sudo rm -rf /var/lib/docker sudo rm -rf /var/lib/containerd sudo rm -rf /etc/docker sudo rm -rf /etc/systemd/system/docker.service.d sudo rm -rf /etc/apt/keyrings/docker.gpg sudo rm -f /etc/apt/sources.list.d/docker.list sudo rm -f /etc/apparmor.d/docker 2/dev/null重点提醒/var/lib/docker里是所有镜像、容器、卷的落盘数据删了就彻底找不回来了。正式清理前确认不需要保留或者先执行sudo tar czf /data/docker-data-backup.tar.gz /var/lib/docker做个备份再删。配置文件同理如果之后还要重装建议先备份/etc/docker重装时直接复用能省不少事。5.3 iptables残留和docker用户组清理Docker运行期间会在iptables里创建DOCKER、DOCKER-USER等链卸载后这些链不一定自动消失。如果机器上还有其他服务在跑不建议直接全局刷空nat表风险很大。更稳的做法是重启机器让iptables规则回到系统默认状态如果不想重启可以按需删除docker相关链sudo iptables -t nat -nL --line-numbers | grep DOCKER确认只有docker相关链是残留后再谨慎删除。我的习惯是操作前先备份整个iptables配置sudo iptables-save /tmp/iptables-before-uninstall.txt清理完还需要删除docker用户组sudo groupdel docker如果系统提示docker组不存在说明安装时没有单独创建该组忽略即可。5.4 验证卸载后的状态最后用一张检查表确认清理干净检查项命令预期结果docker命令是否还存在which docker无输出systemd服务是否还在systemctl status dockernot found 或 inactive数据目录是否删除ls -ld /var/lib/dockerNo such file or directory配置目录是否删除ls -ld /etc/dockerNo such file or directory用户组是否已删getent group docker无输出检查项全部通过卸载流程就结束了。我个人现在维护离线部署包的习惯是“安装脚本 debs目录 卸载脚本”三件套。每次给客户交付前先在一台干净机器上完整走一遍安装和卸载确认两条路径都顺再拿这套包去现场用。离线环境最容易出问题的从来不是命令本身而是“以为包够了”和“以为卸载干净了”这两个错觉。把这个攒包、核依赖、验安装、清残留的流程固定下来不管Ubuntu大版本怎么迭代换一台机器也能照葫芦画瓢。