☰
Ubuntu系统里到底有几个Docker?多版本共存排查全指南
2026/10/7 3:23:33 网站建设 项目流程

1. 为什么要纠结“系统里有几个 docker”:一次真实的环境冲突复盘

先说个我自己的经历。前两年接手一台 Ubuntu 20.04 服务器,前任运维留下的环境。我跑docker ps明明能列出容器,但systemctl restart docker之后容器全没了,后来发现/var/run/docker.sock指向的是 snap 版 docker 的 socket,而我以为我控制的 systemd 服务是 apt 版 docker。同一个系统里同时装了两套 docker 客户端、两套守护进程,互相打架。从那以后我养成了一个习惯:凡是拿到一台陌生的 Ubuntu 机器,第一件事就是先搞清楚这台机器上到底“有几个 docker”——这里说的不是有几个容器,而是有几套 Docker 相关的安装实体。

这句话听起来像是废话,但实际踩坑的人不在少数。你可能会遇到这些场景:

  • docker -v显示的版本和docker version显示的 Server 版本对不上,客户端是 24.x,服务端却是 20.x。
  • 明明执行了systemctl stop docker,但docker ps依然能连上某个守护进程,容器照常运行。
  • 想升级 docker,结果 apt 和 snap 两套包管理各装了一份,升级完一个另一个还是旧的。
  • 排查问题时whereis docker能看到多个可执行文件路径,但你不知道当前 shell 实际用的是哪个。

这篇文章我就以 Ubuntu(涵盖 20.04/22.04/24.04)为背景,手把手教你把系统里的 Docker 相关实体全部揪出来,搞清楚彼此之间的关系,并且给你一套可以直接复制运行的排查脚本。内容不追求面面俱到,重点放在“如何判断系统里有几套 Docker 安装、如何识别它们各自的状态、以及遇到冲突时怎么处理”。

2. 安装方式的差别决定了你会看到几个 Docker

2.1 Ubuntu 下 Docker 的三种主流安装路径

想要排查“有几个 docker”,先得知道 Ubuntu 上 Docker 可能的来源。很多冲突都源于不同来源的 Docker 同时存在,各自又互不知情。

安装方式可执行文件位置服务管理方式套接字默认位置
apt 官方源安装/usr/bin/dockersystemd(docker.service)/var/run/docker.sock
Docker 官方 apt 源安装/usr/bin/dockersystemd(docker.service)/var/run/docker.sock
snap 安装/snap/bin/docker(软链)snap 服务(snap.docker.dockerd)/var/run/docker.sock(也走 snap 的 socket)
二进制手动安装自定义目录(如 /usr/local/bin/docker)不一定有服务文件,可能用 screen / nohup 拉起按启动参数而定

最常见也最坑的组合:系统里同时存在 apt 版和 snap 版。Ubuntu 的软件商店默认推荐 snap,而很多教程让你用 apt 装 docker.io 或官方源。如果你手滑两个都装过,就会出现上面说的那种“看起来能用、但深挖全是坑”的现状。

提示:判断当前 shell 实际调用的是哪个 docker,不要只看which docker,因为 PATH 里哪个目录靠前哪个就生效。which只能告诉你当前环境调用的是哪一个,而不是系统里总共有几个。

2.2 为什么 apt 版和 snap 版会“同时存在”

apt 安装的包路径是/usr/bin/docker,snap 安装的路径则是/snap/bin/docker。两个可执行文件互不覆盖,因为 snap 有自己独立的挂载机制。所以从文件系统层面看,至少两套文件是同时真实存在的。再加上 snap 的 docker 还会自带的 dockerd 守护进程由 snap 服务拉起,而 apt 版同样注册了 systemd 的 docker.service。如果你不认真查,shell 走哪套全看环境变量里 PATH 的优先级——Ubuntu 默认 PATH 中/usr/bin排在/snap/bin之前,所以默认情况下你用的很可能是 apt 版。

这就是为什么很多人看到的现象是:docker -v显示一个版本,snap list docker又显示另一个版本,而systemctl status docker和snap services docker都在“active”。系统里确实是两个 docker。

3. 第一轮排查:用 docker 自身的命令确认客户端和服务端

3.1 docker version 比 docker -v 信息量大得多

