☰
Ubuntu 22.04离线安装Docker与Docker Compose全流程
2026/9/29 15:48:48 网站建设 项目流程

1. 为什么选择离线安装:两种典型场景与方法选型

做运维和开发的都知道,docker这东西在线装就是几行命令的事,curl -fsSL https://get.docker.com | sh一把梭,装完就能用。但真正到了生产环境、内网环境、隔离机房,这套玩法直接就废了。我这次遇到的场景是给一台部署在隔离网段的Ubuntu 22.04服务器装docker和docker compose,目标机器除了能访问内网特定服务器的特定端口之外,外网完全不通。别说get.docker.com了,连apt源都连不上。

1.1 什么时候必须走离线路线

先别急着搜"离线安装docker",你得先确认自己是不是真的走到了离线这一步。我总结了几类典型场景:

  • 内网隔离环境:政务网、金融内网、军工涉密网,物理隔离或者白名单管控,外网根本出不去。这是最常见的离线需求来源。
  • 生产环境变更管控:有些公司生产环境禁止直接暴露公网,所有软件安装必须走审批流,先把安装包提交到内部制品库,再统一分发。
  • 弱网环境:有些机房到公网的带宽小得可怜,装个docker要拉几百兆的包,一断就废,这种时候把包拉到本地再传过去反而更稳。
  • 合规审计要求:需要记录软件来源、版本、校验值,在线装的话你很难说清楚装的是哪个版本、依赖了哪些包。

如果你只是自己开发机想装个docker,那就别折腾离线方案了,在线装省心得多。离线安装是给"不得不过"的场景准备的,它的核心价值不光是能装上,更是让你对装了什么、依赖了什么都心里有数。

1.2 两条主流离线方案怎么选

我调研了一圈,离线安装docker和docker compose的主流做法其实就两条路:

方案A:下载deb包,离线dpkg安装

在一台和和目标机器相同系统版本的联网机器上,把docker相关的deb包以及所有依赖deb包全部下载下来,拷贝过去,用dpkg -i或者apt-get install本地安装。这个方案最贴近原生安装方式,装完之后systemd服务、docker命令、compose插件路径全都符合官方布局,后面维护起来不会出幺蛾子。

方案B:直接拷贝二进制文件

docker和docker compose本质都是编译好的二进制程序,理论上你在任何一台机器上把二进制文件拷贝过去,加上必要的目录结构就能跑。一些精简方案就是这么做的:把/usr/bin/docker整个目录拷过去,或者只拷docker和dockerd两个二进制。docker compose更简单,一个可执行文件就搞定。

方案B看着很爽,实际坑很多。docker运行时依赖很多动态库(libc、libsystemd等),目标机器上版本不匹配就直接启动失败;而且官方安装脚本会同时装containerd、runc、docker-init这几个配套组件,你只拷docker主程序,跑起来的容器会有各种不可预期的问题。我的建议是:能用deb包解决就别直接拷二进制,除非是那种连apt都跑不起来的精简系统,才考虑二进制方案。

方案A里还有个细分选择:是只下载需要的deb,还是干脆搭一个本地apt仓库。前者适合一次性安装三五台机器,后者适合大规模批量部署。我这次要装的机器就两三台,所以走了"精准下载依赖deb"的路线,下面重点讲这条线的实操细节。

2. 在联网机器上准备安装包:依赖链的收集思路

离线安装最烦的环节不是装,而是准备。docker的deb包本身不大,但它的依赖横跨docker-ce、docker-ce-cli、docker-ce-rootless-extras、containerd.io、docker-compose-plugin等多个包,而且每个包还依赖libc、iptables、systemd等一系列系统库。你光下一个docker-ce的deb回去是装不上的,会报一串dependency errors。

2.1 用什么工具把依赖"一网打尽"

我推荐三步走:apt-get download+dpkg-deb检查 + 手动补齐依赖。先说你最需要的几行命令。

在一台和目标机相同系统版本和架构的Ubuntu机器上,先配置好docker官方apt源:

# 添加docker官方GPG key和仓库 sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL "https://download.docker.com/linux/ubuntu/gpg" -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update

注意一个细节:确定目标机的Ubuntu版本代号。22.04是jammy,20.04是focal,别用错了,否则apt会找不到对应版本。查版本代号用:

lsb_release -cs

然后下载docker全部相关包:

mkdir ~/docker-offline && cd ~/docker-offline # 用apt-get download批量下载指定包,--print-uris可以打印所有包的下载地址 apt-get download docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras

这一步会下载5~6个deb。但到这里还没完,因为这些包本身还有系统依赖。你需要用apt-cache depends逐个检查:

apt-cache depends docker-ce

比如docker-ce会依赖iptables、libseccomp2、perl、procps等。其中libseccomp2这种基础库,目标机系统自带的版本一般够用,但为了保险起见,我建议把非基础系统库的依赖也都下载了。

这里分享一个省力的技巧:直接用apt-get install --download-only配合--reinstall,让apt帮你算出完整依赖链并全部下载。比如:

# 模拟安装,把所有依赖deb下载到/var/cache/apt/archives sudo apt-get install --download-only --reinstall docker-ce docker-ce-cli containerd.io docker-compose-plugin docker-buildx-plugin

执行完之后,/var/cache/apt/archives/下会有一堆deb,把docker相关的以及它依赖的系统库都拷贝出来就行。这个办法的优点是apt帮你把依赖算清楚了,不遗漏;缺点是可能多下载一些目标机本来就有的基础库,但反正不占多少空间,多带几个无所谓。

2.2 注意架构与版本:x86/ARM与Ubuntu版本的匹配

这一步特别容易翻车,我单独拿出来说。

架构必须严格匹配。目标机是x86_64就下载amd64的包,ARM机器就是arm64。在联网机器上确认架构:

dpkg --print-architecture uname -m

别天真的以为"我开发机是x86的,服务器肯定也是x86"。我在实际项目里就遇到过一台海光ARM服务器,用x86的deb包安装,dpkg直接报wrong architecture 'amd64',几十个包全部作废重来。

Ubuntu版本必须匹配。如果联网机是22.04、目标机是20.04,你把22.04的docker-ce包拿到20.04上装,大概率会因为libc版本不一致安装失败。因为docker-ce对libc6有最低版本要求,20.04自带的libc6版本太旧。所以联网准备包的机器,系统版本大版本最好和目标机完全一致。

还有一个隐藏的坑:Ubuntu的衍生版。如果你的目标机是Linux Mint、Deepin、UOS这类基于Ubuntu的系统,它们的包管理底层虽然兼容apt,但系统代号不同,直接拿Ubuntu的docker源可能加不上。这种场景建议直接用二进制方案,或者从对应的衍生版官方源找docker包。

2.3 传输到目标机的注意事项

下载好之后,把整个目录打包传到目标机。我习惯用tar压缩一下,deb包单个不大,但文件数量多,压缩后传输效率高很多。目标机能用scp就直接scp,如果连scp都没有,那就走移动硬盘或者内网文件服务器。

tar czf docker-offline.tar.gz ~/docker-offline # 传到目标机后解压 tar xzf docker-offline.tar.gz

传输过程中注意校验一下文件完整性。推荐用md5或者sha256比对关键deb包的checksum:

sha256sum docker-ce_*.deb

在线下载的机器上先记下这个值,传到目标机器再算一遍,一致再装。有些内网的传输链路不稳定,中间丢字节了就会导致dpkg安装报"package architecture does not match"或者"unexpected EOF"这类莫名其妙的问题,校验一下能排查掉一半的沙雕故障。

3. 目标机离线安装docker:顺序、验证与自启

包都到了目标机,接下来就是安装了。这个过程看着简单,但顺序错了或者缺了某个依赖,就会陷入"装A缺B、装B缺C"的循环。我直接给一套稳妥的操作流程。

3.1 dpkg安装的正确打开方式

先把所有deb放到同一个目录下,比如/opt/docker-offline,然后执行:

cd /opt/docker-offline sudo dpkg -i *.deb

这条命令会自动安装当前目录下所有deb包。如果依赖缺失,dpkg会报错并提示缺哪个包。这时不要慌,先执行一下:

sudo apt-get install -f -y

这个命令会让apt自动修复依赖,但它需要能访问源。离线环境下它会提示找不到源,这时你就得手动补包了。补包的办法就是回到联网机器,检查缺失的依赖,下载后再传过来,再执行dpkg。

我在实际安装时遇到的顺序问题是这样解决的:先装containerd.io,再装docker-ce-cli,最后装docker-ce。因为docker-ce依赖docker-ce-cli,docker-ce和containerd是两个独立的daemon,彼此没有硬依赖关系,但同时装的话dpkg会要求所有依赖同时满足。你如果先用一条dpkg -i *.deb操作,恰好目录里包含了所有依赖包,大部分情况下能一把过。

更保险的做法是用apt-get install配合本地文件路径,让apt自动解析依赖顺序:

