☰
Docker 内网离线安装实战:从依赖打包到私有仓库部署
2026/10/8 8:32:57 网站建设 项目流程

简介:内网环境缺少外部镜像源和Yum仓库时,Docker与Docker Compose的离线安装往往卡在依赖匹配上。此压缩包正好面向离线部署场景,提供20个rpm依赖包、1个install.sh自动安装脚本和1个docker-compose-linux-x86_64可执行文件,共22个文件,整体约120.91MB。rpm包覆盖docker-ce 20.10.7、docker-ce-cli、containerd.io、rootless-extras以及slirp4netns、fuse-overlayfs等网络与存储组件,install.sh可批量导入并安装,自动完成依赖检查和环境校验,避免逐条输入rpm命令及版本冲突;docker-compose二进制适用于x86_64 Linux,按目录放置并赋权后即可配合Docker使用,编排命令可正常执行。适用于政务网、生产内网等隔离环境,可在一台CentOS 7兼容系统上快速搭建容器运行平台。已有4706人学习下载,能显著节省依赖收集和排错时间,适合需要离线部署的运维工程师参考。

1. 内网离线安装 Docker:先回答三个问题再动手

内网离线安装 Docker 这件事,卡住人的往往不是 docker 本身,而是依赖、源和传输这三座大山。内网生产机没有外网权限,yum install docker 直接报 Could not resolve host;就算搞到了安装包,缺依赖缺到怀疑人生的情况也常见。我整理的是从环境核对、依赖打包、传输校验,到 docker 与 docker-compose 安装、镜像搬运、私有仓库搭建,再到高频坑点排查的完整落地过程。适合的读者是:手头有一台不能上外网的 Linux 服务器,需要把 docker 和 docker-compose 装上,并且让容器真正跑起来的人。命令都在 CentOS 7.9 和 Ubuntu 20.04 内网环境验证过,照着走能少踩一半坑。

2. 离线安装前的准备:环境核对、依赖打包与传输校验

2.1 环境核对:内核版本、系统发行版和 CPU 架构先对齐

离线安装最忌讳的是拿了包就装,装不上才回头看环境。我一般会在动手前问清三件事:内核够不够新、系统是 CentOS 还是 Ubuntu、CPU 是 x86_64 还是 arm64。这三个答案直接决定下载地址和版本选择。

Docker Engine 对内核有硬性要求。官方文档写的是 Linux 内核 3.10 起步,但那是老黄历——新版 docker 24.x 在 CentOS 7 的 3.10 内核上跑,会遇到 cgroup 和 iptables 的兼容问题。我踩过后的经验是:CentOS 7.9 装 docker 20.10 或 23.0 相对稳,想上 24.0+ 先确认内核要不要升级;Ubuntu 20.04/22.04 内核是 5.4/5.15,基本没这个烦恼。arm64 服务器(比如飞腾、鲲鹏)必须下载 arm64 架构的包,拿 x86_64 的二进制硬跑会直接报 Exec format error。

# 在目标内网机上执行,先摸清家底 uname -r # 内核版本,例如 3.10.0-1160.el7.x86_64 cat /etc/os-release # 发行版与版本号 uname -m # CPU 架构,x86_64 或 aarch64

这三条命令的输出就是选型依据:uname -r 决定能不能跑新版 docker,/etc/os-release 决定从哪类源找依赖,uname -m 决定下载哪个架构的二进制。顺手把 nproc 和 free -h 也跑一下,配 docker 资源限制时用得上。另外还有一个判断内核支不支持 overlay2 存储驱动的小命令:cat /proc/filesystems | grep overlay,有输出说明内核编译了 overlayfs,没有的话就得换存储驱动或者升级内核。