很多新手只知道docker -v,这只能看到客户端版本。要看当前 shell 使用的 docker 是否真的连通了服务端,必须用docker version。它分为 Client 和 Server 两段输出。Server 段落能正常列出版本和 API 版本,说明客户端能找到并连上了一个守护进程;Server 段直接报错Cannot connect to the Docker daemon,则说明当前环境没有可用的服务端,或者 socket 路径不对。

$ docker version Client: Docker Engine - Community Version: 26.1.4 ... Server: Docker Engine - Community Engine: Version: 26.1.4

关键点在于:docker version里的 Client 版本和 Server 版本可以不一致,因为客户端和服务端走的是 HTTP API 通信,只要 API 版本兼容就行。但大版本差太多时会报错提示server version is too old。如果你看到 Client 和 Server 版本明显不匹配,先别急着骂系统,很可能是因为你 shell 里调用的 docker 是 A 套,而正在监听 socket 的 dockerd 属于 B 套。

3.2 查看当前 docker 可执行文件真实路径

which docker type -a docker whereis docker
  • which docker:给出 PATH 中第一个命中的可执行文件路径。
  • type -a docker:会把所有出现在 PATH 里的 docker 都列出来,这是 Shell 内建命令,比 which 更直观。
  • whereis docker:会扫默认二进制目录,可以看到/usr/bin/docker、/snap/bin/docker等多个路径。

实测过程中最常见的结果是:which docker返回/usr/bin/docker,但type -a docker还能看到/snap/bin/docker。要知道,即使你不会主动输入/snap/bin/docker,如果你曾经在某个服务脚本里写了绝对路径调用它,那么那个进程用的就是 snap 版。这也是排查问题的突破口。

$ type -a docker docker is /usr/bin/docker docker is /snap/bin/docker

3.3 用 docker context 看当前连的是哪个 socket

Docker 从 19.03 开始引入了 context 机制,可以配置多个 endpoint。默认 context 是default,连的是unix:///var/run/docker.sock。如果你之前手动创建过 context,或者 Docker Desktop 之类的工具改过配置,当前 shell 可能连的根本不是/var/run/docker.sock。

docker context ls

输出会列出所有 context 以及当前激活的那个(带*号的)。查看详情:

docker context inspect

这个命令能告诉你当前 context 的 Endpoint 到底是什么。我排障时经常先看一眼这里,确认是不是被某个自定义 context 带偏了方向。

提示:如果你在 Ubuntu 上装了 Docker Desktop(目前主要是基于 WSL2 或特定桌面版),它的 SDK 和 CLI 可能会单独管理一套上下文,和系统里的 docker.sock 不是一回事。涉及到 Docker Desktop 的场景,优先检查~/.docker/config.json和docker context ls。

4. 第二轮排查:systemd、snap 服务与 socket 的三方对照

4.1 systemctl 能查到 systemd 注册的 docker 服务

如果 Docker 是 apt 安装的,通常会有 systemd 服务文件:

systemctl list-unit-files | grep -i docker systemctl status docker

输出一般长这样:

● docker.service - Docker Application Container Engine Loaded: loaded (/lib/systemd/system/docker.service; enabled; preset: enabled) Active: active (running) since ...

这会告诉你 systemd 视角下有一个 docker 守护进程正在运行。但注意:这只是“systemd 管理的那个”docker 实例。如果系统里还有 snap 版 docker,snap 版有自己独立的服务,不归 systemd 直接管,而是通过snap命令查看。

snap list | grep docker snap services docker

snap services docker的输出一般包含dockerd和containerd等。如果 snap 服务也是 active,那你基本可以确认系统里有两套服务同时存在。

4.2 对照 socket 文件找出真正的守护进程所有者

Docker 客户端默认通过/var/run/docker.sock和守护进程通信。如果安装方式不唯一,这个 socket 文件可能被不同的守护进程占用。排查方法:

ls -l /var/run/docker.sock

如果输出显示 owner 是 root 且 socket 文件存在,一般来说有守护进程在监听。但你还得确认这个 socket 对应哪个进程。一条命令搞定:

sudo ss -xlp | grep docker

它会列出 Unix socket 监听的进程。ss里的-x表示显示 Unix socket,-l表示监听状态,-p显示进程信息。输出会直接显示类似:

u_str LISTEN 0 4096 /var/run/docker.sock 12345 users:(("dockerd",pid=1234,fd=3))

到这里你能发现一个关键事实:真正监听 docker.sock 的是哪个 dockerd 进程,这个进程的 PID 是什么,它由谁拉起。

