直接开门见山,这篇博文写给想在 Ubuntu 上装好 Docker 和 Docker Compose 的人。不管你是在虚拟机里折腾,还是给云服务器做初始化,搜到这个标题大概率就是想少走弯路,把环境一次性配明白。我先说个结论:用官方 apt 源安装,把 docker-compose-plugin 一起装进去,是最省心的路线,稳、可控、后续升级也简单。至于网上那些一键脚本、二进制替换之类的方式,我会在文章里做对比,把各自适合的场景讲清楚。
我本人这两年在 Ubuntu 22.04 和 24.04 上都反复装过 Docker 环境,踩过不少坑,包括残留版本冲突、socket 权限、Compose 插件找不到之类的问题。这篇文章会把完整的安装步骤、参数背后的原理、以及一整套排错经验写出来,适合从没装过的新手,也适合想弄明白“为什么这么装”的进阶读者。
1. 装之前先做好三件事:版本确认、残留清理和原因分析
1.1 确定 Ubuntu 版本和内核架构
先说为什么不能跳过这一步。Docker 安装源是按 Ubuntu 版本代号区分的,不同代号对应不同仓库路径,写错了 apt 会直接报错找不到包。而且内核版本太老的话,容器运行时会出一些奇怪问题,光是排查就能耗掉你半天。
打开终端,依次执行这几条命令:
lsb_release -a cat /etc/os-release uname -r uname -m建议直接看/etc/os-release里的VERSION_CODENAME,因为配置 apt 仓库时要用到它。Ubuntu 22.04 对应 jammy,24.04 对应 noble。这个字段写错,后面几步全部白搭。
内核方面,20.04 以上的系统基本上都能流畅跑 docker-ce。如果你用的是老掉牙的 18.04,我建议先升级系统,而不是在这个旧版本上硬装,否则后续做容器存储驱动配置时会遇到一堆麻烦。很多人看到网上老教程提到 Ubuntu 版本,就直接照着抄,结果发现自己的 apt update 已经 404,这种情况真的很常见。
架构信息同样重要。绝大多数 PC 和云服务器是 amd64,也就是 x86_64,但 ARM 机器(比如树莓派、部分云 ARM 实例)需要把架构参数换成 arm64。这个在添加软件源时会用到。
1.2 清理可能存在的旧版 Docker
很多机器上其实已经装过 Docker,只是你忘了。比如之前用apt install docker.io装过,或者装过 docker-engine、containerd 之类的老组件。docker.io 是 Ubuntu 官方仓库维护的快照版本,和 Docker 官方发布的 docker-ce 是两条不同的发布线,两者共存时很容易出冲突。
先检查一下:
dpkg -l | grep -i docker如果有输出,建议做好备份后清理干净。除非你明确知道自己要保留旧环境,否则不要跳过清理步骤。我遇到过最典型的场景:机器上残留了 containerd 旧版本,新装 docker-ce 后一运行容器就报 shim task 创建失败,折腾很久才发现是两个运行时版本在打架。
清理命令:
sudo apt remove docker docker-engine docker.io containerd runc注意,这一步不会删除/var/lib/docker里的数据。如果你的旧环境里跑过容器、有过镜像和卷,重装后这些数据还在。对新机器来说这些都不用管,但如果你是从旧环境迁移,记得先把数据备份出来再动手。
1.3 为什么要用官方 apt 源而不用脚本一键装
Docker 官网给过一个快速安装脚本:
curl -fsSL https://get.docker.com | sh这个脚本确实方便,适合临时测试,我在一些一次性实验环境里也这么干过。但我个人不太推荐把它用在正式环境或者长期维护的机器上,原因有两点。
第一,脚本会直接拉取最新版,你很难锁定版本做升级管理。今天装的和三个月后同事装的,可能就差了版本。第二,脚本执行的内容不透明,万一团队有安全审计要求,你需要知道这台机器上每一步到底做了什么,脚本方式不好交代。
用 apt 源的好处在于:仓库里所有 docker-ce 版本都能查得到,可以固定主版本,跟随系统的 apt 升级节奏,还能配合 Ubuntu 的 unattended-upgrades 做自动安全更新。后面我会把 apt 源的配置过程一步步写出来。
提示:如果你只是想在本地快速跑个容器体验一下,脚本装也行。但如果你打算长期维护、后续要上项目,建议直接看第 2 章,用官方源来装。
2. 用官方 apt 源安装 Docker 引擎:一条命令装出标准环境
2.1 安装依赖工具
先确保curl、ca-certificates、gnupg这些基础工具存在。有很多精简版系统镜像里连 curl 都没有,直接跑后续命令会报 command not found。
sudo apt update sudo apt install -y ca-certificates curl gnupggnupg 的作用是处理签名密钥。Docker 的 apt 仓库带了 GPG 签名,如果没有 gnupg,后续 apt update 校验签名时会直接失败。
2.2 添加 Docker 官方 GPG 密钥
添加密钥这一步,不同教程的写法有些差异,早期教程喜欢把密钥写到/etc/apt/trusted.gpg.d/,但新的官方文档推荐写到/etc/apt/keyrings/下,独立目录,职责更清晰,也方便统一管理。
我建议用官方推荐的方式:
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解释一下这三条命令做了什么:install先生成目录并设置 0755 权限;curl把 Docker 的 GPG 公钥下载到指定位置;chmod a+r确保 apt 进程能读取这个文件。如果文件权限不对,apt update 会报错说 key 无法读取,指向的路径问题非常隐晦。
2.3 写入 apt 软件源
以 Ubuntu 24.04 为例,添加如下软件源:
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu noble stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null如果你的机器是 arm64 架构,把arch=amd64改成arch=arm64。如果是 22.04,把noble换成jammy。
添加完自定义源之后,必须让 apt 重新索引一次仓库元数据,否则 apt 感知不到新仓库里有哪些包:
sudo apt update这里如果报签名校验失败,基本可以确定是 2.2 节的路径或者权限没写对。可以重新执行一次 2.2 的三条命令,再跑一次 update。
2.4 一条命令安装 Docker 引擎、CLI 和容器运行时
这是核心安装命令:
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin很多人只装 docker-ce 和 docker-ce-cli,后面需要 Compose 时又发现没有,只能回来补装。我在这条命令里直接加了docker-compose-plugin,这样docker compose子命令开机即用,不用再单独折腾。
这几个包分别是什么:
docker-ce:Docker 守护进程本体,负责管理容器、镜像、网络。docker-ce-cli:命令行客户端,你敲的docker命令实际由它提供。containerd.io:容器运行时,真正把容器跑起来的底层组件。docker-buildx-plugin:构建镜像的扩展插件,支持多平台构建等高级特性。docker-compose-plugin:Compose v2 的官方插件,提供docker compose子命令。
2.5 验证安装结果
sudo systemctl status docker docker versionsystemctl status能看出服务有没有正常运行。docker version会同时显示 client 和 server 两部分的版本信息,如果只看到客户端版本而 server 部分连接不上,要么是服务没启动,要么是权限问题,下一章会详细讲。
到这里,Docker 引擎本身已经装好了。但先别急着把镜像拉起来,第 3 章要处理的是启动服务、开机自启、镜像加速这些第一次使用最容易卡住的地方。
3. 首次启动前的网络配置:镜像加速与开机自启
3.1 启动 Docker 并设置开机自启
安装完成后 Docker 服务不会自动启动,需要手动拉起:
sudo systemctl start docker sudo systemctl enable dockerenable这一步会把 docker 服务注册到 systemd 的开机启动项里。如果你在虚拟机或者云服务器上长时间使用,建议确认 enable 成功,否则重启机器之后 Docker 不会自己跑起来,服务就“失联”了。
也可以用一条命令完成启动和注册:
sudo systemctl enable --now docker3.2 配置 registry mirror 加速镜像拉取
Docker Hub 的访问速度在不同网络环境下差别很大,拉大镜像时经常超时重试。最直接的解决办法是配置 registry mirror,也就是镜像加速器。这个配置写在/etc/docker/daemon.json里。
先检查文件是否存在:
sudo ls -l /etc/docker/daemon.json如果不存在,手动创建并写入配置:
sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://docker.m.daocloud.io"] } EOF我这里写的是一个示例加速地址,实际选择请根据你所在网络环境的可用性调整。配好之后重启 Docker 让配置生效:
sudo systemctl daemon-reload sudo systemctl restart docker需要说清楚的是,registry mirror 的本质是给 Docker 拉镜像时提供一条代理路径,它不改变你指定镜像地址的行为,只是拉取的时候更快更稳。对个人项目和中小团队来说,配置一个可用的加速地址基本够用了。
3.3 跑第一个容器试试水
sudo docker run hello-world这个 hello-world 镜像很小,拉取成功后终端会打印一段说明文字,代表 Docker 引擎工作正常。注意这里我用了sudo。如果你的普通用户没加入 docker 组,不加 sudo 大概率会遇到 permission denied 的报错,这个坑在第 5 章会专门讲。
如果 hello-world 拉取特别慢,优先确认 registry mirror 是否生效,可以查一下当前生效的镜像配置:
sudo docker info --format '{{json .RegistryConfig.Mirrors}}'输出里能看到你配置的加速地址,说明生效了。如果输出是空数组,说明配置没加载进去,检查一下 daemon.json 的格式,sudo systemctl restart docker之后再看一次。
4. Docker Compose v2 的三种安装方式:我最终选了官方插件方案
4.1 三种方式横向对比
先做一个对比,方便你根据自己的情况一步到位选择:
| 方式 | 命令示例 | 版本 | 维护方式 | 适用场景 |
|---|---|---|---|---|
| 官方插件 apt 安装 | apt install docker-compose-plugin | v2 + 跟随系统源更新 | 系统包管理器 | 最推荐,生产环境首选 |
| 静态二进制 | 下载 docker-compose-linux-x86_64 文件 | v2 单文件 | 手动覆盖文件 | 离线环境、定制版本 |
| Python pip | pip install docker-compose | v1/v2 混杂 | 依赖 Python 环境 | 老项目兼容,新项目不推荐 |
v1 时代的docker-compose命令用 Python 写的,功能能用但启动慢、日志格式一般,而且 Python 版本一变就影响使用。v2 用 Go 重写,作为 Docker CLI 的插件形式存在,启动速度、输出体验都好很多。所以从 2023 年到现在,新项目基本都统一用docker compose,也就是空格分隔的子命令格式。
如果你搜“docker-compose 安装”,网上能翻出大量 v1 年代的教程,They will guide you to pip install docker-compose or download docker-compose binary. 这些方式倒不是说不能用,但放到今天的 Docker 生态里确实有些过时。
4.2 如果你已经通过 apt 源安装
如果照着第 2 节的命令安装,docker-compose-plugin已经在了,直接验证:
docker compose version输出类似Docker Compose version v2.27.1。只要看到这个,后续docker compose up、docker compose ps这些命令都能直接跑。
4.3 离线安装 Docker Compose 的完整步骤
很多内网机器既没有外网,也不能直接使用 apt 源,这时静态二进制方案最省心。
先确认架构:
uname -mx86_64 对应 amd64,aarch64 对应 arm64。然后在一台可以访问外网的机器上,从 GitHub Releases 页面下载对应版本的docker-compose-linux-x86_64或docker-compose-linux-aarch64文件。
把文件传到目标机器后,执行:
sudo mkdir -p /usr/local/lib/docker/cli-plugins sudo mv docker-compose-linux-x86_64 /usr/local/lib/docker/cli-plugins/docker-compose sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose注意文件名必须叫docker-compose,目录必须位于 Docker 查找 CLI 插件的路径中。我之前见过有人把文件移到/usr/local/bin下,结果docker compose依然提示找不到命令,因为 Docker 插件机制不扫描/usr/local/bin。
最后验证:
docker compose version这里有个细节值得说。Docker CLI 查找 compose 插件的路径一共有三处:/usr/local/lib/docker/cli-plugins、/usr/share/docker/cli-plugins、以及用户目录下的~/.docker/cli-plugins。我推荐使用/usr/local/lib/docker/cli-plugins,因为它在系统级,不依赖特定用户的家目录是否存在。
4.4 为什么我的选择是官方插件
我的倾向很明确:能用 apt 源安装的地方,不要手动下载二进制。原因不是二进制方式不能用,而是升级成本完全不同。
apt 安装的插件会跟随系统的apt upgrade一起更新,你不必记得每台机器上装了哪个版本的 compose,系统统一管理。手动方式虽然也能用,但每次升级都要重新下载、覆盖、再次确认,机器一多就乱了。
不过,如果目标机器完全离线,静态二进制几乎是唯一稳妥方案。它的优点是文件独立、不依赖 Python 运行时,也不依赖系统 libssl 的动态版本,拷进去就能用。
5. 安装完成后最容易踩的五个坑:权限、PATH 与服务状态
5.1 没有把用户加入 docker 组导致权限不足
这是我最常被问到的问题。Docker 的 socket 文件/var/run/docker.sock默认属于 root 和 docker 组,普通用户直接执行docker ps会看到 permission denied 的报错。解决办法是把当前用户加入 docker 组:
sudo usermod -aG docker $USER newgrp dockernewgrp可以让当前终端立即生效,不用退出重登。如果下次登录还是报权限问题,检查一下用户组是否真的加上去了:
id $USER确认输出里有 docker 字样。没有的话说明 usermod 没生效或当前登录的会话没刷新。
这里必须提醒一句:加入 docker 组等同于拥有 root 权限,因为容器可以被映射为 root。生产环境给别人开通权限时,一定要谨慎使用,最好通过 sudo 做收口管理。
5.2 服务起不来时看的几个指标
执行docker ps时提示:
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?按顺序排查:
systemctl status docker看服务当前状态journalctl -u docker --no-pager -n 50看日志尾部ls -l /var/run/docker.sock看 socket 文件是否存在
多数情况是服务没启动或者启动过程中崩溃。日志里出现 failed to start daemon 之类的关键词时,多半是 daemon.json 写错了。这时候先把配置文件恢复默认:
sudo mv /etc/docker/daemon.json /etc/docker/daemon.json.bak sudo systemctl restart docker等基础功能确认正常之后,再小心地把镜像加速配置补回来,一次只改一个变量,这是排错的基本原则。
5.3 旧版本残留导致 containerd 冲突
如果你之前手动装过旧版 docker-engine,或者系统里有 containerd 残留,新装 docker-ce 后运行容器可能报:
failed to create shim task: OCI runtime create failed这个报错和 OCI runtime 有关,本质上是 containerd 或 runc 版本冲突。我会先检查系统里有没有多个 containerd 或 runc:
which containerd which runc dpkg -l | grep containerd dpkg -l | grep runc如果 apt 安装的 containerd.io 和别的路径下旧文件共存,建议移除旧文件,只保留系统包管理的版本。老版本 runc 对 cgroup v2 的支持不完整,而 Ubuntu 22.04 及以上默认使用 cgroup v2,所以表现尤其隐蔽。
5.4 PATH 和命令调用方式混乱
一个很常见的状态就是:网上教程一半写docker-compose(带横杠),一半写docker compose(带空格),结果你机器上装的可能只有其中一种。
我用一张表总结两者的关系:
| 命令形式 | 对应版本 | 安装来源 |
|---|---|---|
docker compose | v2 插件 | docker-compose-plugin 或静态插件 |
docker-compose | v1 / v2 独立可执行文件 | pip 或手动移动二进制 |
如果你习惯写docker-compose,但机器上没这个命令,就先确认docker compose是否可用。想兼容两种写法也可以做软链:
sudo ln -s /usr/local/lib/docker/cli-plugins/docker-compose /usr/local/bin/docker-compose不过这种方案我不太推荐用于生产环境。团队里统一使用docker compose,要比每个人各写各的好维护得多。毕竟新版本的 compose 功能迭代都集中在 v2 插件上,独立可执行文件更像历史遗留。
5.5 启动服务之后容器网络异常
这个问题不会在安装阶段出现,但第一次实际部署项目时特别容易遇到。容器起来了,宿主机却访问不了服务端口;或者容器能通外网,但容器之间互相访问不通。
先检查端口映射是否生效:
docker ps docker port 容器名再用 iptables 确认 Docker 的 NAT 链路是否正常:
sudo iptables -t nat -L -n | grep -i docker如果 NAT 链路被清空,重启 Docker 服务会自动重建。但如果你同时用了 firewalld 或自写防火墙脚本,重启顺序不同可能影响规则加载顺序。一般情况下,先起 docker 服务,再加载防火墙规则,顺序比较稳。
6. 用一个真实 Compose 项目验证安装:从空目录到服务跑通
6.1 搭建一个极简的 Nginx + Redis 场景
验证完 Docker 引擎基础功能之后,我习惯用 Compose 起一个真实服务来确认插件工作正常。这里用 Nginx 和 Redis 做最小组合,镜像体积小,启动快,覆盖了“Web 服务 + 有状态服务”两个常见类型。
先建目录和文件:
mkdir -p ~/compose-demo && cd ~/compose-demo touch docker-compose.yml写入如下内容:
services: web: image: nginx:1.27-alpine ports: - "8080:80" restart: unless-stopped cache: image: redis:7.2-alpine ports: - "6379:6379" restart: unless-stopped这个文件的语法是 Compose v2 的标准写法。注意services是顶层键,v1 时需要写 version 字段,v2 已经不需要了,新版 compose 会自动判断格式。如果你抄网上老教程,看到最顶上写着version: '3',在新版本中可以直接不写,不影响使用。
6.2 后台启动并观察日志
docker compose up -d第一次执行会拉取两个镜像,期间输出会滚动显示每个镜像层的下载进度。拉完之后:
docker compose ps看到两个服务都是 running 状态,说明环境没问题。再验证一下网页可以直接访问:
curl -I http://localhost:8080返回 200 OK 就说明 Nginx 已经在容器里跑起来了。
查看日志用:
docker compose logs -f-f是 follow 模式,实时滚动输出日志,调试非常方便。按 Ctrl+C 退出日志查看,不会影响容器运行。
6.3 验证配置变更和重建流程
实际项目中经常会改配置,比如改端口、改环境变量。改完docker-compose.yml之后,不需要手动停掉整个项目再启动,直接执行:
docker compose up -dCompose 会自动对比当前配置和运行中的容器配置,发现有变化就只重建变化的那几个服务。这是 Compose 相比手动docker run的核心优势,环境配置变成了一个可以版本化的文件。
如果需要彻底停止并清理容器和网络:
docker compose down如果连数据卷一起清理,加上-v参数:
docker compose down -v注意:
-v会把服务定义里挂载的匿名卷一并删除。我在测试环境里不小心删过数据,虽然不是生产库,但也够让人心跳加速的。这条命令务必在确认不持有重要数据的情况下才执行。
6.4 把 Compose 项目加入开机自启
Compose 项目本身不直接支持开机自启,但它依赖的 docker 服务会随 systemd 启动。一个常见做法是给 compose 项目写一个 systemd unit,更简单的做法是使用restart: unless-stopped让容器在 docker 服务拉起后自动恢复。
restart策略有几种:
| 策略 | 行为 |
|---|---|
no | 不自动重启 |
always | 无论怎么退出都会重启 |
unless-stopped | 除非手动停止,否则开机自动拉起 |
on-failure | 只在异常退出时重启 |
我一般用unless-stopped,因为它能区分“手动停止”和“异常退出”,比always更贴近日常运维习惯。比如你临时想停掉某个服务,用docker compose stop手动停止后,机器重启时它不会自己又拉起来;但如果是程序崩溃退出,docker 会尝试重新拉起。
6.5 验证完之后的体感
整个流程走完,其实就证明 Docker 引擎、CLI 和 Compose 插件都已经正常工作了。之后你再部署任何项目,只需要拿到对应的docker-compose.yml,在机器上执行docker compose up -d,服务就跑起来了。这个体验和手动一条条敲docker run完全不是一个层级。
我在实际使用中还有一个习惯:把第 2 节的安装命令固化成setup-docker.sh脚本,放到团队的初始化文档里。新机器来了直接跑一遍,不用每次回忆步骤,也不会漏掉插件。装完后顺手执行两条验证命令——docker compose version和docker run --rm hello-world,一个验证插件,一个验证引擎,两步都过了,环境基本不会有坑了。