☰
Ubuntu下Docker从安装到实战:镜像、数据卷与网络排查全攻略
2026/9/30 8:13:36 网站建设 项目流程

前几天帮朋友收拾一台Ubuntu服务器,发现他还停留在“一条apt命令装完就开跑”的阶段,装Docker Desktop时又弹出virtualization support not detected,折腾一晚上没起来。Ubuntu上跑Docker这件事,很多坑其实都出在没理清安装路径和运行机制。这篇文章我就按自己常用的路线从头顺一遍,覆盖Ubuntu 22.04 LTS下的Docker安装、镜像容器和数据卷的核心概念、MySQL与Redis这些高频实战场景,还有网络不通的排查思路。不管你是刚接触Linux的新手,还是在服务器上批量部署服务的老手,里面都有可以直接照做的部分。

1. 安装方式选型:为什么先别急着装Docker Desktop

1.1 三种主流安装路径的取舍

在Ubuntu上装Docker,常见路径其实就三条:直接用apt安装发行版自带的docker.io,添加Docker官方软件源之后安装docker-ce,以及在桌面环境下安装Docker Desktop。我通常的建议是:服务器上正经跑容器,果断选docker-ce;开发机上想要图形界面和跨平台一致的体验,再考虑Docker Desktop;而docker.io这条路径,能不用就不用。

docker.io的优势只有一条命令的简单,sudo apt install docker.io就能装完,不需要维护GPG密钥和软件源。但代价是版本滞后,在22.04上它对应的Docker引擎版本往往比官方新版落后好几个大版本,一些新特性、安全补丁和兼容性修复都享受不到。docker-ce是Docker官方维护的稳定分支,版本新、更新及时,官方源里同时提供了containerd和runc的配套版本,整体可靠度明显更高。

Docker Desktop的定位又是另一回事,它本质上是“Docker引擎+图形界面+虚拟化/WSL集成”的组合体。在Ubuntu桌面版上安装之后,你可以通过界面查看容器列表、镜像列表和日志面板,点几下鼠标就能完成启停操作,对不习惯命令行的用户非常友好。但它的体积大,还依赖systemd、GNOME等桌面组件,适合本机开发调试,不适合服务器。服务器的要求是轻量、稳定、可控,一个dockerd守护进程加上命令行工具就够了。

1.2 官方源安装docker-ce的完整步骤

以Ubuntu 22.04 LTS为例,24.04 LTS同样适用。第一步先更新APT索引并安装后续需要的基础工具:

sudo apt update sudo apt install -y ca-certificates curl gnupg

第二步添加Docker官方的GPG密钥,这一步的目的是让APT能够验证软件包来源的合法性。很多教程把密钥直接放到旧路径,在22.04上容易出警告,建议使用新的keyrings目录:

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

