搞Linux服务器的人,十有八九都绕不开Docker。这东西把一个应用连同它的运行环境一起打包,分发、部署、隔离都省事,特别适合快速交付和测试环境复用。最近我又在一台Ubuntu 22.04上重新折腾了一遍Docker安装流程,把踩过的坑和验证过的命令整理出来,从环境检查、官方仓库安装,到装完之后的权限、加速、自启配置,再到常见的报错排查,一条线捋清楚。这篇文章适合刚接触Docker的小白,也适合需要在Ubuntu上快速装好Docker环境的运维和开发同学,照着操作基本能一次跑通。
1. 环境检查与安装方案选型
1.1 为什么我不建议直接装 docker.io
很多朋友刚上手时图省事,直接执行sudo apt install docker.io,一条命令就把Docker装好了。这个包的来源是Ubuntu官方仓库,本质上是把Docker社区的二进制包重新打包维护,确实能跑,但我自己用下来发现两个问题:一是版本更新明显滞后,Docker官方已经更新到大版本了,Ubuntu仓库里可能还停留在半年前甚至更早的版本;二是docker.io在升级节奏上跟官方主线的 API 兼容性偶尔会有偏差,比如新版的docker compose子命令、Buildx 插件,在老仓库包里要么没有,要么行为不一致,排查起来很头疼。
如果只是临时在本地跑一个容器实验,用docker.io没问题;但如果你打算长期维护、参与开源项目、做 CI/CD 流水线,或者需要跟团队其他人保持环境一致,我强烈建议安装 Docker 官方维护的 Docker Engine(也就是大家常说的 Docker CE)。它的更新路径清晰,插件机制完整,遇到问题还能直接查官方文档,社区方案也都是基于这套环境来写的。
1.2 确认系统版本与前置条件
安装前我会先确认三样东西:系统版本、系统架构、内核版本。
# 查看 Ubuntu 版本 lsb_release -a # 查看系统架构 dpkg --print-architecture # 查看内核版本 uname -rDocker Engine 对 Ubuntu 的官方支持范围是 LTS 版本,比如 20.04、22.04、24.04,非 LTS 版本能用但出了问题官方不会优先处理,我自己也只在 LTS 上跑。架构方面,x86_64和arm64是主流,如果你用的是树莓派这类 ARM 设备,把架构列出来很关键,后面换源的时候会用到。
内核版本方面,官方要求 3.10 以上,实际现在 Ubuntu 20.04 以后的内核基本都是 5.x,完全满足需求。我习惯顺手看一眼/proc/version,确认当前内核确实正常加载,这一步虽然大多数时候没问题,但能帮你排除掉那些“换了内核没重启”的隐藏问题。
1.3 三条安装路线的取舍
Ubuntu 上装 Docker 大致有三条路,我分别试过,使用场景不太一样:
| 安装方式 | 操作难度 | 适用场景 | 缺点 |
|---|---|---|---|
| 官方 apt 仓库安装 | 中等 | 生产环境、长期维护 | 需要手动配置 GPG 密钥和源 |
| 官方便捷脚本 | 最简单 | 临时测试、快速验证 | 脚本升级频率高,不易审计 |
| 离线 deb 包安装 | 较复杂 | 内网隔离环境 | 需要手动处理依赖关系 |
官方便捷脚本就是执行curl -fsSL https://get.docker.com | sh,一条命令搞定,但我个人不太推荐在生产环境里直接这么干。管道执行远程脚本属于“看完再执行”的典型反面教材,脚本每次更新你都不知道它额外改了哪些系统配置,出了问题很难定位。所以我下面的完整流程会以“官方 apt 仓库安装”为主线,把每一步都拆开讲清楚,这样出了问题你知道该查哪里。
2. 使用官方仓库完整安装 Docker Engine
2.1 清理历史残留
如果你之前用docker.io或脚本装过 Docker,建议先清理干净,避免后续产生冲突。
# 卸载可能存在的旧版本 sudo apt-get remove docker docker-engine docker.io containerd runc这一步需要注意:卸载包不会自动删除/var/lib/docker目录,里面存着你的镜像、容器、卷。如果你是有意重新安装,想要保留这些数据,卸载时不要手动删这个目录;如果是彻底重来,等卸载完成后可以再执行sudo rm -rf /var/lib/docker把它清掉。我遇到过不止一次,旧版 docker 卸载不干净,导致新版安装后启动时报iptables failed,所以这个清理动作别省。
2.2 配置 GPG 密钥与 apt 源
先用旧版方式的人容易在这里踩坑。以前官方文档让你用apt-key add导入密钥,新版本已经明确不推荐了,改用把公钥文件放到/etc/apt/keyrings目录下,然后通过signed-by参数指定,更安全也更好维护。
# 安装必要依赖 sudo apt-get update sudo apt-get install -y ca-certificates curl # 创建密钥目录 sudo install -m 0755 -d /etc/apt/keyrings # 下载 Docker 官方 GPG 密钥 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这里有一点我特别想强调:密钥文件的路径和名称影响着后续能否正常使用。22.04 和 24.04 较新版本的官方文档用的是/etc/apt/keyrings/docker.asc,而更早的文档里写的是docker.gpg,两者都能用,但你的sources.list配置必须跟文件名对应上,不然apt update会报公钥错误。
接下来把 Docker 仓库写进 apt 源列表:
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这条命令里的$(. /etc/os-release && echo "$VERSION_CODENAME")会自动读取当前系统的代号,比如jammy(22.04)或noble(24.04),这样你就不用手动去改版本号了,换机器部署的时候特别方便。
添加完源之后跑一遍更新:
sudo apt-get update正常的话,你会在输出里看到Get:... https://download.docker.com/linux/ubuntu jammy stable这样的字样,说明源已经生效。
2.3 执行安装并验证
源弄好之后就是安装本体,一条命令装齐运行时和常用插件:
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin这四个包的分工我说一下:
docker-ce:Docker 守护进程和服务管理文件,核心中的核心。docker-ce-cli:docker命令行工具,你敲的每个docker命令都靠它。containerd.io:容器运行时,负责真正地把容器跑起来,Docker 通过它来管理容器生命周期。早期版本里 Docker 直接管运行时,后来拆出去独立成 containerd,系统里如果没有它会直接启动失败。docker-buildx-plugin和docker-compose-plugin:镜像构建插件和 Compose 插件,新版 Docker 已经把这两块做成插件体系了,一次性装齐能省很多后面的事。
安装完成先启动服务,再跑官方测试镜像:
sudo systemctl start docker sudo systemctl enable docker sudo docker run hello-worldhello-world是个极小的测试镜像,如果看到Hello from Docker!那段输出,就说明 Docker 已经能正常拉取镜像并把容器跑起来了,整个安装链路验证通过。
2.4 新手必看:验证失败怎么判断原因
hello-world跑不起来时,不要慌,先分方向排查。最常见的三类情况是:
第一种,输出里提示Unable to find image 'hello-world:latest' locally,然后卡住不动或显示网络错误。这通常是拉取镜像的网络链路有问题,原因可能是服务器到镜像仓库的连接不稳定。这种时候先重试两次,问题还在的话就在/etc/docker/daemon.json里配置一个可用的镜像加速源,具体配置我在后面第 3 章专门讲。这里注意,hello-world虽然很小,但也要走一次镜像拉取流程,网络问题会直接暴露出来。
第二种,提示权限不足Got permission denied while trying to connect to the Docker daemon socket。这是因为你当前用户不在docker用户组里,需要用sudo执行,或者按照下一章的配置把当前用户加进组里。
第三种,提示Cannot connect to the Docker daemon。这个通常是服务本身没起来,先systemctl status docker看守护进程状态,再用journalctl -u docker看最近的日志。大多数情况是配置文件写坏了,或者端口被占用。
3. 装完必做的四件小事
3.1 配置镜像加速
很多人装完 Docker 兴冲冲去拉镜像,结果发现速度很慢,甚至直接拉不动。这跟网络链路有关,解决方案不是去改什么终端代理,而是给 Docker 配置一个镜像加速器,让它通过更近的节点拉取公共镜像。
操作很简单,先创建或修改 Docker 的配置文件:
sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://你的加速器地址"] } EOF加速器地址怎么找?阿里云和腾讯云都提供容器镜像服务,登录控制台就能看到专属的加速器地址。拿到地址后替换到上面的配置里,然后重启 Docker:
sudo systemctl daemon-reload sudo systemctl restart docker重新跑一次docker run hello-world,你会发现镜像拉取速度快了很多。需要提醒的是,加速器只影响 Docker Hub 上的公共镜像,如果你用的是私有的镜像仓库,配置是另外一套,别混淆。
3.2 让当前用户免 sudo 使用 docker
装完 Docker 后,每次执行docker命令都要带sudo,用久了真的很烦。解决方法是把当前用户加入docker用户组:
sudo usermod -aG docker $USER newgrp dockernewgrp docker是让当前终端立即生效,不用注销重登。如果你在 SSH 会话里操作,执行完这行命令后后续命令就不需要sudo了。
这里我必须负责任地说清楚:加入docker用户组等价于获得 root 权限。因为 Docker 守护进程是 root 权限运行的,能操作 Docker 基本就能以 root 身份挂载宿主机目录、执行容器命令。所以这个操作只推荐在单用户开发机上做。如果你是多人共用服务器,或者环境里有特权账户管理要求,让每个人自己加组前要想清楚风险。安全意识强的话,也可以继续用sudo docker,无非多敲几个字母。
3.3 开启开机自启与常用命令速查
咱们第 2 章里已经执行过sudo systemctl enable docker,这时开机自启其实是已经打开的。那为什么还要单独提?因为我发现很多人装完 Docker 直接忽略了这步,结果服务器重启后 Docker 服务没起来,业务容器全部挂掉,排查了半天才发现是服务没启动。
确认一下自启状态:
systemctl is-enabled docker输出enabled就说明没问题。再看一眼服务运行状态:
systemctl status docker看到active (running)就放心。最后我把平时用得最多的命令整理成一张速查表,刚入门的朋友直接照着抄就行:
| 操作 | 命令 |
|---|---|
| 查看容器列表 | docker ps -a |
| 启动容器 | docker start 容器名或ID |
| 停止容器 | docker stop 容器名或ID |
| 进入容器内部 | docker exec -it 容器名 /bin/bash |
| 查看日志 | docker logs -f 容器名 |
| 拉取镜像 | docker pull 镜像名:标签 |
| 查看所有本地镜像 | docker images |
| 删除无用镜像 | docker image prune |
刚开始不需要死记硬背,把ps、logs、exec这三个用熟,日常就够用了。
3.4 顺手装好 Docker Compose 插件
上一章的安装命令里已经带了docker-compose-plugin,所以你的环境里应该已经有了docker compose命令。验证一下:
docker compose version在比较老的环境里,你可能会遇到只有docker-compose(带横杠)的情况,那是独立的 Python 版 Compose,已经停止维护了。新版统一用docker compose,注意这是两个不同工具,别混用。
为什么要强调 Compose?因为单容器场景docker run就够了,但一旦涉及多个服务之间的配合,比如 Web 应用加 MySQL 加 Redis,用 Compose 写一个 YAML 文件就能把整个环境定义清楚,一条docker compose up -d全部拉起,比手动执行一堆docker run靠谱得多,也方便版本管理。
这里给个最简单的示例,你在项目目录下创建compose.yaml:
services: nginx: image: nginx:stable-alpine ports: - "8080:80"然后执行:
docker compose up -d浏览器访问http://服务器IP:8080就能看到 Nginx 欢迎页。这就是 Compose 的基本玩法,写多服务时再加几个services下的段落就行。
4. 安装踩坑实录与排查速查
4.1 添加源后 update 报错或拉取 GPG 密钥超时
我自己的踩坑记录里,apt-get update阶段出问题最让人恼火,因为这时你还没机会装 Docker,容易怀疑是不是源地址输错了。
最常见的是添加了 Docker 官方源之后,apt update卡在Get:... download.docker.com这一步,然后超时。除了网络链路本身的原因外,还有可能是系统里残留了旧的 Docker 源配置。比如某些教程让你把源写进/etc/apt/sources.list而不是单独放在/etc/apt/sources.list.d/docker.list,两者共存时 apt 会去读取两次,内容冲突就会出现奇怪问题。
排查思路是这样:先grep -r docker /etc/apt/sources.list /etc/apt/sources.list.d/看有没有重复配置,有的话删掉多余的;然后确认 GPG 密钥文件路径和signed-by参数一致;最后再看daemon.json里有没有不小心配置了跟仓库源相关的错误参数。把这三处捋顺,绝大多数 update 问题都能解决。
另外还有一个高频坑是密钥过期或下载不完整。解决方式很简单,重新把 GPG 密钥下载一遍覆盖到原路径:
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 sudo apt-get update4.2 “Package 'docker-ce' has no installation candidate”
这个报错我见过不少次,字面意思是“找不到 docker-ce 这个包”,但实际原因通常不是包不存在,而是源没有真正生效,或者源地址跟系统版本不匹配。
排查步骤我建议按顺序来:
# 1. 确认 docker.list 文件存在且内容正确 cat /etc/apt/sources.list.d/docker.list # 2. 确认 apt 能读到该源 sudo apt-get update # 3. 搜索 docker-ce 包 apt-cache policy docker-ce如果apt-cache policy docker-ce输出里没有候选版本,说明 apt 根本没把这个源里符合条件的包暴露出来。这时重点检查deb [arch=... signed-by=...]这一段,arch是否跟你dpkg --print-architecture输出的架构一致,signed-by是否指向真实存在的密钥文件。
还有一种情况是系统版本不匹配,比如你在 Ubuntu 的某个非 LTS 测试版上装,VERSION_CODENAME取到的代号在 Docker 官方源里没有对应的稳定仓库,自然找不到包。这种情况下要么换回 LTS 版本,要么手动把代号改成相近的 LTS 代号,但后者可能引入依赖兼容问题,我不太推荐。
4.3 “Cannot connect to the Docker daemon”
这个报错的完整提示一般是Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?,一句“is the docker daemon running”其实已经指出了方向。
先检查服务状态:
sudo systemctl status docker如果服务没起来,看日志精确定位:
sudo journalctl -u docker --no-pager -n 50根据我的经验,服务起不来最常见的原因是/etc/docker/daemon.json配置写坏了。比如registry-mirrors地址格式不对、JSON 语法少了个逗号,Docker 守护进程一启动读取配置就崩。这种时候先把配置文件备份,然后临时移走再重启:
sudo mv /etc/docker/daemon.json /etc/docker/daemon.json.bak sudo systemctl restart docker服务能起来说明问题就出在这个文件上,再小心翼翼地改配置。改动配置的原则是“一次只改一处,改完就重启验证”,不要一次性堆很多不确定的选项上去,否则报错了都不知道是哪行的问题。
4.4 权限不足“Got permission denied”
这个报错我前面提过,典型提示是:
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock原因就是当前用户不在docker组里,Docker 守护进程通过 Unix socket 跟客户端通信,而这个 socket 默认只允许root和docker组的用户访问。
最简单的解决方式我已经在上文写过了,执行sudo usermod -aG docker $USER然后重新登录。但要注意,某些发行版对 docker 组名的处理不太一样,如果usermod报错提示组不存在,可以手动创建:
sudo groupadd docker sudo usermod -aG docker $USER另外,如果你在脚本或 CI 里遇到这个报错,不要让脚本去自动加组,直接在命令前加sudo,或者提前把执行用户配置好,避免权限扩散。
4.5 WSL2 环境下的特殊注意事项
现在很多开发者在 Windows 上用 WSL2 跑 Ubuntu 做开发,装 Docker 的方式跟纯 Linux 服务器基本一致,但有几点差异需要注意。
首先,安装前确认 WSL2 已经启用。在 PowerShell 里执行wsl --status,如果输出显示默认版本是 2,说明 WSL2 正常。如果还在用 WSL1,Docker 是跑不起来的,因为 WSL1 的架构跟 Docker 需要的 Linux 内核特性不匹配,会直接报发行版错误。
其次,在 WSL2 里我给的建议是直接安装 Docker Engine,不要装 Docker Desktop 的 WSL 后端。因为 WSL2 本身已经是一个完整的 Linux 虚拟机,直接在里面装 Docker Engine,配合 VS Code 的 Dev Containers 扩展,开发体验已经很舒服,没必要再叠一层 Docker Desktop,白白多占内存。
第三点,WSL2 的内存分配默认是宿主机的一半,如果跑多个容器导致内存不够,可以在%UserProfile%/.wslconfig文件里限制:
[wsl2] memory=6GB改完执行wsl --shutdown再重启 WSL 让配置生效。这个文件是 Windows 侧的全局配置,对里面所有 WSL 发行版生效,注意别一下给太多了,要给宿主机留足空间。
4.6 常见报错速查表
把上面这些坑汇总成一张表,方便大家直接对照排查:
| 报错信息 | 常见原因 | 解决方案 |
|---|---|---|
apt-get update卡在 docker 官方源 | 网络链路问题或重复源配置 | 检查 sources.list 文件,重试或换网络 |
Package 'docker-ce' has no installation candidate | 仓库源未生效、架构或版本代号不对 | 检查 docker.list 和 GPG 密钥路径 |
Cannot connect to the Docker daemon | 服务未启动或 daemon.json 错误 | journalctl -u docker查看日志并修正配置 |
Got permission denied | 当前用户不在 docker 组 | sudo usermod -aG docker $USER后重新登录 |
iptables failed | 旧版残留或网络配置冲突 | 清理旧包,重启 docker 服务 |
| 镜像拉取超时 | 网络链路到 Docker Hub 不稳定 | 配置镜像加速器 |
5. 卸载与升级时容易忽略的坑
5.1 彻底卸载干净
卸载 Docker 不是直接apt remove就完事了,尤其当你后面还想重装时,残留的配置和数据会让你一头雾水。
完整的清理流程是:
# 卸载 Docker 相关软件包 sudo apt-get purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # 删除镜像、容器、卷等数据 sudo rm -rf /var/lib/docker sudo rm -rf /var/lib/containerdpurge会连配置文件一起删掉,如果只想保留数据只留软件,用apt-get remove就行,但一般卸载都是为了重来或者彻底清掉,用purge更干净。另外/etc/docker/daemon.json、/etc/apt/keyrings/docker.asc、/etc/apt/sources.list.d/docker.list这几个位置经常被忽略,重装后残留的旧配置和旧密钥会干扰新环境,建议卸载时一起手动清理掉。
需要注意的是,rm -rf /var/lib/docker会把所有本地镜像、容器、网络配置和数据卷一并清除,执行前务必确认你的数据已经有别的备份路径。我见过有人卸载时顺手把别人数据库容器的数据卷也删了,那真是哭都来不及。
5.2 升级与版本锁定
日常升级 Docker 非常简单:
sudo apt-get update sudo apt-get install --only-upgrade docker-ce docker-ce-cli containerd.io但“简单”不代表“安全”。Docker 升级时守护进程会重启,这意味着跑在上面的所有容器都会中断。如果你的服务是restart: always策略,升级后容器会自动拉起来,但短暂停机难以避免。生产环境升级前,我建议先把容器数据卷备份一份,或者在业务低峰期操作。
还有一类场景是需要锁定版本,比如线上环境为了稳定性不想让 Docker 跟着全量升级跑。这时用apt-mark hold把不需要升级的包锁住:
sudo apt-mark hold docker-ce docker-ce-cli containerd.io之后apt upgrade就不会动这几个包。后面确定要升了再取消锁定:
sudo apt-mark unhold docker-ce docker-ce-cli containerd.io版本锁定有个副作用:Docker 官方源里的包是滚动更新的,你长期不升级可能会错过安全补丁。所以锁版本这个操作适合短期紧耦合的联调窗口,不适合长期生产环境,生产环境还是要建立定期升级和回滚预案。
再提一个容易被忽略的细节:升级完 Docker 后,老版本创建的容器网络和卷通常能兼容,但如果你用到了一些较新的特性,比如特定版本的 Buildx 插件,跨大版本升级后可能行为不一致。升级完我会习惯性跑一遍docker run --rm hello-world和docker build --help,确认运行时和构建链路都没问题再继续干活。
最后分享点个人的实际体会。Ubuntu 上装 Docker 这件事本身不难,跟着官方文档一步步来基本不会错,真正容易出问题的永远在“你以为装完就没事了”的那段:权限没配好、加速没设置、服务没自启、升级没规划。我后来每装一台新机器,都会把第 3 章那四件事当成固定流程走一遍,宁可多花五分钟,也别等到半夜服务挂了再排查。如果你也想给常用镜像扩展点玩法,装好 Docker 后可以试试用 Compose 一键部署全家桶环境,比如把 Nginx、MySQL、Redis 写在同一个 compose 文件里,一条命令建立起本地开发环境,这个体验用过就回不去了。