4.3 进程层面:所有 dockerd 和 containerd 都列出来

不要只看一个 socket,直接按进程名找:

ps aux | grep -E "dockerd|containerd" | grep -v grep

这个方法可以列出所有正在运行的守护进程相关进程。如果发现两个不同的 dockerd 进程,而且一个的开始命令里带snap字样,另一个是/usr/bin/dockerd,那系统里就是同时有两套 Docker 运行时在跑。两个进程如果都在监听 socket 但监听路径不同,那么客户端只能连上其中某一个,取决于你的DOCKER_HOST环境变量或 context 配置。

env | grep -i docker

检查是否有DOCKER_HOST环境变量。一旦你设置了DOCKER_HOST=tcp://...,那么你本地所有 docker 命令都不会走本机 socket,而是去连远程地址。这种状态下你查到的“Server 版本”可能根本不是本机 dockerd。Ubuntu 桌面环境一般不会设置这个变量,但服务器环境里有些人会配。

5. 第三轮排查:把文件系统里所有 docker 实体挖出来

5.1 不同位置的 docker 二进制文件

我建议做一个全盘扫描,找出所有名称包含 docker 的可执行文件或者目录:

sudo find / -name "docker" -type f 2>/dev/null sudo find / -name "dockerd" -type f 2>/dev/null

这个命令不要在小内存机器上跑全盘,但一般服务器也没多大负担。扫描结果里最常见的:

  • /usr/bin/docker:apt 安装
  • /snap/bin/docker:snap 安装
  • /usr/local/bin/docker:手动二进制安装
  • /usr/libexec/docker/cli-plugins/:docker compose 等插件目录

如果你看到/usr/local/bin/docker,那说明曾经有人手动下过二进制包,这种安装方式最麻烦——它不会注册任何服务文件,没有 systemd 单元,也不会出现在 apt 或 snap 的包管理列表里,但如果你手动跑过dockerd,它照样可以运行并占用 socket。

5.2 配置目录和服务文件

  • /etc/docker/daemon.json:这是 dockerd 的核心配置。无论哪套安装,只要用了这个路径的配置文件,它都会生效。如果你看到/etc/docker/下有配置文件,但 systemd 服务是 disabled 状态,就要怀疑是不是有手动启动的 dockerd 进程在读取这个配置。
  • /etc/systemd/system/docker.service.d/:apt/官方源安装时可能存在的 override 目录。
  • /var/lib/docker/:默认的数据目录。如果你按数据目录大小判断,也可以看到是否真的有两个独立的数据目录,例如/var/lib/docker和/var/snap/docker/common/var-lib-docker。

snap 版 docker 默认数据目录在/var/snap/docker/common/var-lib-docker,和 apt 版的/var/lib/docker完全隔离。如果你发现两个目录都存在且都有大量数据,说明两套 docker 都实际工作过。这个信息很重要——如果你想卸载旧的那一套再迁移数据,就得弄清数据分别存哪。

5.3 检查包管理器的安装记录

用 apt 和 snap 分别查:

dpkg -l | grep -i docker snap list | grep -i docker

dpkg列表会展示 docker.io、docker-ce、docker-ce-cli、containerd.io 等包。注意docker.io是 Ubuntu 仓库自带的旧版,docker-ce是 Docker 官方源里的版本。如果两个同时存在,dpkg 会显示两个独立的包,但实际上docker-ce会替换/usr/bin/docker,而docker.io可能已经处于半卸载状态。查看包是否被接管:

dpkg -l | grep docker

出现rc状态(已删除但配置文件还在)的包不算真正的安装,不用管。但若出现两个版本都是ii(installed),就说明你真的装了两套基于 systemd 的 docker。同理,snap list 里看到 docker 就说明还有一套 snap 的。

5.4 再补充一个:用户态 rootless docker

Ubuntu 上还可能存在 rootless 模式的 docker,这种模式不会用 systemd 服务,也不用/var/run/docker.sock,而是在用户目录下:

ls -l ~/.docker ls -l /run/user/$(id -u)/docker.sock

如果你曾经用dockerd-rootless.sh启动过 rootless docker,那么系统里也会有一个 dockerd 进程,但它监听的 socket 路径是/run/user/1000/docker.sock之类。这时候你用默认的docker ps连的是系统级 docker,而那个 rootless docker 往往被忽略了。这也是“系统里有多个 docker”的一种隐藏形态。

