1. 装之前先把这三个前提确认好,别让系统环境坑了你
网上Ubuntu安装Docker的教程一抓一大把,官方文档也写得清清楚楚,但实际操作中翻车的概率依然不低。我见过太多人照着官方命令敲了一遍,最后卡在各种莫名其妙的问题上:装是装上了,但docker run一执行就报错;或者镜像pull到一半直接超时;更常见的是装完发现apt源损坏、内核太老导致容器无法运行。
先说结论:绝大多数安装失败,不是Docker本身的问题,而是装之前该确认的事情没确认。这里我把最容易踩坑的三个前提放在最前面讲。
1.1 内核版本:5.x以下建议先升级
Docker对Linux内核有硬性要求,尤其是OverlayFS存储驱动和网络命名空间这些核心能力,依赖内核版本必须达到4.0以上,实际生产环境里最好在5.0以上。
Ubuntu 20.04默认内核版本是5.4+,正常来说是满足要求的。但如果你是从16.04或18.04升级上来的老系统,或者用的是某些定制内核,版本可能还停留在4.15甚至更低。
检查方法很简单:
uname -r如果输出的版本号低于5.0,建议先升级内核再装Docker。虽然老内核不一定装不上,但容器网络、存储驱动方面会有一堆细碎的兼容问题,排查起来非常折磨人。与其后面纠结,不如一开始就补齐。
sudo apt update sudo apt install linux-generic sudo reboot顺便提一句,很多低版本内核的机器装完Docker后,执行docker info会出现:
WARNING: No swap limit support这个警告虽然不影响基本使用,但说明内核的cgroup内存限制功能没开启。如果之后要用--memory参数限制容器内存,就会失效。这也是老内核的一个典型坑。
1.2 残留的旧版本Docker:先清干净再装新的
如果你这台机器之前装过Docker,或者用的是别的容器运行时(比如containerd、podman),一定要先彻底卸载干净。这里说的“彻底”,不只是apt remove docker.io这么简单。
旧版Docker(尤其是用apt直接装的docker.io)会留下一堆配置文件和服务残留。最典型的坑是:新版Docker装完后,systemctl status docker显示active,但执行任何命令都会卡在Cannot connect to the Docker daemon。
清理命令如下:
sudo apt remove --purge docker docker-engine docker.io containerd runc -y sudo rm -rf /var/lib/docker sudo rm -rf /var/lib/containerd/var/lib/docker默认存放所有镜像、容器和卷的数据。如果在确认不需要旧数据的前提下不清除,新老版本的数据目录结构不一致,很可能启动后读取到脏数据,直接把dockerd搞崩溃。
注意:
/var/lib/docker删了就没了,里面的镜像、容器、数据卷都会一并清空。如果机器上有不能丢的容器数据,操作前先备份或导出。
1.3 网络环境:决定你是顺利装完还是折腾一下午
Docker安装过程中需要从apt源下载软件包,docker pull需要连接镜像仓库,这两件事都很依赖网络。国内服务器和部分企业内网环境,会在这里卡住。
你可以在执行安装命令之前先做个简单的连通性测试:
curl -s --connect-timeout 5 https://download.docker.com/linux/ubuntu/dists/focal/Release如果这条命令超时或者返回空,说明直接访问Docker官方源不可靠。那安装步骤里就要换用清华、阿里云的apt源镜像,不是“可选”而是“必须”。
另外注意,Ubuntu 20.04的代号是focal,下载Docker源时务必确认用的是focal而不是jammy(22.04)或noble(24.04),否则apt会报错或者拉不到包。这个细节后面会细讲。
2. 三种安装路线怎么选,以及我为什么只推荐其中一种
Docker在Ubuntu上的安装方式大致有三种:官方一键脚本、apt仓库安装、离线安装。我见过不少教程推荐第一种,也见过有人坚持用离线包,但从可维护性和后续升级的角度,我最推荐的是apt仓库安装。
2.1 官方一键脚本:快是真的快,坑也是真的多
官方提供了一条命令安装:
curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh这个脚本会自动检测系统版本、配置apt源、安装Docker Engine和containerd,还会顺手把compose插件一并装上。适合测试环境和个人快速体验。
但它的坑也很明显:第一,脚本默认下载源还是官方地址,网络不好的时候一样卡死;第二,脚本为了兼容不同发行版,做了一层封装,安装完成后路径、配置和服务文件都比较“黑盒”,出了问题不好排查;第三,它默认安装的版本不一定是当前最新,有时候是稳定版里的旧版本。
如果你在自己笔记本上随便玩玩,用这个脚本没问题。但如果是装在公司服务器或者要长期维护的机器上,不建议用它。
2.2 apt仓库安装:推荐首选,理由很实在
所谓apt仓库安装,就是先把Docker官方的软件源添加到Ubuntu的apt源列表里,然后用apt install docker-ce来安装。相比一键脚本,它的优势很明确:
- 安装的组件是标准的deb包,路径、服务、配置全部符合Linux惯例,出问题好排查;
- 后续升级直接
apt update && apt upgrade就行,不需要重新跑脚本; - 可以自己指定安装某个特定版本(
apt install docker-ce=<version>),方便版本管理。
完整步骤如下。
2.3 离线安装:没有外网的机器才需要
有些内网服务器是不能连外网的,这时候只能去一台能联网的机器上下载.deb包,然后拷过去手动安装。需要下载的组件包括:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
用apt download方式把这些包抓下来:
sudo apt download docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin然后拷到内网机器上,执行:
sudo dpkg -i *.deb依赖关系如果不满足,可能还需要sudo apt --fix-broken install。这个方法比较折腾,只有在断网环境下才值得用。能联网的机器,直接apt仓库装最省心。
3. 完整安装过程:从换源到跑通hello-world
下面是我在Ubuntu 20.04上完整安装Docker的过程,每一步都追加了说明,你照着走一遍基本不会出错。
3.1 第一步:更新apt源并安装基础依赖
sudo apt update sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release这些包是后面添加HTTPS源和导入GPG密钥时要用到的工具。apt-transport-https让apt支持通过HTTPS协议访问软件源,ca-certificates是CA证书库,curl用来下载密钥,gnupg负责密钥的导入与校验,lsb-release用于获取系统版本代号。
3.2 第二步:添加Docker官方GPG密钥
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 a+r /etc/apt/keyrings/docker.gpg这里有一个细节值得多说一句:早期教程里常见的写法是把密钥直接apt-key add到系统里,但这种方式在apt升级时会报apt-key is deprecated的警告,而且密钥是全局生效的。新的推荐做法是只安装在/etc/apt/keyrings目录下,并在source列表里指定具体路径,这样密钥的生效范围更精确。
如果你执行curl这一步时超时,说明官方域名访问有问题。可以先把GPG密钥改成从国内镜像下载,比如清华源的对应地址,或者手动下载后拷贝到目标机再导入。
3.3 第三步:添加Docker软件源(重点:Ubuntu 20.04代号)
这里需要特别注意架构和版本代号。Ubuntu 20.04的代号是focal,写错一个字,apt就找不到安装包。
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null上述命令中:
$(dpkg --print-architecture)会输出当前系统架构(一般是amd64,树莓派等ARM机器会输出arm64);$(lsb_release -cs)会输出Ubuntu版本代号,20.04就是focal。
如果你在添加源之前手动执行过lsb_release -cs确认代码,输出的是focal就没问题。如果发现输出不对,或者system是刚从其他版本升级过来的,就手动写死:
echo \ "deb [arch=amd64 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提示:国内网络环境下,把
download.docker.com替换为mirrors.tuna.tsinghua.edu.cn/docker-ce或阿里的mirrors.aliyun.com/docker-ce,能显著提升apt update的成功率。替换后同样要把路径拼写完整。阿里云路径示例:https://mirrors.aliyun.com/docker-ce/linux/ubuntu。
3.4 第四步:更新索引并安装Docker Engine
sudo apt updateapt update这一步可能会提示The repository ... does not have a Release file或者404错误。出现这个问题的原因,九成是source列表里的仓库路径拼错了,或者lsb_release -cs输出了不匹配的代号。检查一下路径是不是ubuntu后面漏了/,或者把focal错写成了bookworm这种其他发行版代号。
确认源无误后,直接安装:
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin建议一次性把docker-compose-plugin也装上。现在官方推荐用docker compose(带空格)作为子命令,而不是老式独立的docker-compose。新版compose插件配置更清晰,后续编排容器时能省不少事。
安装完成后,先别急着用,检查一下服务状态:
sudo systemctl status docker如果输出显示active (running),说明dockerd已经启动成功。再用docker version确认客户端和守护进程版本一致。
3.5 第五步:验证跑通
sudo docker run hello-world如果前面配置没问题,这条命令会拉取hello-world镜像并运行,最终输出一段说明Docker工作正常的提示。
在这步如果卡在Unable to find image 'hello-world:latest' locally然后又不断重试,说明镜像拉取失败,网络问题。别急,下一节专门讲镜像加速的配置。
4. 跑通hello-world只是开始,这几项基础配置必须做
安装完成后,很多人觉得能docker run就大功告成。但实际上,如果不做下面这几项配置,你的Docker在后续使用中会接二连三地遇到问题。
4.1 镜像加速器配置:这个不配,国内镜像基本拉不动
Docker默认的镜像源docker.io在国内的访问速度非常不稳定。哪怕你能正常安装Docker,也不代表你能顺利pull下来一个镜像。这俩走的是不同的网络链路。
修改方式很简单,编辑或创建/etc/docker/daemon.json:
{ "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com", "https://docker.nju.edu.cn" ] }这里贴几个目前测下来相对稳定的加速地址。注意镜像加速器的可用性变化很快,如果某个地址失效,就去搜索最新可用的加速器列表。
改完后重启Docker:
sudo systemctl daemon-reload sudo systemctl restart docker用以下命令确认配置生效:
docker info | grep -A 5 "Registry Mirrors"注意:daemon.json的格式必须是合法JSON,注释和尾逗号都不能有。因为配置解析失败导致dockerd无法启动的情况,我见过太多太多次了。
顺手把日志驱动和存储驱动也一并配置进去。我一般会在daemon.json里再加两行:
{ "registry-mirrors": ["https://docker.m.daocloud.io"], "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" }, "storage-driver": "overlay2" }日志限制非常关键。如果某个容器疯狂打日志,不限制大小的话,/var/lib/docker/containers目录会迅速膨胀,最终把磁盘塞满。overlay2是当前推荐的存储驱动,性能比老旧的aufs和devicemapper好得多。
4.2 免sudo操作:把当前用户加入docker组
Docker守护进程默认以root权限运行,/var/run/docker.sock这个socket文件的所有者是root。这意味着每次执行docker命令都要加sudo,时间长了非常烦人。
解决方法是把当前用户加入docker组:
sudo usermod -aG docker $USER newgrp docker然后重新登录终端,执行:
docker ps如果不再提示权限错误,说明配置成功。
这里必须说清楚一个安全风险:加入docker组等同于拥有root权限,因为你可以通过挂载宿主机目录的方式直接读写宿主机的任意文件。如果你对这台机器的安全要求很高,不建议给普通用户加入docker组。生产环境更稳妥的做法是用sudo或者配置细粒度的权限管理。
4.3 开机自启动设置:防止重启之后Docker没起来
Ubuntu使用systemd管理服务,docker-ce安装完成后默认会设置开机自启。可以用下面的命令确认:
sudo systemctl is-enabled docker如果输出disabled,就执行:
sudo systemctl enable docker防止重启之后Docker没起来。
4.4 Docker Compose验证
既然前面装了docker-compose-plugin,顺手验证一下:
docker compose version能看到版本号就说明compose插件可用。后续写docker-compose.yml编排多容器应用时,直接用docker compose up -d即可,不需要额外安装独立的docker-compose二进制文件。
5. 存储是迟早要面对的问题:默认路径迁移实战
Docker默认把所有数据存放在/var/lib/docker,包括镜像层、容器层、数据卷和网络配置。这个分区如果空间不够大,跑一段时间就会出问题。我在一台数据盘在/data的服务器上踩过这个坑:系统盘只有40G,跑了几个镜像后直接磁盘告警,容器全部进入异常状态。
5.1 什么时候需要迁移存储路径
需要迁移的情况主要有两种:一是系统盘空间紧张,Docker的数据盘在另一个挂载点;二是希望把Docker数据放到独立的磁盘或SSD上,提升IO性能。
在动手之前,先看清楚当前磁盘使用情况:
df -h如果/var/lib/docker所在分区的空闲容量明显不够用,而且另外挂载了一个更大容量的分区(比如/data、/home),那就建议迁移。
5.2 迁移步骤:三步搞定
第一步,停止Docker服务和containerd:
sudo systemctl stop docker sudo systemctl stop containerd第二步,把现有的Docker数据目录整体拷贝到目标路径。建议使用rsync,因为它支持断点续传和增量同步,比cp更适合大数据量迁移:
sudo rsync -avxP /var/lib/docker/ /data/docker/如果你的系统还没有rsync,先sudo apt install rsync装上。
第三步,修改/etc/docker/daemon.json,把数据目录指向新位置:
{ "data-root": "/data/docker" }保存后重启Docker:
sudo systemctl daemon-reload sudo systemctl start docker启动后用以下命令验证当前数据目录:
docker info | grep "Docker Root Dir"看到输出是/data/docker就说明迁移成功了。原目录/var/lib/docker确认无误后再手动清理,不要迁到一半就删。
提示:有些教程建议修改
/etc/fstab做软链接,比如把/var/lib/docker挂载到新分区。这种方法也能用,但相比daemon.json配置,可维护性和可读性都差一些。实测下来,daemon.json的方式在重启和版本升级后更为稳定。
5.3 数据盘独立分区的场景:先格式化再挂载
如果目标位置是一块全新的数据盘,需要先分区、格式化、挂载:
sudo fdisk /dev/sdb sudo mkfs.ext4 /dev/sdb1 sudo mkdir -p /data sudo mount /dev/sdb1 /data为了让重启后挂载依然生效,需要在/etc/fstab里增加一行:
/dev/sdb1 /data ext4 defaults 0 0先执行mount -a测试一下配置无误再重启系统。
6. 装完Docker后常见的几个坑,附排查思路
Docker安装完成后,真正麻烦的是使用过程中遇到的各种问题。这里挑几个高频故障,把排查思路写出来,下次遇到直接照着走。
6.1 docker pull超时或报错
这是国内环境最常遇到的问题。报错形式一般是:
Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection排查思路:
- 先确认daemon.json里的镜像加速器配置是否生效,执行
docker info看Registry Mirrors字段; - 确认配置没问题后,试试手动拉取一个加速器的镜像,比如
docker pull daocloud.io/library/nginx:latest,如果能成功说明加速链路是通的,问题出在某个特定镜像源上; - 如果启用了公司内网的HTTP代理,检查
/etc/systemd/system/docker.service.d/http-proxy.conf里的代理配置是否正确。
6.2 Ubuntu 20.04特有的:iptables / nftables冲突
Ubuntu从18.04开始,防火墙从iptables迁移到nftables,但Docker默认的防火墙处理机制仍然基于iptables。在某些安装了ufw防火墙的机器上,Docker发布端口后会出现从外部访问不了容器服务的问题。
排查方法:
sudo iptables -L -n | grep DOCKER如果没有看到DOCKER链,说明iptables规则没正常加载,dockerd处于异常状态。重启Docker:
sudo systemctl restart docker如果重启后仍然没有DOCKER链,检查一下是不是装了nftables包并且配置了防火墙规则,导致和Docker的规则冲突。最稳妥的办法是确保系统的防火墙管理和Docker兼容:如果你用ufw,至少把/etc/default/ufw里的DEFAULT_FORWARD_POLICY改成"ACCEPT"。
6.3 容器都正常,但宿主机访问不了容器内服务
这类问题大多是端口映射或网络模式导致的。先排除基础问题:
docker ps docker port <container_id>如果端口映射显示正常,但访问不通,试试curl容器IP加端口:
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' <container_id> curl http://<容器IP>:<端口>容器IP能通而宿主机映射端口不通,优先怀疑防火墙拦截。容器IP都不通,优先检查容器内服务本身有没有监听在正确的IP和端口上。
6.4 overlay2存储目录膨胀,镜像删了空间还是不够
很多人删除镜像后发现磁盘空间没有释放。这是镜像分层机制导致的:某个镜像被删除时,如果它的底层镜像层仍被其他镜像引用,那部分数据不会真正释放。
排查思路:
docker system df这个命令会把镜像、容器、数据卷、构建缓存占用的空间列出来。大多数情况下,占空间最大的是Build Cache(构建缓存),因为每构建一次镜像,都会产生新的中间层缓存。定期清理:
docker builder prune -f docker system prune -f注意docker system prune会把所有停止的容器、悬空镜像、未使用的网络全部清掉,执行前确认没有需要保留的东西。
6.5 DNS配置问题:容器无法解析域名
容器内执行apt update报域名解析失败,宿主机却正常。这是因为容器的DNS默认继承自宿主机,而宿主机的DNS配置(比如/etc/resolv.conf里指向了内网DNS服务器,但该服务器拒绝了容器网络的查询)在容器内不适用。
解决方案有两个:
一是全局配置,编辑daemon.json加上:
{ "dns": ["8.8.8.8", "223.5.5.5"] }二是只给特定容器指定DNS,运行容器时加--dns参数:
docker run --dns 223.5.5.5 ubuntu实际工作中,第二种方式更灵活,不会影响其他容器。
7. 几条我在实际使用中总结的经验
最后说几个零散但实用的经验,算不上系统的教程,但都是我踩过坑之后才总结出来的。
第一,不要在daemon.json里配置太多的镜像加速地址。配置两三个够用的就行,配置多了反而会触发Docker的fallback机制,在第一个镜像源超时后反复切换,导致拉取速度更慢。
第二,Docker版本升级要谨慎。虽然apt upgrade能自动升级Docker,但大版本升级前一定要先备份所有数据卷里的数据。我在一次从19.03升到20.10的过程中遇到过containerd版本不兼容导致容器全部退出的事故。最稳妥的做法是看官方changelog,确认没有breaking change后再升。
第三,善用docker logs排查容器启动问题。容器启动后立即退出,先别急着改代码,先看日志:
docker logs --tail 50 <container_id>很多问题一眼就能看出来,比反复重启容器高效得多。
第四,Docker的数据安全是个永恒话题。无论你用的是数据卷还是绑定挂载,都要定期备份。不要以为容器里的数据是持久的就掉以轻心——容器本身随时可以被删,数据卷一旦误删找回的成本极高。
我能想到的坑和技巧基本都在这了。Ubuntu 20.04装Docker本身不是难事,难的是装完之后的网络、存储、权限、兼容性这些细节。把这些基础打好了,后面跑MySQL、Redis、GitLab这些容器化服务的时候会省心很多。