前阵子给一台刚装好的 Linux 服务器配置 Docker,过程没什么技术难度,但零零散散踩了几个小坑,比如系统源没换、镜像加速没配、权限不对导致反复 sudo。想了想干脆把完整的安装过程写成一篇图文解说版分享出来,标题看着是“Linux 下安装 Docker”,其实里面的思路放在 Debian、Ubuntu、CentOS、RHEL 这些主流发行版上都通用。这篇内容适合刚接触 Linux 的运维新手,也适合那些已经装了 Docker 但经常被权限、拉镜像超时、存储目录乱七八糟折磨的朋友。我会把每一步为什么要这么做讲清楚,你跟着操作就行。
1. 安装 Docker 前,先想清楚这几件事
1.1 容器和虚拟机不是一个东西,别搞混
很多人第一次接触 Docker 时会拿它跟虚拟机比较,这是对的,但结论容易跑偏。虚拟机是在宿主机上再跑一个完整的操作系统,里面有独立的内核、系统库、服务管理,开销大但隔离性最强。而 Docker 容器利用的是 Linux 内核自带的 namespaces 和 cgroups 能力,多个容器共享宿主机内核,通过命名空间把进程、网络、文件系统、用户这些视图隔离开来。
这个概念不是考试用的,它直接影响你装完之后的理解。比如为什么 Docker 容器里跑的 Linux 发行版可以是 CentOS、Ubuntu、Alpine,但底层用的还是宿主机的内核版本;为什么容器里执行uname -r看到的是宿主机内核;为什么重启容器会比重启虚拟机快很多。把这些底层逻辑想明白,后面遇到“容器里能做什么、不能做什么”时就不会一脸懵。
1.2 为什么我反而不推荐用发行版自带的 docker 包
Linux 各发行版软件源里基本都有 Docker 相关包,比如 Debian/Ubuntu 的docker.io,CentOS 老的docker包。很多教程上来就让你apt install docker.io,图省事。但我的习惯是坚决不用它,原因有三点:
第一,发行版仓库里的 Docker 版本通常滞后很多,官方已经迭代了好几个大版本,你装到的可能还是老掉牙的版本,新特性、安全补丁都跟不上。第二,官方docker.io包只包含基础组件,像docker buildx、docker compose plugin这些常用工具经常缺失,你后面想用还得再补。第三,官方仓库的 Docker 包名叫docker-ce,由 Docker 官方维护升级节奏,跟发行版自身的更新节奏解耦,出了问题更容易在社区里找到同一版本的问题记录。
所以如果你不是有非常特殊的兼容性要求,我建议直接上 Docker 官方仓库。当然,这里有个例外,如果你用的是某些信创或者国产化系统,默认软件源里的 Docker 移植版本可能是团队专门适配过的,那用发行版的包反而更稳妥。
1.3 对不同 CPU 架构和内核版本,心里要有数
Docker 官方仓库支持的 CPU 架构主要是x86_64和aarch64,也就是我们常说的 Intel/AMD 64 位和 ARM 64 位。这几年国产 CPU 环境越来越常见,比如用龙芯、飞腾、鲲鹏的服务器,系统可能是基于 LoongArch 或者 ARM 架构的,这时候安装方式就得调整,不能直接拿 x86_64 的安装命令硬套。一般来说,ARM 架构的机器可以直接使用官方 aarch64 的仓库源;LoongArch 这类非主流架构,尽量找系统厂商或者社区提供的 Docker 移植版本。
另外内核版本也有最低要求。官方文档写的门槛是 3.10 以上,但我个人建议内核至少 4.0 以上,最好能用 5.x 或更新的版本。原因很简单,新版 Docker 很多能力依赖较新的内核特性,比如 overlay2 存储驱动的稳定性、核心的cgroup v2支持,老内核跑起来能跑,但性能和稳定性会打折扣。检查内核版本用一条命令:
uname -r看到类似5.15.0-91-generic这样的输出就说明内核版本足够新,没什么好担心的。
2. 环境准备:正式动手前需要做好的三件小事
2.1 查看系统版本和内核参数
开始安装之前,先确认一下你手里的系统到底是什么发行版、什么版本。因为不同发行版用的包管理器不同,下面的安装命令也会不同。最稳妥的查看方式是:
cat /etc/os-release输出里会带出ID、VERSION_ID、PRETTY_NAME这些字段。比如在 Ubuntu 22.04 上,你会看到ID=ubuntu,VERSION_ID="22.04"。这决定了你后面用apt还是dnf,以及应该添加哪个官方仓库地址。
除了版本号,还需要确认几个内核模块。Docker 的网络隔离要用到br_netfilter,存储驱动要用到overlay。可以在安装前先检查:
lsmod | grep -E "overlay|br_netfilter"如果什么都没有输出,说明模块没加载,手动加载一下:
sudo modprobe overlay sudo modprobe br_netfilter为了让模块在重启后依然生效,最好在/etc/modules-load.d/docker.conf里写入这两行。顺手再把内核的网络转发参数也打开,让容器能正常访问外网:
sudo tee /etc/sysctl.d/docker.conf <<EOF net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 net.bridge.bridge-nf-call-ip6tables = 1 EOF sudo sysctl --system这套准备动作和 Docker 本身没有直接关系,但少了它,后面容器网络经常会出现各种奇怪问题,而且排查起来特别费劲。
2.2 彻底卸载旧版本并保留数据
如果你之前装过 Docker,无论是否装成功,我建议先卸载干净再重装。不然系统里同时存在两套 Docker 相关命令,docker命令指向的版本可能跟你以为的不一样,后面排查问题会很痛苦。
Debian/Ubuntu 系卸载命令:
sudo apt remove docker docker-engine docker.io containerd runcCentOS/RHEL 系卸载命令:
sudo yum remove docker docker-client docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine这里有个容易忽略的点:默认情况下,Docker 的所有镜像、容器、卷都存在/var/lib/docker目录下。卸载的时候,如果直接rm -rf /var/lib/docker,那原来的数据就全没了。如果你是清掉旧环境重新开始,那无所谓;但如果你只是想把 Docker 的包换掉、想保留已有镜像和容器数据,卸载前一定要先备份这个目录,或者直接把它挪走:
sudo mv /var/lib/docker /var/lib/docker.bak重装完成且验证没问题之后,再把数据目录恢复回去。注意新旧版本的数据目录格式不保证完全兼容,所以恢复之前建议先在新环境上跑一个docker info,确认版本差异不大。
2.3 换好软件源,别让安装卡在第一步
这一步属于“看似无关、实际救命”的操作。如果你在国内网络环境下直接使用官方源安装系统依赖包,apt update或dnf makecache都可能慢到你怀疑人生。所以动手装 Docker 之前,先把操作系统的软件源换成分流镜像源,常见的像阿里云、清华 TUNA、华为云都有,这里不纠结选哪家,只要在你网络下够快就行。
以 Ubuntu 22.04 为例,改源不复杂。备份一下原始源文件,然后编辑/etc/apt/sources.list,把域名替换成镜像站域名。替换完后执行:
sudo apt update如果看到列表刷刷刷下载,说明源已经生效。CentOS/RHEL 系则可以先备份/etc/yum.repos.d/下所有仓库文件,然后用镜像站提供的 CentOS-Base.repo 替换,再执行:
sudo yum makecache这一步做完,后面安装 Docker 系统依赖包的速度会有肉眼可见的提升。
3. 官方仓库安装 Docker 的完整实操(含代码)
3.1 Debian / Ubuntu 系安装步骤详解
以 Ubuntu 22.04 为例,先把需要的依赖包装上:
sudo apt update sudo apt install ca-certificates curl gnupg lsb-release接着添加 Docker 官方的 GPG 密钥。这个密钥的作用是校验后面下载的 Docker 包确实由官方签名,防止源被篡改:
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然后写入软件源配置。这里要注意版本代号,Ubuntu 22.04 的代号是jammy,你可以通过lsb_release -cs自动获取:
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再次更新源,安装 docker-ce 及配套组件:
sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin这里的docker-ce是 Docker 引擎本体,docker-ce-cli是命令行工具,containerd.io是底层的容器运行时,docker-buildx-plugin和docker-compose-plugin是构建镜像和编排容器的插件。很多人只装前三个,等用docker compose的时候才发现命令不存在,所以我们一次性装齐。
3.2 CentOS / RHEL / Fedora 系安装步骤详解
CentOS 7 虽然官方已经停止维护了,但存量用户还是很多。在 CentOS 上安装 Docker 官方源的方式是先安装 yum-utils:
sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo然后安装:
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin如果你用的是 RHEL 9 或 Rocky Linux 9,思路一样,只是把仓库地址换成对应的系统版本。Fedora 则用 dnf 代替 yum:
sudo dnf install -y dnf-plugins-core sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin这里有个小问题要注意:CentOS 默认的防火墙 firewalld 可能会拦截 Docker 容器的端口映射。安装完 Docker 后,如果容器端口映射出去访问不通,记得先去防火墙放行对应端口,或者干脆在安全环境里把 firewalld 停掉。这块内容后面常见问题部分还会讲到。
3.3 官方快速脚本安装:什么时候能用,什么时候别用
Docker 官方其实提供了一个傻瓜式安装脚本:
curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh脚本会自动识别系统、配置源、安装最新版 Docker。这套方案适合什么场景?适合你只想在本机快速搭一个 Docker 环境,比如做实验、测试、临时跑个容器,一切求快。但我不建议生产环境直接用脚本装,原因有三个:脚本的安装过程不透明,你很难精确控制它到底改了哪些系统文件;它默认安装的是官方当前最新版,没法指定某个历史版本;如果你需要自定义安装参数,比如指定存储驱动、修改 containerd 配置,脚本做不到那么细。所以生产环境我还是推荐手动配置源再安装,过程虽然多几条命令,但可控性高得多。
3.4 安装完成后的第一轮自检命令
装完之后先别急着跑容器,用几条命令看看状态:
docker version docker infodocker version会显示 client 和 server 两个部分的版本信息。如果 server 那一栏显示不出来,说明 Docker 服务还没启动或者没权限连接,先不要继续往下操作。docker info会输出更详细的信息,包括存储驱动、Cgroup 版本、镜像目录、CPU 和内存数量、容器数量等。
重点盯几个字段:
Storage Driver:正常情况下应该是overlay2;Cgroup Version:新版本可能显示2;Docker Root Dir:默认是/var/lib/docker;Server Version:确认 server 端已经正常响应。
如果这些字段都正常,说明引擎本身没问题,可以进行下一步。
4. 启动 Docker 服务并跑通第一个容器
4.1 通过 systemd 启动并设置开机自启
Docker 装好后,服务不会自动启动,需要你用 systemd 来管理。执行:
sudo systemctl enable --now dockerenable的意思是设置开机自启,--now的意思是立刻启动服务。平时你也可以分两步执行,效果一样。验证一下服务状态:
sudo systemctl status docker正常情况下会看到active (running),并且下面有几行日志,提示监听在/var/run/docker.sock。这个 socket 文件就是 Docker 客户端和守护进程通信的入口,后面很多权限问题都和它有关。
4.2 hello-world 验证:看懂一次完整的容器生命周期
服务起来后,跑第一个容器:
sudo docker run hello-world如果本地没有hello-world镜像,Docker 会去 Docker Hub 拉取,然后基于镜像创建并启动容器,容器输出一段欢迎信息后退出。看起来很简单,但这个过程实际上走完了容器完整生命周期:拉取镜像、创建容器、启动容器、执行入口命令、容器退出。
你可能会好奇,为什么hello-world容器执行完就退出,而像 Nginx 这样的容器就能一直后台运行?关键在于容器的主进程是什么。hello-world的主进程执行完输出就结束了,容器自然也就退出了。Nginx 的主进程会一直监听端口,不会退出,所以容器能一直保持运行状态。
可以用docker ps -a查看刚才创建过的容器记录,哪怕是已经退出的容器也能看到。这一步验证通过,说明镜像拉取、容器运行时、网络链路基本都通了。
4.3 把当前用户加入 docker 组,少敲一半 sudo
每次用 Docker 都加sudo很烦,而且容易在脚本里因为权限问题踩坑。Docker 官方提供了一个用户组方案:
sudo usermod -aG docker $USER执行完后,需要重新登录一次或者执行newgrp docker,让用户组变更在当前会话里生效。之后你再执行docker version就不需要加 sudo 了。
这里我必须要提醒一句:把用户加入 docker 组,等同于赋予该用户 root 级别的权限。因为 Docker 的 socket 连接具备了操作宿主机的完整能力,理论上可以利用挂载把宿主机的/目录映射到容器里随意读写。所以生产服务器上,不要随便把不信任的用户加入 docker 组,也不要为了图省事把/var/run/docker.sock的权限直接改成 777。
5. 安装完成后,推荐立刻做的四项配置
5.1 配置镜像加速器和 daemon.json
装好 Docker 只是第一步,顺手把镜像加速配上,后面拉镜像会舒服很多。Docker 默认从 Docker Hub 拉镜像,但网络环境不一定都能顺畅访问 Docker Hub,所以很多云厂商都提供了镜像加速服务。你可以在自己的阿里云容器镜像服务控制台里找到专属加速地址,也可以使用网上的公共加速器,选一个稳定可用的就行。
配置方法很简单,编辑/etc/docker/daemon.json,这个文件是 Docker 守护进程的主要配置文件,如果不存在就新建一个:
{ "registry-mirrors": ["https://你的加速地址"] }改完执行:
sudo systemctl daemon-reload sudo systemctl restart docker然后用docker info查看Registry Mirrors字段,确认加速器已经生效。这里有个细节:daemon.json必须是严格的 JSON 格式,不能有注释,不能有多余的逗号,否则 Docker 服务起不来。这也是后面常见问题里最容易被忽略的一个坑。
5.2 修改镜像与容器默认存储目录
默认情况下,所有镜像和容器数据都存在/var/lib/docker。如果你的系统盘比较小,或者你希望把数据放在独立的大容量数据盘上,建议在首次使用前就修改存储目录,因为以后数据变多再迁移会很折腾。
同样在daemon.json里增加:
{ "data-root": "/data/docker" }改成/data/docker之后,记得提前创建目录并设置好权限:
sudo mkdir -p /data/docker然后重启 Docker。为什么要强调“首次使用前”?因为如果/var/lib/docker下已经有镜像和容器数据了,你改变数据目录后新目录是空的,旧容器都找不到了。除非你手动把旧数据复制过去,但容器正在运行的时候千万别这么干,很容易造成数据损坏。如果确实要迁移,合理的流程是:停掉 Docker 服务、拷贝数据目录、修改 daemon.json、启动服务、验证所有容器都正常。
5.3 限制容器日志大小,防止磁盘被塞满
这是运维里最容易踩的坑。一个容器如果不断往标准输出打印日志,Docker 默认会一直收集,时间久了/var/lib/docker/containers目录下会出现几个 G 甚至几十个 G 的日志文件。所以我每次装完 Docker 都会第一时间限制日志大小:
{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }这个配置表示每个容器最多保留 3 个日志文件,每个文件最大 10MB,单个容器日志量上限就是 30MB。配置是全局默认,容器启动时如果没有单独指定日志参数,都会使用这个默认策略。这样即使日志再疯狂,也不会把磁盘打爆。对于已经运行的容器,修改这个配置不会立即生效,需要把容器删除重建才能应用新策略。
5.4 关于 iptables 和防火墙,提前打个预防针
Docker 在启动时会往宿主机的 iptables 里插入自己的链,用来处理容器网络地址转换和端口映射。这也意味着,你宿主机上原本自定义的 iptables 规则有可能会被 Docker 影响,尤其是 FORWARD 链上的一些策略。
如果你在宿主机上开启了 firewalld 或者 ufw,并且发现容器端口映射不通,通常是因为 Docker 和防火墙的规则冲突。一个常见的临时排查方法是用iptables -L -n -t nat看看DOCKER链是否正常生成,另一个方法是先在防火墙里放行容器映射的端口。
但我不建议你一上来就关防火墙,毕竟安全要紧。比较稳妥的方式是把 Docker 需要的端口挨个放行,防火墙规则保持最小化授权。如果集群环境里还用到了 Kubernetes 或者 Calico 这类网络插件,那么 Docker 的 iptables 管理还涉及更复杂的配置,这里先不展开,你只要记住 Docker 和防火墙之间不是“谁替代谁”的关系,而是需要协同配合的。
6. 常见问题与排查技巧实录
6.1 Docker 服务启动失败,十有八九是 daemon.json 写坏了
我见过太多人改了daemon.json之后,执行systemctl restart docker,然后看到一行红色错误:
Job for docker.service failed because the control process exited with error code.这种错误本身信息量太少,真正有用的日志要看这里:
sudo journalctl -u docker --no-pager | tail -n 50多数情况下你会看到类似error parsing daemon.json: invalid character ...这样的提示,说明 JSON 格式不合法。JSON 对逗号、括号、引号都特别敏感,我见过有人写参数时最后一个键值对后面多留了一个逗号,就导致服务起不来。
还有一个高频原因是对应目录权限不对,比如你设置了"data-root": "/data/docker",但/data/docker目录的属主不是 root,Docker 启动时会因为没有权限而直接退出。解决办法就是创建目录,然后chown -R root:root /data/docker。
6.2 拉取镜像超时或速度极慢,不要只想着重试
执行docker pull的时候卡在:
pull access denied for xxx或者一直停在Waiting、Retrying,大概率是网络问题。这时候优先做两件事:一是检查系统 DNS 配置,容器拉镜像时也需要解析域名;二是确认镜像加速器是否已经生效。
docker info | grep -A 1 "Registry Mirrors"如果Registry Mirrors下面为空,说明daemon.json没生效,服务可能用了旧的配置。也可以试试拉一个小体积镜像测试:
docker pull busybox:latestbusybox只有几 MB,拉取失败的话说明网络链路确实有问题,而不是镜像太大。这时候可以考虑给 Docker 配置 HTTP 代理,或者在能访问 Docker Hub 的跳板机上拉好镜像再导出导入,方法很多,不建议反复重试同一套命令。
6.3 提示 Got permission denied,问题出在 socket 权限
有些人在新机器上装好 Docker,直接执行:
docker ps然后看到:
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock这个提示我们已经碰到过了。原因是 Docker 客户端需要访问/var/run/docker.sock,这个 socket 文件的属组是docker,你的用户不在组里,所以被拒。解决办法就是前面提到的usermod -aG docker $USER,重新登录即可。
这里有个反直觉的地方:很多教程只会说“加入 docker 组”,但加入之后当前终端会话不生效,还是继续报错。要么重新 SSH 登录,要么执行newgrp docker触发重新加载用户组信息。我每次在脚本里操作的时候,都倾向于直接用sudo,因为脚本执行环境往往没有交互式登录,newgrp在脚本里的效果不稳定。
6.4 磁盘被 /var/lib/docker 占满怎么办
容器数量一多,镜像、容器层、卷、日志都会占用不小的空间。先看看占用情况:
docker system df这个命令会列出镜像、容器、本地卷、构建缓存分别占多少空间。发现占用过高时,清理命令别乱用,分清场景:
docker system pruneprune会清理已经停止的容器、无用的网络、悬空的镜像和构建缓存。加上-a之后会连所有没有被容器使用的镜像一起删掉,这个动作比较激进,如果你只是临时想救急空间,不建议直接上-a。如果你想查看哪些容器日志最大,可以进/var/lib/docker/containers目录下挨个看*.log文件,找到后清空文件即可,不用删除整个目录:
sudo sh -c "truncate -s 0 /var/lib/docker/containers/*/*-json.log"当然长期方案还是我在配置里提到的限制日志最大大小,从源头控制。
6.5 容器启动了但端口访问不通,先从这三步排查
这类问题听说得最多,症状是docker ps能看到端口映射,比如0.0.0.0:8080->80/tcp,但从外部访问宿主机的 8080 端口就是不通。
第一步,先在宿主机上测试容器 IP:
curl http://172.17.0.2如果容器 IP 能访问,说明容器本身没问题,问题出在端口映射或防火墙。第二步,看宿主机端口是否被监听:
ss -tlnp | grep 8080如果没输出,说明 Docker 的端口映射没生效。第三步,检查防火墙。用sudo iptables -L -n | grep 8080看看有没有对应的 DNAT 规则,同时检查 firewalld 或 ufw 有没有放行该端口。绝大多数情况都是卡在第三步,放行端口后立竿见影。
排查到这里,我对 Docker 安装、启动、权限、网络这几个高频坑基本都过了一遍。最后分享一个小习惯:我拿到一台新服务器,装完 Docker 后会把上面这些配置固化成一个初始化脚本,包括系统源替换、内核参数调整、daemon.json 配置、用户组设置、hello-world 验证,全部自动化。以后无论换到哪台机器,整套流程跑一遍就能交付一个稳定的容器环境,不用每次重复踩坑。如果你也经常跟多台 Linux 服务器打交道,建议尽早做这样一套自己的初始化模板,省下的时间和避免的麻烦,远比写脚本的几分钟成本值得。