第三步写入软件源。这里用$(dpkg --print-architecture)自动获取本机架构,用$(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组件。我习惯把buildx和compose插件一起装掉,后面构建镜像和编排服务都省事:

sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

装完之后启动服务并验证。sudo systemctl enable --now docker这条命令同时完成开机自启和启动;然后执行sudo docker run hello-world,它会先检查本地有没有hello-world镜像,没有就从Docker Hub拉取。能正常打印Hello from Docker!说明引擎没问题。如果拉取超时,多半是网络或镜像源问题,后面会专门讲。

1.3 装完必做的三个配置

装好只是开始,我每次都会立刻做三件小事,能省掉后面一大堆的权限和磁盘问题。

第一件,把当前用户加入docker组,避免每次敲docker命令都要sudo:

sudo usermod -aG docker $USER

改完用户组之后要重新登录会话,或者执行newgrp docker刷新组权限,否则docker命令会一直提示permission denied。这个细节我见太多人栽过,其实不是docker没装好,是用户组没生效。

第二件,确认服务开机自启。用systemctl enable --now docker之后,一般会创建好符号链接。如果在桌面版环境里同时装了Docker Desktop,它有自己的自启机制,这时要注意别让两个引擎抢同一个socket,我后面会细说。

第三件,提前规划数据目录。我习惯在/etc/docker/daemon.json里指定data-root,把镜像和容器数据迁到大分区,避免根分区被撑爆,同时顺手把日志轮转限制加上:

{ "data-root": "/opt/docker/data", "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "3" } }

不少服务器的根分区默认只有几十G,容器日志不限制的话,跑几天就能吃掉几个GB。限制单个日志文件50MB、最多保留3个,是生产环境维护期最有用的配置之一。改完配置别忘了sudo systemctl restart docker,而且这个配置最好在刚装完、还没拉多少镜像的时候设置,数据目录迁移起来最省事。

2. 镜像、容器、数据卷:搞懂这三个东西就赢了一半

2.1 镜像和容器到底什么关系

很多人一开始会被镜像和容器这两个词绕晕,我用一个生活化的类比:镜像是一张光盘里的系统安装包,容器是光盘启动起来之后正在运行的电脑。镜像只读、不可变,容器则是镜像运行时的实例,有独立文件系统、可以从镜像启动,但运行时的修改不会写回镜像。

所以容器里删文件、改配置都不会污染镜像,这也是为什么容器适合跑“一次性”任务。但反过来,容器删掉之后,你在里面产生的所有数据也会一起消失。如果只是跑一个临时环境,无所谓;如果跑的是数据库、日志服务这类有状态应用,就必须把数据放到容器外面,这就引出数据卷。

这种机制带来一个思维方式上的转变:容器应该被当作牛羊而不是宠物。牛羊养大了可以杀掉换新的,宠物死了会心疼。Docker的使用习惯应该是“随时可以删掉容器重建”,环境全部声明式地写在镜像和启动命令里,而不是登录进容器里手动改来改去。

2.2 数据卷:把数据留在容器外面

数据卷的核心作用,是把容器内的目录映射到宿主机上,让数据不依赖容器的生命周期。最常用的写法是-v或者--mount参数。

docker run -d --name nginx -p 80:80 -v /opt/nginx/html:/usr/share/nginx/html nginx

左边/opt/nginx/html是宿主机目录,右边/usr/share/nginx/html是容器内目录。这样你在宿主机上改网页文件,容器里立刻就能看到;就算容器被删掉,数据还在。

对于数据库这类应用,我强烈建议把所有配置和数据目录都挂出来,否则容器一删,数据全没。MySQL的/var/lib/mysql、Redis的/data、PostgreSQL的/var/lib/postgresql/data,都是需要重点挂载的目录。挂载目录时还有一个容易踩的坑:宿主机目录的属主和权限要提前想好,容器里的进程通常以特定UID运行,挂载目录权限不对会报Permission denied,这也是后面MySQL实例中常见的启动失败原因。

2.3 常用命令速记:会这些就能干活了

日常使用中,我不喜欢背一大堆手册,真正高频的就下面这些。选镜像用docker pull,看本地镜像用docker images,起容器用docker run,看运行状态用docker ps,看日志用docker logs -f,进容器用docker exec -it。

以下几个参数最常用:-d表示后台运行,-p做端口映射,-v做数据挂载,-e设置环境变量,--name给容器命名,--restart=always设置重启策略。对于有依赖关系的多个容器,推荐用docker compose文件统一管理,把端口、环境变量、挂载、网络写成一份YAML,项目重建时一条docker compose up -d就搞定,不用记一长串参数。

我还习惯用docker inspect和docker logs这两个排查命令,遇到容器起不来的情况,先看docker logs -f <容器名>拿到确切报错,再用docker inspect <容器名>查状态、挂载和网络配置,比瞎猜高效得多。说白了,Docker的命令体系并不复杂,掌握十几条就能覆盖日常80%的需求。

3. 三个高频实战场景:MySQL、Redis、Python

3.1 用容器部署MySQL 8.0并持久化数据

以MySQL 8.0为例,这是网上问得最多的一个。先准备目录:

sudo mkdir -p /opt/mysql/{data,conf}

然后启动容器,注意这串参数里每个都不能少:

docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=YourStr0ngPass \ -v /opt/mysql/data:/var/lib/mysql \ -v /opt/mysql/conf:/etc/mysql/conf.d \ --restart=always \ mysql:8.0 \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_unicode_ci

这里有几个关键点:MYSQL_ROOT_PASSWORD是首次初始化时设置root密码的环境变量;挂载两个目录分别对应数据文件目录和自定义配置目录;最后的两个参数是追加给mysqld的启动参数,强制使用utf8mb4字符集,避免中文乱码。

启动后用docker logs -f mysql8观察初始化日志,出现ready for connections说明就绪。接着进容器验证:

docker exec -it mysql8 mysql -uroot -p

输入密码后执行show variables like 'character%';,可以看到字符集相关变量基本都是utf8mb4,就说明配置生效。

常见失败原因有两个:一是宿主机3306端口被占用,改映射端口即可;二是挂载目录权限不对导致mysqld无法写临时文件,日志里会明确报错。遇到权限问题,可以先用docker run --rm -it mysql:8.0 bash进一个临时容器,查一下镜像里mysql用户的UID,再用chown把宿主机目录属主改成一样的。

3.2 Redis主从复制:一主两从怎么搭

主从复制常用于读写分离和数据备份。先用docker network create redis-net创建一个自定义桥接网络,让三个容器用容器名互访,而不是依赖每次变化的IP地址。

docker run -d --name redis-master --network redis-net -p 6379:6379 --restart=always redis:7 redis-server --appendonly yes docker run -d --name redis-slave1 --network redis-net --restart=always redis:7 redis-server --replicaof redis-master 6379 docker run -d --name redis-slave2 --network redis-net --restart=always redis:7 redis-server --replicaof redis-master 6379

重点在于--replicaof后面跟的是容器名redis-master,而不是IP。在自定义网络里Docker内置DNS会将容器名解析成对应地址,这样主节点重启换IP也不影响从节点连接,这是很多人容易忽略的一点。

验证主从状态:

docker exec -it redis-slave1 redis-cli -p 6379 info replication

看输出里role:slave、master_link_status:up即可。如果状态是down,大多数情况下是从节点连不上主节点,可以用docker logs -f redis-slave1看日志,或者用docker network inspect redis-net检查容器是否都在同一网络里。

从节点默认是只读的,配置文件里的replica-read-only yes保证数据不能被写入。如果业务需要从节点接受读请求,客户端也要做读写分离配置。这个场景很适合用来理解Docker网络的作用:同网络内容器之间用名字通信,跨网络则需要端口映射或路由。

3.3 运行Python环境与部署微服务

跑Python临时环境是容器最直接的福利之一,一条命令就能获得一个干净、可复现的Python工作区:

docker run -it --rm -v $(pwd):/app -w /app python:3.11-slim bash

这句话的意思是:以交互模式启动一个python:3.11-slim容器,把当前目录挂载到容器内/app,工作目录设为/app,进去就是bash,代码在宿主机改,容器里直接跑。--rm表示退出即删除容器,很适合日常临时调试,不会堆积一堆停止状态的容器。

如果要部署微服务,我会写一个简洁的Dockerfile:

FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple COPY . . CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

镜像构建使用docker build -t my-service .,启动用docker run -d --name my-service -p 8000:8000 --restart=always my-service。

这里想提醒一个新手最容易踩的坑:CMD里写的地址必须是0.0.0.0,而不是127.0.0.1。容器内部是一个隔离的网络命名空间,如果服务监听127.0.0.1,映射出去的端口永远连不通。这个坑在各类应用都有,不只是Python。微服务之间的调用,则建议放在同一个自定义网络里,用服务名互相访问,记得把--network参数加上。

另外,很多slim镜像默认不带编译器,凡是需要pip编译C扩展的项目,构建阶段必须apt-get update && apt-get install -y build-essential,否则pip install会报缺少gcc。这个错误在热词里也经常出现,成因基本都是镜像太精简。

4. Docker网络不通怎么办?一份排查实录

4.1 先定位是哪一层不通

网络问题千奇百怪,但归纳下来就三种:容器访问外网不通、宿主机访问容器端口不通、容器之间互访不通。这三种问题的排查方向完全不同,先别急着改防火墙,第一步应该确认现象到底属于哪一种。

容器访问外网不通,典型表现是容器里apt update或curl外网地址超时,但宿主机本身网络正常。这种情况优先检查宿主机上是否开启IP转发,因为容器对外访问要经过NAT,而NAT依赖内核的net.ipv4.ip_forward开关。可以用sysctl net.ipv4.ip_forward检查,输出0就打开它,写入/etc/sysctl.conf后执行sysctl -p生效。

宿主机访问容器端口不通,需要按链路拆开看:端口映射有没有绑定正确、容器内服务有没有监听、docker-proxy或iptables规则有没有生效。我先用docker port <容器名>看映射结果,再用curl -v测试,最后进容器用ss -lnt看服务是否真的在监听。定位到具体环点再动手,往往比盲目重启快得多。

容器之间互访不通,一般集中在两点:两个容器不在同一个网络,或者自定义网络模式下防火墙规则有残留。用docker network inspect <网络名>看容器IP和归属,基本就能判断是不是网络选错了。

4.2 我常用的一套排查命令

排查的时候我有一套固定的动作,按顺序执行,能快速缩小范围。先把所有网络和容器状态看一遍:

docker network ls docker ps -a docker inspect <容器名> | grep -A10 "NetworkSettings"

然后查看iptables的NAT规则,Docker的端口映射本质上就是DNAT规则,如果规则缺失,映射就不生效:

sudo iptables -L -n -t nat sudo iptables -L -n -t filter

再检查IP转发和网卡状态:

sysctl net.ipv4.ip_forward ip addr show docker0

如果docker0网卡不存在,说明docker服务本身没正常初始化,这时候要去看dockerd日志:journalctl -u docker -n 50。

一个容易被忽略的点是,宿主机人为改了防火墙策略,比如用了ufw并启用了默认拒绝,会让容器流量被拦。Ubuntu上常见做法是把/etc/default/ufw里的DEFAULT_FORWARD_POLICY改成ACCEPT,或者干脆在部署Docker的机器上不启用ufw,让Docker自己管理iptables规则。两种策略都有讲究,重点是别让两套防火墙规则互相打架。

4.3 几个我踩过的网络坑

第一个坑是改/etc/docker/daemon.json里的bip字段后容器网络异常。bip用于自定义docker0网桥地址,如果设置成和局域网网段重叠,路由和DNS都会出问题。个人经验是,bip只在你明确需要变更内网网段时才去动,默认172.17网段几乎不需要改。

第二个坑是systemd环境下手动用iptables-restore覆盖了规则,导致所有容器网络瞬间断开。因为Docker会在iptables里维护自己的规则链,你手动恢复规则时很容易把FORWARD链的规则覆盖掉。相关操作一定要先备份现有规则,再增量修改。

第三个坑是热词里经常出现的“docker网络不通”,很多其实不是网络问题,而是容器没起来。一些镜像在启动时会把健康检查的端口绑错,或者entrypoint脚本需要等待依赖,日志里反复重启,外部看起来就像“网络不通”。所以每次排查网络问题,我都会顺手看一眼docker ps和docker logs,把服务状态先确认一遍,再聊网络。

5. Docker Desktop的坑

5.1 virtualization support not detected

这个报错多出现在Windows上安装Docker Desktop,或是在VMware里装Ubuntu后又想跑Docker Desktop的场景。Docker Desktop在Windows上依赖Hyper-V或者WSL2,虚拟化功能没有启用时,启动就会直接弹virtualization support not detected。

如果是Windows物理机,通常解决办法是去BIOS开启CPU虚拟化,然后到“启用或关闭Windows功能”里勾选Hyper-V和“适用于Linux的Windows子系统”,重启后重新启动Docker Desktop。注意Windows 10家庭版没有Hyper-V,一般走WSL2方案更顺。进入系统后执行bcdedit检查hypervisorlaunchtype状态,如果是off也会导致检测不到虚拟化。

在VMware虚拟机里跑Ubuntu,再在Ubuntu里装Docker Desktop,就涉及“虚拟机套虚拟机”了。VMware的虚拟机里嵌套使用Hyper-V或KVM需要开启虚拟化引擎的“向客户机操作系统公开硬件辅助虚拟化”选项,同时客户机CPU要确认能看到vmx或svm标志。简单判断方法是在Ubuntu里执行lscpu | grep -i vmx,有输出说明嵌套虚拟化可用,没有的话就老老实实回到命令行docker-ce方案,完全不影响使用容器。

5.2 failed to connect to the docker api

另一个高频报错是failed to connect to the docker api at npipe:////./pipe/dockerDesktopLinuxEngine,几乎每个新装Docker Desktop的用户都会遇到一次。这个错误的字面意思是Docker客户端连不上Docker引擎,常见于引擎还没启动完、引擎启动失败,或者客户端与引擎的版本不匹配。

处理思路很简单:先确认Docker Desktop右下角的状态图标是否变成运行的鲸鱼,再在终端执行docker version,分别看client和server两段是否都有输出。如果只有client没有server,说明引擎没起来,去设置里看日志,或者执行wsl --shutdown后重新启动Docker Desktop。

如果server有了,但执行命令仍然报npipe连接失败,多半是DOCKER_HOST环境变量被设置成旧的地址了,Ubuntu下一般是/var/run/docker.sock,Windows下是npipe。检查一下当前终端echo $DOCKER_HOST,有值就先unset,或者改成docker-desktop对应的URL。这个报错还经常在系统重启后出现,因为Docker Desktop的引擎需要用户登录后才启动,而脚本里提前执行了docker命令。

5.3 在虚拟机里跑Docker的路线选择

在VMware里装Ubuntu,然后想用Docker,我建议直接装docker-ce,而不是Docker Desktop。虚拟机本身已经有了一层虚拟化,再做嵌套虚拟化不仅配置麻烦,性能也有额外开销,除非你的目标就是想体验Docker Desktop的图形界面。

虚拟机里跑docker-ce,需要注意VMware的网卡设置为NAT模式或桥接模式都可以,只要宿主机能上网,虚拟机里的容器就能上网。有时候容器外网不通,反而是因为VMware虚拟网卡的DNS配置有问题,在/etc/resolv.conf里临时加上可用DNS即可测试。另外虚拟机的内存建议分配4GB以上,docker跑几个容器加上构建任务,2GB明显捉襟见肘。

至于热词里提到的“docker desktop 汉化包”,个人不建议装,Docker Desktop界面本身路径固定、词条不多,装第三方汉化包反倒可能因为版本不匹配出现界面异常。把接口和命令行工具用顺手,效率可能更高。

6. 常见问题速查与长期使用心得

6.1 高频问题速查表

这里整理一张表,把标题相关的最常见问题、原因和解决办法列出来,直接收藏照着排查就行。

问题现象常见原因解决办法
docker命令提示permission denied用户不在docker组sudo usermod -aG docker $USER后重新登录
docker run hello-world拉取超时默认Docker Hub源不稳定在daemon.json配置registry-mirrors或更换可达镜像源
容器启动一会就退出前台进程退出,或入口脚本报错用docker logs -f查看错误日志,确认CMD是否以前台方式运行
宿主机连不上容器映射端口服务监听127.0.0.1或iptables规则异常服务内绑定0.0.0.0,检查docker port映射和NAT规则
MySQL容器启动失败挂载目录权限不对或端口占用查看日志确认报错,调整目录属主或换端口重新映射
容器之间互访不通不在同一自定义网络创建自定义网络并让容器加入,用容器名通信
virtualization support not detectedBIOS未开虚拟化或权限不足开启硬件虚拟化,Windows启用WSL2/Hyper-V
failed to connect to the docker api引擎未启动或DOCKER_HOST错误等待引擎启动,检查docker version,清理环境变量
安装Ubuntu子系统报0x80070424Windows Update服务异常重启wuauserv服务并重新安装WSL更新

这张表是我实际维护多台机器时的浓缩版,基本覆盖了网络热词里那些常见报错。遇到问题先对号入座,能省下大量搜索时间。

6.2 我在长期使用中沉淀的几点心得

装Docker不是目的,把容器用起来才是。我自己的使用习惯是:凡是跑在服务器上的中间件,一律用Docker Compose维护,一份compose文件同时定义镜像版本、端口、挂载、重启策略和环境变量,换机器时复制文件跑起来就能恢复。凡是临时调试,一律用docker run --rm,用完即焚,不留下垃圾容器。

日志管理方面,尽早配置daemon.json里的日志轮转,是对所有容器一视同仁的兜底策略。生产环境里我看过太多因为日志文件写到上百GB导致磁盘满的案例,容器本身没问题,纯粹是日志没限制,这个配置一定要优先落好。

最后想提醒的是,不要因为容器方便就把服务器上的传统服务全部容器化。有些系统服务、内核模块相关的任务并不适合塞进容器,遇到这类需求时,先想清楚隔离边界在哪里。Docker的价值在于让应用交付和运行环境变得可复用,该用的时候用的彻底,不该用的时候别硬上,这是我在Ubuntu和Docker打交道这几年最深的一点体会。

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

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

立即咨询