6. 实际排查案例:一台 Ubuntu 22.04 上找出 3 套 Docker

为了让你更有体感,我把一次真实排查过程记录下来。机器是我的测试机,现象是重启后docker ps能列出容器,但systemctl restart docker再执行却提示“Unit docker.service not found”。这个现象本身就很典型:说明当前 shell 用的 docker 客户端不是 systemd 管理的那个。

第一步,确认系统里能看到的 docker 实体:

$ which docker /usr/local/bin/docker $ type -a docker docker is /usr/local/bin/docker docker is /usr/bin/docker docker is /snap/bin/docker

这里已经出现三个 docker 可执行文件。/usr/local/bin在 PATH 中优先级最高,所以 shell 默认调用了手动安装的二进制。

第二步,查看当前客户端连的 Server:

$ docker version Client: 20.10.17 Server: 20.10.3

Client 和 Server 都来自同一个二进制版本 20.10.17? 实际上 Client 显示 20.10.17 说明/usr/local/bin/docker是 20.10.17,Server 显示 20.10.3 说明它连接的 dockerd 是另一个版本,这个 dockerd 是 20.10.3,我推测是 snap 版或 apt 版。因为手动安装的二进制版本和系统里已有的 dockerd 不一定一致,两者之间只要 API 兼容就能工作。

第三步,检查 socket 监听进程:

$ sudo ss -xlp | grep docker u_str LISTEN 0 4096 /var/run/docker.sock 12345 users:(("dockerd",pid=9876,fd=3))

查看 pid=9876 来自哪里:

$ ps -p 9876 -o command= /usr/bin/dockerd

好,这个监听默认 socket 的是/usr/bin/dockerd,属于 apt 版本。

第四步,检查 snap 服务:

$ snap services docker Service Startup Current Notes docker.dockerd enabled active -

说明 snap 版也在运行,但 snap 的 dockerd 没有占用/var/run/docker.sock。snap docker 默认也绑到/var/run/docker.sock?这里涉及 snap 接口的 socket 连接,但实际通过ss只看到一个监听者,说明 snap 的 dockerd 监听的是自己的 socket 或者处于失败重试状态。进一步查:

$ ps aux | grep dockerd | grep -v grep root 9876 ... /usr/bin/dockerd root 5432 ... /snap/docker/current/bin/dockerd

最终结论:这台机器有 3 个 dockerd 进程?不,第二行ps只显示了一个/usr/bin/dockerd和一个/snap/docker/current/bin/dockerd。snap 的这个进程起来了吗?如果两个 dockerd 都监听同一个 socket 是不可能的,第二次绑定会失败。具体哪个是 slave?可以看进程状态,一个处于 active 状态监听 socket,另一个可能一直重试绑定 socket 或监听其他路径。

经过sudo lsns查看命名空间,发现 snap dockerd 被运行在一个独立的 mount 和 net namespace 中。这就是 snap 包特有的安全隔离:snap docker 的 dockerd 实际上跑在自己的命名空间里,所以它的/var/run/docker.sock和宿主机的/var/run/docker.sock不是同一个文件。这才是 snap 版 docker 能和平共处的核心机制——它把 socket 文件也隔离开了。但 snap 的 controller 接口会通过docker命令重定向到宿主机的 socket?不展开细说,因为不同版本的 snap docker 行为不太一样。我要强调的是:在宿主机视角看,跑着两个 dockerd,但在默认 socket 上只有一个监听者。

最终这台机器的情况就是:

  • /usr/local/bin/docker:手动安装的客户端二进制,优先级最高,平时敲 docker 用的就是它。
  • /usr/bin/dockerd:apt 安装的守护进程,监听着/var/run/docker.sock,是实际对外服务的。
  • /snap/bin/docker和 snap dockerd:snap 安装的完整套件,由于命名空间隔离,自成一派,和 apt 版互不干扰。但两个都存在,资源浪费,且后续升级混乱。

处理方式:卸载 snap 版,保留 apt 版,清理手动安装的/usr/local/bin/docker,让系统只保留一套标准路径的 docker。

7. 写一行脚本自动汇总:Ubuntu 系统里有几套 Docker

鉴于手动排查步骤多,我写了个简单的 Bash 脚本,可以一次性输出关键信息。你直接复制到服务器上执行即可,不依赖额外工具。