还有一件事容易被忽略:/var/lib/docker 所在分区要预留足够空间。docker 装好后镜像、容器、卷都堆在这里,一个 mysql 8.0 镜像加数据轻松到十几 GB。我见过内网服务器根分区只有 50GB,装完 docker 拉几个镜像就告警的,建议先用 df -h 看一遍挂载点,必要时把># 跳板机上执行:下载 docker 引擎与 compose 独立二进制 wget https://download.docker.com/linux/static/stable/x86_64/docker-24.0.7.tgz wget https://github.com/docker/compose/releases/download/v2.24.6/docker-compose-linux-x86_64

版本选择上我的原则是:能选经过验证的稳定版,不选刚发布的新版。docker 24.0.7 是验证过很久的版本,兼容性已知问题都清楚;如果目标机是 CentOS 7.9 且内核没动过,就选 20.10.24。docker-compose 同理,v2.24.6 对应 docker 24.x 用起来很顺。版本定下来后,下载链接里的路径就是固定的,别用 latest 之类的动态链接,内网环境没法重试。

下载完别急着传,先做两件事:md5sum 校验包完整性,防止传一半损坏;把文件放到同一个目录,后面一次 scp 传过去。

md5sum docker-24.0.7.tgz docker-compose-linux-x86_64 > checksum.txt ls -lh docker-24.0.7.tgz docker-compose-linux-x86_64

第二种路线是系统包管理器拉 rpm/deb。适合目标机器没有 systemd(某些精简环境),或公司安全策略要求用 rpm 安装并保留记录。做法是在跳板机上用 yumdownloader 或 apt download 把 docker 和依赖一次性拉下来:

# CentOS 7.9 跳板机 yum install -y yum-utils yumdownloader --resolve --destdir=/opt/docker-rpms docker docker-ce-cli containerd.io

--resolve 参数把依赖一起拉下来,--destdir 指定输出目录。需要注意,这个命令得先配好 docker 的 yum 源,否则拉的是系统自带的老 docker。yumdownloader 拉下来的 rpm 包之间可能有版本依赖关系,docker-ce-cli 和 containerd.io 的版本要跟 docker-ce 匹配。yum 在线安装时会自动处理,但在内网手动 rpm -Uvh *.rpm 时依赖不会自动解析,所以 --resolve 拉全依赖这件事必须做对。拉完检查一下目录里是不是有十几个 rpm,如果只有三五个,说明源配置有问题,依赖没拉全。拉到后清点文件数量,安装时心里才有数。

静态二进制包也好、rpm 包也好,本质都是把安装期要用的东西提前备齐,区别只是交付形态。我推荐静态二进制:不污染系统目录,卸载也干净——删掉 /usr/bin/dockerd 和服务文件就完事。

2.3 传输与校验:scp、U 盘与 md5 双端核对

文件备齐后是传输。内网机器通常和外网物理隔离,但局域网内一般能通过 scp 从跳板机推到目标机。如果目标机连局域网都不通,只能走 U 盘拷,这时候文件清单和校验更重要。

# 跳板机执行:传给内网目标机 scp -r /opt/docker-rpms root@192.168.1.10:/opt/install/

文件清单长这样,两个二进制包加一个校验文件。核对清单能帮你在目标机上确认文件齐全再动手,尤其走 U 盘拷贝时,少拷一个文件可能要来回跑一趟机房。

文件大小(约)用途
docker-24.0.7.tgz70MBdocker 引擎静态二进制
docker-compose-linux-x86_6460MBdocker-compose v2 独立二进制
checksum.txt1KBmd5 校验清单

传到目标机后,第一件事不是解压安装,而是重新校验 md5。内网传输经过交换机防火墙,文件损坏概率不大,但二进制一旦损坏,安装时报错非常难查——tar 解压报 unexpected EOF,或 dockerd 启动直接 segment fault。我第一次部署时从 Windows 机器经 U 盘拷文件,换行符被改成 CRLF,二进制直接跑不起来。从那以后,每次传输完都强制 md5sum -c。

# 目标机上执行 cd /opt/install/ md5sum -c checksum.txt

输出两个 OK 就是安装前的最后确认,通过后进入正式部署。

3. 二进制安装 Docker 与 Compose:从解包到 systemd 托管

3.1 解包并放置二进制:可执行文件各就各位