sudo apt-get install -y /opt/docker-offline/*.deb

这条命令相当于告诉apt"我用本地文件安装,你帮我解决依赖顺序"。apt会按依赖拓扑排序安装,比dpkg -i智能得多。

3.2 安装后的组件验证

装完之后别急着配镜像加速,先验证这四个核心组件是否都正常:

# 1. 主程序版本 docker --version # 2. 服务端daemon版本 docker version --format '{{.Server.Version}}' # 3. containerd运行时 containerd --version # 4. compose插件(先别管,后面细说) docker compose version

docker --version只能验证客户端装好了,服务端daemon没起来的话,执行docker version会显示Server: ERROR,容器根本跑不了。所以要确认docker version的两个部分都有版本号输出。同时检查一下systemd服务是否注册成功:

systemctl status docker --no-pager systemctl status containerd --no-pager

如果服务启动了,会显示active (running)。如果显示failed,多半是daemon配置错了或者依赖的iptables有问题,往下看。

3.3 配置开机自启与基础运行环境

离线安装的deb包其实会自动注册systemd服务,但必须手动启用开机自启:

sudo systemctl enable docker.service sudo systemctl enable containerd.service

然后启动docker:

sudo systemctl start docker sudo systemctl status docker

再验证docker能正常启动容器。离线环境没有镜像,所以先拉一个本地不存在的镜像必然是失败的,但不影响验证daemon状态。验证daemon正常可以用一个轻量方式:

sudo docker run --rm hello-world

这个会提示找不到镜像hello-world:latest,只要报错是"Unable to find image"而不是daemon错误,说明docker基本可用了。如果你手头有内网镜像仓库,可以直接配daemon的insecure-registries指向内网地址,然后从内网拉镜像测试:

{ "insecure-registries": ["registry.internal.example.com:5000"] }

修改完后重启docker:

sudo systemctl restart docker

这个配置单独说一下:离线环境下没有官方镜像源,你拉镜像的唯一途径就是内网仓库或者镜像文件,insecure-registries允许docker以HTTP方式访问内网仓库,不然默认强制HTTPS会直接握手失败。

4. docker compose离线部署:二进制安装与插件机制

docker compose这里坑最多,很多人在线装惯了没感觉,离线装的时候各种"command not found"和"unknown command"。核心原因在于:docker compose和docker是两个完全独立的组件,docker主程序不知道compose的存在,它只是按约定去特定目录找compose插件。

4.1 v2插件机制与路径关系

现在的docker compose已经迭代到v2版本,安装方式不再是docker-compose独立命令,而是作为docker的cli-plugin插件来使用。docker cli会从以下两个目录查找插件:

  • /usr/local/lib/docker/cli-plugins/:系统级目录,所有用户可用
  • ~/.docker/cli-plugins/:用户级目录,仅当前用户可用

你执行docker compose version时,docker cli就会去这两个目录找名为docker-compose的可执行文件。找到了,就能用docker compose子命令;找不到,就会报"unknown command: docker compose"。

在线安装docker-compose-plugin的deb包,会自动把插件放到/usr/local/lib/docker/cli-plugins/,所以一条命令就完事。离线安装也一样,只要你前面的deb下载列表里包含了docker-compose-plugin这个包,用dpkg装完,compose插件就会自动落位。所以走deb路线的同学,记得准备包时把这名字加上。

4.2 安装、授权与验证

如果你不想用deb,或者目标机架构特殊找不到对应的compose-plugin包,那就直接用二进制文件安装。从GitHub下载对应架构的docker-compose-linux-x86_64之类的文件,放到插件目录并赋予执行权限:

# 创建插件目录 sudo mkdir -p /usr/local/lib/docker/cli-plugins/ # 如果有现成的二进制文件(比如你从其它机器拷贝的),放进去 sudo cp docker-compose /usr/local/lib/docker/cli-plugins/ # 赋予执行权限 sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose # 验证 docker compose version

这一步还涉及另外一个坑:文件名的规范。插件文件名必须叫docker-compose,不能带版本号后缀,也不能叫docker-compose-v2.24.1这种。docker cli查找的规则是精确匹配这个名字,你多带个版本号它就找不到了,然后又是熟悉的"unknown command: docker compose"。

权限也是一个隐藏问题,如果你放在用户级目录~/.docker/cli-plugins/,那么只有当前用户可以运行docker compose,换一个用户就报command not found,但docker命令又是好的。所以建议老老实实放系统级目录。

4.3 版本匹配建议

compose插件版本和docker主程序版本不需要严格对应,官方在兼容性上做得比较宽。但为了少踩坑,我建议:

  • docker主程序版本 >= 20.10,建议上到23.0以上。
  • compose插件版本 >= 2.20,因为早期v2版本有一些bug,比如docker compose up的--wait参数行为不稳定。
  • 不要用compose v1(docker-compose standalone)和docker v2主程序混用,v1老早就不维护了,很多新语法不支持。

查看当前已装版本:

docker version --format '{{.Server.Version}}' docker compose version

如果两者都正常输出版本号,compose这块就算完工了。

我在实际验证compose环境时喜欢用一个极简的docker-compose.yml先跑一遍:

services: test: image: busybox:1.36 command: ["echo", "compose ok"]

执行:

docker compose up

如果能拉取镜像并输出"compose ok",说明compose插件、docker daemon、容器运行链路全部通了。内网环境拉不到busybox可能失败,这时就改成内网仓库里已有的任意镜像,只要能docker compose up正常执行就算验证通过。

5. 离线环境下的镜像获取与网络问题

装好了docker和compose,万里长征才走了一半。真正的业务要跑起来,得有镜像。离线环境没有公网镜像源,怎么搞到镜像就成了下一个核心问题。

5.1 联网机器上docker save / load

最简单直接的思路:在联网机器上把镜像打包,传到内网后在目标机load。命令很简单:

# 在联网机器上 docker pull nginx:1.25 docker save -o nginx-1.25.tar nginx:1.25 # 传到目标机后 docker load -i nginx-1.25.tar

注意几个细节,都是实际坑:

  • 用save -o还是save > file都行,但-o这种方式在docker 24以后推荐,因为重定向在Windows下容易把二进制文件弄坏。
  • docker save会把镜像的所有层和元数据打成一个tar包,尺寸通常是镜像体积的等效体积,一个1GB的镜像打包后也可能接近1GB。传之前用gzip压缩能省一半以上的传输时间:
docker save nginx:1.25 | gzip > nginx-1.25.tar.gz # 目标机上 gunzip -c nginx-1.25.tar.gz | docker load
  • 如果是多架构镜像,docker pull默认拉的是当前机器架构的版本。你在x86机器上save的是amd64镜像,拿到ARM服务器上load能成功,但运行时可能会报exec format error。解决方式是拉镜像时显式指定平台:
docker pull --platform linux/arm64 nginx:1.25

5.2 镜像加速与内网镜像仓库的取舍

如果你要管理的镜像很多,比如十几个服务,每个服务还有版本迭代,那靠docker save传tar包的方式就是自虐。这种场景的正确做法是搭一个内网镜像仓库。

离线环境搭镜像仓库分两步:

  1. 在联网机器上运行docker pull+docker push,把需要的镜像推到内网registry。
  2. 目标机器配置docker daemon,指向内网registry。

但有个麻烦:普通镜像推送到registry用的是HTTPS,内网环境往往没有CA证书。所以必须配置insecure-registries让docker允许HTTP访问:

{ "insecure-registries": ["192.168.1.100:5000"] }

修改完/etc/docker/daemon.json后重启docker生效。注意这个配置是daemon级的,改完要重启docker服务,不是重载配置就行的。如果你用自签HTTPS证书,也可以配置registry-mirrors指向内网的镜像加速服务,但要保证目标机信任这个自签CA,否则会报x509: certificate signed by unknown authority。

5.3 docker网络不通的常见原因

离线环境装完docker,网卡、防火墙、路由都可能和标准环境不一样,docker网络出问题是重灾区。我遇到过几个典型:

第一类是容器能启动,但容器访问不了外网(或者内网其他服务)。这通常是因为iptables规则没被docker正确接管。docker daemon靠iptables做NAT转发,如果目标机本身开了自己的iptables服务并且清空规则,docker的FORWARD链可能会被拦。排查命令:

iptables -L -n -v | grep -i docker iptables -t nat -L -n | grep -i docker

如果发现docker相关链缺失,重启docker服务让它重新生成规则:

sudo systemctl restart docker

第二类是docker bridge网段和内网已有网段冲突。默认bridge网段是172.17.0.0/16,如果你的内网刚好是172.17.x.x,那容器网络就直接撞车了。解决方式是修改/etc/docker/daemon.json,指定新的bip:

{ "bip": "10.200.0.1/24" }

第三类是系统防火墙和docker的兼容问题。有些发行版默认开了nftables/firewalld,会干扰docker创建的iptables规则。建议离线环境里先明确目标机的防火墙策略,有条件的话临时关掉防火墙验证一下是不是它的问题:

sudo systemctl stop ufw sudo systemctl status ufw

当然这只用于排查,安全要求高的环境你最终还是要回归防火墙策略,把docker需要的端口和网段规则加进去。

6. 完整踩坑记录:我从离线安装中总结的5个教训

离线装docker的过程,我前前后后折腾了不下十次,从ubuntu 18.04到22.04各种版本都碰过。这里把最有价值的几个教训整理出来,每一个都是用实际故障换来的。

6.1 不要轻易动docker的默认数据目录

离线环境装完docker,磁盘空间规划很重要。默认情况下docker的数据目录是/var/lib/docker,一般在系统盘。如果系统盘空间紧张,你会想把数据目录挪到挂载盘。这个思路本身没问题,但挪目录要小心服务启动顺序。

我试过直接改daemon.json的>sudo mkdir -p /data/docker sudo chmod 711 /data/docker

再改daemon.json:

{ "data-root": "/data/docker" }

然后重启docker。这里有一个细节:/data/docker的权限不要用777,而要用711,这是docker官方推荐权限,用777在某些安全审计环境会被拦。

6.2 "unknown command: docker compose"的真正原因

这个报错我遇到过三次,每次原因都不同:

  • 第一次是compose插件没装,docker-compose-plugin这个deb没打包。
  • 第二次是插件文件命名错误,从GitHub下载的文件名带了一串版本号,我直接复制到cli-plugins目录没改名。
  • 第三次是权限问题,插件目录在某个用户的home下,我用另一个用户执行docker compose,自然找不到。

写这个教训就是想提醒大家:碰到这个报错先排查三件事——插件装没装、文件名对不对、路径和权限对不对。别一上来就重装docker,那解决不了问题。

6.3 dpkg安装到一半失败怎么办

dpkg装deb最怕装一半报错,报错以后一堆包处于"半安装"状态,dpkg -i再执行会提示"dpkg interrupted"。这是最烦人的状态,因为后续所有包都装不了了。

解决办法是先把破损状态清理掉:

sudo dpkg --configure -a

这会尝试修复所有处于半配置状态的包。如果修复失败,比如某个包的依赖还是缺失,那就把你需要的包从缺失依赖里拆出来,先装依赖,再装主包。我遇到过docker-ce依赖libseccomp2版本太旧的情况,解决办法是下载更新的libseccomp2 deb包先装上,再装docker-ce。

6.4 传输时压缩能省很多事

这不算故障,算经验。deb包集合加docker镜像tar包,随随便便就是几个GB。我第一次离线部署的时候没压缩,用U盘拷贝,几个文件夹分开拷,结果有一部分丢了,到现场才发现。后来统一用tar打包再传到目标机解压,速度快不说,还能保留目录结构和权限。

# 打包时保留文件权限 tar czf docker-bundle.tar.gz /opt/docker-offline /data/images # 校验 tar tzf docker-bundle.tar.gz | head -20

我自己还有一个习惯:打包后生成一个checksums.txt,把所有deb和tar包的sha256记录在内,传到目标机后逐项比对。这五分钟的活儿,能省掉现场排查"为什么装不上"的两小时。

6.5 版本锁定是离线环境的保命符

在线环境随时可以apt update升级,离线环境不一样,一个版本被移出镜像源之后你就再也拉不到了。所以我强烈建议:离线环境安装时,把安装的docker版本和compose版本明确记录下来,固化到运维文档里。包括每个deb包对应的具体版本号:

dpkg -l | grep -E "docker|containerd"

输出里会显示类似这样的版本信息:

ii docker-ce 27.1.1-1~ubuntu.22.04~jammy ii docker-ce-cli 27.1.1-1~ubuntu.22.04~jammy ii containerd.io 1.7.18-1 ii docker-compose-plugin 2.29.2-1~ubuntu.22.04~jammy

这几个版本号就是你在离线环境里的"锚点"。后续如果要从内网仓库升级,这些版本号决定了你需要找哪些新deb。如果某个依赖被apt自动升级了,导致docker不兼容,回滚也会非常痛苦。

最后补充一个小技巧:把安装过程中用到的所有deb包、tar包、daemon.json配置、docker-compose文件,全部归档到一个目录,并写一份简短的README.md记录机器IP、版本号、安装日期和操作人。这样下次另一台机器需要部署时,照着文档走一遍就行,不用再从零摸索。

离线安装docker这件事,本质上不是技术难点,而是工程规范问题。只要你控制了依赖、校验了包、记录了版本、验证了运行链路,后面基本就是体力活。希望这篇东西能帮你少踩几个坑,尤其是那些"在错误的方向上努力了很久"的坑,才是真正浪费时间的地方。

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

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

立即咨询