#!/bin/bash echo "===== 可执行文件路径 =====" type -a docker 2>/dev/null || which docker echo -e "\n===== 当前实际调用的 Docker =====" which docker echo -e "\n===== Docker 客户端与服务端版本 =====" docker version 2>&1 | sed -n '1,20p' echo -e "\n===== systemd 服务状态 =====" systemctl list-unit-files 2>/dev/null | grep -i docker systemctl status docker --no-pager 2>&1 | head -n 15 echo -e "\n===== snap 服务状态 =====" snap list 2>/dev/null | grep -i docker snap services docker 2>/dev/null echo -e "\n===== 监听中的 docker socket =====" sudo ss -xlp 2>/dev/null | grep -i docker || echo "未检测到 docker socket" echo -e "\n===== dockerd 进程 =====" ps aux | grep -E "[d]ockerd" || echo "无 dockerd 进程" echo -e "\n===== 常见数据目录 =====" sudo ls -ld /var/lib/docker /var/snap/docker/common/var-lib-docker 2>/dev/null

执行后你会得到一份汇总。我建议重点关注三块:

  1. type -a docker列出了几个路径,决定 shell 会调用哪一个。
  2. systemd和snap服务是否同时 active。
  3. ss -xlp里看到底是谁在监听默认 socket。

如果输出显示有多个可执行文件路径,或者 systemd 和 snap 服务同时存在,那就说明系统里不止一套 docker。定位到之后,你再按需清理保留目标版本。

8. 发现多套 Docker 之后怎么收敛成“只有一套”

8.1 先确定你想保留哪一套

大多数生产环境推荐保留 apt 官方源安装的 docker-ce,理由很直接:

  • systemd 管理,开机自启,日志集成方便。
  • 数据目录明确在/var/lib/docker。
  • 升级路径清晰,和 Ubuntu 原生的安全更新机制对齐。
  • 不需要 snap 自带的那一套隔离层,排查网络问题时少一个变量。

snap 版 docker 本身也不是不能用,但它在磁盘占用、网络隔离、日志查看上给我的体验都不如 systemd 版本直观,而且不少第三方监控脚本默认读/var/lib/docker数据,snap 版还要额外适配路径。

8.2 卸载多余版本的正确姿势

卸载 snap 版:

sudo snap stop docker sudo snap remove docker

注意保留数据目录的话,先手动备份/var/snap/docker/common/var-lib-docker。

卸载 apt 版:

sudo systemctl stop docker docker.socket sudo apt purge docker.io docker-ce docker-ce-cli containerd.io

手动二进制安装的清理:

sudo rm -f /usr/local/bin/docker /usr/local/bin/dockerd /usr/local/bin/docker-compose sudo rm -rf /usr/local/lib/docker

清理之后重新检查一次:

type -a docker which docker docker version systemctl status docker

如果一切正常,你应该看到可执行文件只有一个路径,systemd 服务是唯一的管理者,docker version的 Client 和 Server 版本一致或能对应到同一套安装。

提示:执行卸载前,建议用docker ps -a先看看现有容器是否保留。purge卸载 docker 不会自动删除/var/lib/docker里的容器数据,但如果你执行了apt autoremove或者手动清了目录,数据就没了。这种操作比卸载本身更危险。我的习惯是:把/var/lib/docker整个 tar 打包一份扔到备份盘再动包管理工具。

8.3 最后验证:从一个“干净状态”确认只剩一套

收敛完成后,我一般会做 10 秒验证:

docker version docker run --rm hello-world systemctl is-active docker

如果 hello-world 能正常跑起来,systemd 又是 active,这台机器就回归到标准状态了。之后你再排查任何 Docker 相关的问题,思路都不用绕弯。

9. 查“几个 docker”这个动作本身的价值

很多人觉得“系统里有几个 docker”是一个伪命题——不就是一条命令的事吗?实际经历过后你会发现,这背后的信息量非常大:它告诉你这台机器的 Docker 是怎么来的、由谁在管、数据存在哪、升级走什么通道、出了问题日志去哪看。这些信息平时用不到,一旦涉及排障、迁移、审计,就全是关键线索。

我自己现在拿到任何一台 Ubuntu 机器的第一件事就是跑一遍第 7 节那个脚本,花两分钟把 Docker 的“家庭背景”摸清楚。别等出了问题再查,那时候你已经站在别人踩过的坑里了。

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

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

立即咨询