拿到 docker-24.0.7.tgz 后,安装动作很朴素:解压、复制、写服务文件、启动。官方 tgz 解压后是一个 docker 目录,里面有 dockerd、docker、docker-proxy、containerd、containerd-shim-runc-v2、ctr、runc 等。dockerd 是守护进程,docker 是客户端,containerd 是容器运行时,runc 负责真正创建进程,docker-proxy 负责端口映射的流量转发。

cd /opt/install/ tar xzf docker-24.0.7.tgz cp docker/* /usr/bin/ docker version

docker version 这时会输出 Client 和 Server 两段,Server 段现在是空的,因为 dockerd 还没启动,这是正常现象。小细节:cp docker/* /usr/bin/ 会把 containerd、runc 全拷进去,它们是 dockerd 启动时按 PATH 找的,放 /usr/bin 最保险,别只拷 dockerd 和 docker。拷完后 ls -lh /usr/bin/dockerd 确认文件大小正常,别是个 0 字节的空文件。

3.2 写 systemd 服务文件:让 Docker 开机自启

静态二进制包不带 systemd 服务文件,这是它与 rpm 包最大的区别。需要手动写 /usr/lib/systemd/system/docker.service,让 systemd 托管 dockerd。文件里每行都值得解释:Type=notify 表示 dockerd 启动完成后通过 sd_notify 通知 systemd;ExecStart 指定守护进程路径;Restart=always 让 dockerd 挂掉后自动拉起。

cat > /usr/lib/systemd/system/docker.service <<'EOF' [Unit] Description=Docker Application Container Engine After=network-online.target firewalld.service Wants=network-online.target [Service] Type=notify ExecStart=/usr/bin/dockerd ExecReload=/bin/kill -s HUP $MAINPID LimitNOFILE=infinity LimitNPROC=infinity LimitCORE=infinity TimeoutStartSec=0 Delegate=yes KillMode=process Restart=always StartLimitBurst=3 StartLimitInterval=60s [Install] WantedBy=multi-user.target EOF

After=network-online.target 确保网络就绪后再启动 docker,避免容器启动时网卡还没配好。LimitNOFILE=infinity 放开文件描述符限制,容器数量上来后 docker 对 fd 需求很大,默认 ulimit 肯定不够。KillMode=process 只杀 dockerd 主进程,不杀它派生的 containerd 和容器进程,docker 重启时容器不会跟着全挂。StartLimitBurst 和 StartLimitInterval 控制重启频率,防止 dockerd 陷入崩溃循环把系统拖垮。

写完重载并启动:

systemctl daemon-reload systemctl enable docker systemctl start docker systemctl status docker

启动后立刻 docker info 检查三项:Storage Driver 是否 overlay2,Cgroup Driver 是否 systemd,Server Version 是否是目标版本。这三项对不上,容器跑起来迟早出问题。docker info 里还能看到 Docker Root Dir,确认>cd /opt/install/ cp docker-compose-linux-x86_64 /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose docker-compose version

这里有个常见坑:老教程教的是 pip install docker-compose,那是 python 脚本,依赖 requests、pyyaml 等一堆包,内网 python 环境装这些包本身就是灾难。能用独立二进制的场景,坚决不碰 pip 方式。另外注意,用 sudo 执行 docker-compose 时报 command not found,多半是 sudo 的 secure_path 没包含 /usr/local/bin。解决办法是把二进制放到 /usr/bin,或者在 /etc/sudoers 里把 /usr/local/bin 加进 secure_path,二选一即可。

3.4 配置 daemon.json:存储驱动、日志上限与私有仓库声明

dockerd 的默认配置在内网场景下不能直接用。最典型的是日志:json-file 驱动默认不限制大小,容器跑几天日志能涨到几个 GB,把 /var/lib/docker 所在分区撑爆。另外内网机器没有外网,registry-mirrors 配了也没用,但如果有私有仓库,必须用 insecure-registries 声明 HTTP 地址。

mkdir -p /etc/docker cat > /etc/docker/daemon.json <<'EOF' { "data-root": "/var/lib/docker", "storage-driver": "overlay2", "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "3" }, "exec-opts": ["native.cgroupdriver=systemd"], "insecure-registries": ["192.168.1.100:5000"], "live-restore": true } EOF

关键参数说明:storage-driver 选 overlay2,是目前最稳、性能最好的存储驱动,前提是内核支持;log-opts 里 max-size=50m 表示单个日志文件超过 50MB 就切割,max-file=3 保留最近 3 个文件,单个容器日志最多占约 200MB;exec-opts 里 cgroupdriver=systemd 让 docker 的 cgroup 驱动和 kubelet 保持一致,以后要在这个环境搭 Kubernetes,这个参数必须对上;live-restore 允许 docker daemon 重启时容器不停止,内网环境 docker 配置调整导致的重启很频繁,这个参数能少很多事。

改完必须重启 docker 才生效。重启前检查 JSON 语法,一个逗号错误就能让 dockerd 起不来:

python3 -m json.tool /etc/docker/daemon.json systemctl restart docker docker info | grep -E "Storage Driver|Cgroup Driver"

提示:daemon.json 配了># 跳板机执行 docker pull nginx:1.25 docker pull mysql:8.0 docker save -o images.tar nginx:1.25 mysql:8.0 gzip images.tar ls -lh images.tar.gz

docker save 可一次打包多个镜像,-o 指定输出。注意:镜像名必须带 tag,不带 tag 时 docker 默认打 latest,本地没有 latest 会直接报错。打包产物较大(nginx 一百多 MB,mysql 五百多 MB),所以 gzip 压缩。传到内网后解压再 load:

# 目标机执行 gunzip images.tar.gz docker load -i images.tar docker images

load 完 docker images 应看到 nginx:1.25 和 mysql:8.0。如果看到一堆 ,多半是 save 时用了镜像 ID 而不是 repo:tag。docker save 用 ID 打包的话,load 进去的镜像没有仓库名和标签,不重新打 tag 没法用。这个问题在避坑章再展开。

4.2 私有仓库:用 registry 容器搭内网镜像源

docker save/load 适合小规模。但内网多台机器要装同样镜像,或后续要更新镜像版本,save/load 效率太低——每台机器都要拷一遍 tar 包。这时候适合在内网搭一个私有仓库(registry),其他机器配好 insecure-registries 就能像外网一样 pull。registry 本身也是容器,先通过 save/load 把 registry 镜像弄进来。

# 跳板机 docker pull registry:2.8.3 docker save -o registry.tar registry:2.8.3 gzip registry.tar # 传输后,在仓库服务器上执行 docker load -i registry.tar.gz docker run -d --name registry \ -p 5000:5000 \ -v /opt/registry:/var/lib/registry \ --restart=always \ registry:2.8.3

registry 容器把镜像数据存在容器内 /var/lib/registry,通过 -v 挂载到宿主机 /opt/registry,方便备份迁移。-p 5000:5000 暴露 5000 端口,--restart=always 保证重启自动拉起。在内网环境里,这台 registry 就是所有容器的镜像源头,挂了内网全拉不了新镜像。跑起来后用 curl http://localhost:5000/v2/ 验证,返回一个空 JSON 对象 {} 就说明仓库正常。

仓库跑起来后,其他机器要拉镜像,需要配两件事:一是 daemon.json 里 insecure-registries 加上 192.168.1.100:5000,二是把镜像重新打上仓库地址的 tag。

# 已有镜像打上仓库前缀并推送 docker tag nginx:1.25 192.168.1.100:5000/nginx:1.25 docker push 192.168.1.100:5000/nginx:1.25 # 其他内网机器拉取 docker pull 192.168.1.100:5000/nginx:1.25

为什么用 HTTP 而不是 HTTPS:registry 默认走 TLS,但内网配证书比较麻烦,所以 daemon.json 用 insecure-registries 声明这个地址允许 HTTP 访问。这个配置只在受信任内网使用,放到公网就是裸奔——镜像数据明文传输,别人能直接拉走你的镜像内容。

4.3 用 docker-compose 编排内网服务:一个最小可用模板

镜像有了、仓库有了,最后是让服务真正跑起来。内网部署通常不是单容器,而是一组有依赖的服务,docker-compose 的价值就在这里:一个 yml 定义所有服务、网络、卷,一条 docker-compose up -d 拉起来,比一堆 docker run 好维护。

mkdir -p /data/apps/nginx-demo && cd /data/apps/nginx-demo cat > docker-compose.yml <<'EOF' version: '3.8' services: app: image: 192.168.1.100:5000/nginx:1.25 container_name: nginx-app restart: always ports: - "8080:80" volumes: - ./html:/usr/share/nginx/html environment: - TZ=Asia/Shanghai healthcheck: test: ["CMD", "curl", "-f", "http://localhost/"] interval: 30s timeout: 5s retries: 3 EOF docker-compose up -d

这个文件是我内网部署常用的最小模板。image 地址是私有仓库前缀,保证所有内网机从同一源拉镜像。比如要部署 kodbox 网盘或 mysql 8.0,把对应镜像推到仓库,目标机上改改 image 字段和端口映射即可。restart: always 保证节点重启后容器自动拉起;healthcheck 让 docker 定期探测容器内 HTTP 服务,失败自动重启。注意 healthcheck 里的 curl 得在镜像里存在,nginx 官方镜像自带,但很多精简镜像没有,会报 curl: not found,这时候换镜像或改成 wget 探测。

docker-compose up -d 后用 docker-compose ps 查看状态,STATUS 显示 Up 说明编排成功。显示 Restarting 多半是健康检查失败或入口命令问题,用 docker-compose logs app 看容器日志逐行排查,别只看 STATUS 就下结论。

5. 内网离线安装避坑:五个高频翻车点与排查记录

5.1 permission denied while trying to connect to the Docker API

现象:docker ps 报错 permission denied while trying to connect to the Docker API at unix:///var/run/docker.sock,加 sudo 才能执行。

原因:docker 客户端通过 /var/run/docker.sock 这个 unix socket 与 dockerd 通信,socket 属组是 docker,当前用户不在 docker 组里就没有读写权限。这不是离线安装特有,但内网很多机器直接拿 root 跑 docker,一旦切到普通用户就踩这个坑。

解决:把用户加入 docker 组,重新登录使组权限生效;测试机就直接用 root 操作:

sudo usermod -aG docker $USER newgrp docker docker ps

5.2 CentOS 7.9 内核 3.10 与新版 Docker 不兼容

现象:dockerd 启动成功,docker run 创建容器时报 error creating overlay mount to /var/lib/docker/overlay2/xxx: invalid argument,或者容器内 ifconfig 没有网卡。

原因:CentOS 7 的 3.10 内核较老,overlay2 存储驱动需要内核 overlayfs 支持,3.10 内核默认没编译进去;新版 docker 的 iptables 管理依赖 nf_tables,3.10 只支持老的 iptables 框架,两者相遇各种诡异问题。

解决:优先装 docker 20.10.x,它在 3.10 内核上经过多年验证;或者升级内核到 4.18+(elrepo 源)。我的建议是内网生产求稳,CentOS 7.9 配 docker 20.10.24 是很稳的组合,别一味追新。

# 检查 overlay 模块是否可用 lsmod | grep overlay modprobe overlay uname -r

5.3 docker load 后镜像全是

现象:docker load -i images.tar 执行成功,docker images 里出现一串 REPOSITORY 和 TAG 都是 的镜像。

原因:docker save 时用了镜像 ID 而不是 repo:tag,tar 包里的镜像没有仓库名和标签信息;另一种情况是 save 加了 --all 参数,把无 tag 的中间镜像层也打进包了。

解决:save 时写全 repo:tag,例如 docker save -o images.tar nginx:1.25;已 load 进去的 镜像,用 docker inspect 查原始 ID 后手动打 tag:

docker tag <镜像ID> 192.168.1.100:5000/nginx:1.25

5.4 docker-compose 命令找不到或 version 格式不支持

现象:执行 docker-compose up -d 报 ERROR: Version in './docker-compose.yml' is unsupported: 3.8;或者 sudo docker-compose 报 command not found。

原因:前者的根源是 docker-compose 太老,v1 的 python 版最多支持 compose file 3.7,遇到 3.8 就拒绝执行;后者是 sudo 的 secure_path 里没有 /usr/local/bin,sudo 环境里的 PATH 不包含这个目录。

解决:docker-compose 换 v2.20+ 独立二进制;命令找不到就把二进制放 /usr/bin,或者用 visudo 在 secure_path 里追加:

visudo # 找到 Defaults secure_path= 一行,在末尾追加 /usr/local/bin

5.5 dockerd 起不来:iptables 与 firewalld 冲突

现象:systemctl start docker 返回 failed,journalctl -u docker 里看到 Failed to start Docker Application Container Engine,再往下翻是 Error starting daemon: Error initializing network controller: iptables failed: No chain/target/match by that name。

原因:CentOS 7 的 firewalld 自带一套 iptables 规则,docker 要往 FORWARD 链加自己的规则,和 firewalld 的规则集冲突。内网机器很多人习惯开着 firewalld 不关,于是 docker 网络初始化就挂掉。

解决:内网环境直接停 firewalld,让 docker 自己管理 iptables:

systemctl stop firewalld systemctl disable firewalld systemctl restart docker docker info | grep -i iptables

注意,停 firewalld 意味着机器上没有额外的防火墙策略。内网环境网络隔离由交换机负责,问题不大;但机器有公网入口时先确认上层防火墙覆盖出入方向,别把唯一的防线拆了。

提示:以上五个坑是我在多个内网项目里反复遇到的。这些现象记下来后你会发现,看似环境问题,本质都是版本组合与配置声明问题——内网离线安装的思路就是先固定一个已知稳定的版本组合,再围绕它配置整个环境。

6. 验证与维护:让 Docker 在内网长期稳定运行

6.1 离线环境里怎么验证引擎与网络

内网没有外网,hello-world 那种在线验证方式用不了。我习惯用 busybox 代替:镜像只有几 MB,跳板机上提前 save 打包带过来,load 后跑两条命令,分别验证引擎和网络链路。

docker run --rm busybox:1.36 echo "docker ok" docker run --rm busybox:1.36 ping -c 2 192.168.1.1

能输出 docker ok、ping 有回包,说明引擎、容器运行时和网络桥接链路全通,比 hello-world 实用得多。

6.2 巡检三板斧与升级回滚

离线环境最怕磁盘被日志和镜像层堆满。我每周固定跑一次 docker system df 看磁盘占用分布,docker ps -a 看不健康容器,journalctl -u docker 看 daemon 日志异常。docker system prune -f 清构建缓存,日志超过百 MB 就检查 daemon.json 里 log-opts 是否生效。

内网升级 docker 比在线环境风险大,没有 yum 源可秒回滚。我的做法是升级前把当前版本二进制备份到 /opt/docker-backup,再解压新版本覆盖 /usr/bin,起不来就拷回去,三分钟恢复:

mkdir -p /opt/docker-backup cp /usr/bin/{dockerd,docker,containerd,runc,docker-proxy,ctr} /opt/docker-backup/ tar xzf docker-25.0.5.tgz && cp docker/* /usr/bin/ systemctl restart docker && docker version # 起不来就回滚 cp /opt/docker-backup/* /usr/bin/ && systemctl restart docker

这个备份→替换→验证→回滚的习惯救过我一次:某次在客户内网升级 docker 后容器网络全挂,现场没时间查原因,我直接 cp 备份回去、重启 daemon,三分钟内恢复服务。从那以后,我每次动 docker 二进制前,都强制把备份目录过一遍,再动刀。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询