我第一次正经在 Ubuntu 上装 Docker,是在某个加班的晚上。当时照着网上的帖子改 daemon.json,填了一堆 registry mirror,结果sudo docker info直接报错,Docker 服务彻底起不来。那时候我连“镜像”和“镜像加速器”都分不清,只知道复制粘贴,最后只能把整个 Docker 卸载重装,才算救回来。
后来把 Ubuntu + Docker 这对组合用了两三年,回过头看,入门阶段真正挡人的根本不是命令记不住,而是脑子里没有一个完整的模型:镜像到底是什么,容器和虚拟机的差别在哪,数据卷为什么要单独挂出来,端口映射到底映射了什么。这篇文章打算一次性把这些讲透,顺便把我踩过的坑也列出来。如果你刚接触 Linux,或者之前在 Windows 上只用过 Docker Desktop,这篇内容会比较对胃口。
我会把安装步骤、常用命令、一次完整的实战部署,还有新手翻车最频繁的五个场景全部串起来讲。跟着走一遍,你对 Docker 的认识会比单纯看文档清楚很多。
1. 为什么偏偏是 Ubuntu:学 Docker 之前的环境选择题
很多人学 Docker 的第一个问题不是“怎么装”,而是“装在哪”。Docker 虽然也支持 Windows 和 macOS,但你只要在 Ubuntu 上体会过一遍容器启动的速度,就回不去了。这不是心理作用,是底层机制决定的。
1.1 Linux 才是 Docker 的“原乡”,内核能力决定起点
Docker 的核心是 Linux 内核的 namespace 和 cgroups 两项能力。namespace 负责隔离:进程、网络、文件系统、用户,每一层都能独立成空间,让容器里的进程误以为自己独占一台机器;cgroups 负责限制:CPU、内存、磁盘 IO,都能精确管控。
Windows 和 macOS 没有这两个内核机制,Docker 只能套一层虚拟机兜底。以 macOS 为例,Docker Desktop 后台其实跑着一个轻量 Linux 虚拟机,所有容器都生存在那台虚拟机里。一旦做了这层中转,文件挂载、端口映射、网络通信都会多一道开销,你在测试时偶尔会碰到“Windows 上正常,放进容器就变慢”的情况。在原生 Ubuntu 上,Docker 直接调用宿主机内核,没有中间层,行为更干净,排错也更简单。
这也是为什么你搜 Docker 相关的论坛帖子,大量解决思路都默认“你是 Linux 环境”。学 Docker 选 Ubuntu,等于站在了官方主推的主场,二手信息少得多。
1.2 实体机、双系统、虚拟机、WSL2,到底选哪个
搞清楚原理之后,还是要落地。我按实际体验给四种方案排个序,适合不同情况的人。
| 方案 | 上手难度 | 性能 | 适合人群 | 注意点 |
|---|---|---|---|---|
| 实体机安装 Ubuntu | 中 | 最好 | 准备长期用 Linux 做主力 | 安装前备份数据,驱动确认清楚 |
| 双系统 | 中高 | 好 | 既不想放弃 Windows 又想有原生环境 | 分区要留够,引导容易出问题 |
| VMware/VirtualBox 虚拟机 | 低 | 一般 | 纯体验、怕折腾 | 要开启嵌套虚拟化,磁盘别用动态容量到底 |
| WSL2 | 低 | 较好 | 手头是 Windows,想快速起步 | 文件跨系统读写慢,别把项目放在 /mnt/c 下 |
个人建议很直接:如果你是学生或开发者,愿意接受一段时间的不方便,直接用实体机装 Ubuntu,收获最大。如果只是想知道 Docker 是什么,虚拟机或 WSL2 足够了,没必要为了学容器专门重装系统。
用 WSL2 的话,有一个细节容易被忽略:项目文件尽量放在 WSL 的 Linux 文件系统里,比如~/projects,不要放在/mnt/c/Users/...下。放 Windows 盘会导致容器挂载和文件读写明显变慢,这个坑我见很多人踩过。
1.3 安装 Ubuntu 时提前埋好的几个“免坑设置”
既然决定在 Ubuntu 上学 Docker,那装系统时就要为 Docker 做准备。我第一次装 Ubuntu 时,分区只分配了 60GB,用半年后 Docker 镜像一多,磁盘直接满了,非常尴尬。
- 磁盘分区给足:建议至少 80GB,如果你打算用 Docker 跑 GitLab、数据库这类大型服务,给到 150GB 以上更从容。Docker 默认把数据放在
/var/lib/docker,所以根分区大就是硬道理。 - swap 不能省:容器里跑编译任务,内存不够时 swap 能救急。重装系统时单独分一个 swap 分区,或者用 swapfile,8GB 内存配 4GB swap 起步。
- 安装时选择“最小安装”:装完系统干净清爽,极大的程序可以后面再补。桌面环境之后跑 Docker 占用的资源也更少。
- 配置好软件源再动手:安装完成后第一件事就是把
/etc/apt/sources.list换成速度快的软件源,否则后面apt update能卡到你怀疑人生。
这些坑不解决,你的 Docker 学习之路会从“装系统”就埋下隐患。环境一旦乱掉,后面排查问题时分不清是 Docker 的问题还是系统的问题,那才是真的折磨。
2. 镜像、容器、仓库:先搞懂三个核心词,再谈操作
Docker 的概念体系里,最基础也最容易混淆的是三样东西:镜像、容器、仓库。很多时候命令记不住,就是因为这三个词背后的关系没建立起来。我试着用最朴素的方式讲清楚。
2.1 镜像不是安装包,是“只读模板”
传统认知里,装软件要下载安装包,运行后变成安装在系统里的程序。Docker 镜像完全不是这个逻辑。
镜像是一个只读的、分层存储的文件系统模板。你可以把它理解成做甜品的模具:不管你用它做多少次,模具本身不变,得到的每个成品都长得一样。镜像里已经打包好了运行一个软件所需的代码、运行时、系统库、环境变量和默认配置。
镜像还有一个特性:分层。每一层都在上一层之上做增量修改,Docker 拉取镜像时如果本地已有相同层,会直接复用,不重复下载。这就是为什么你拉很多镜像后,du -sh /var/lib/docker显示的大小不等于各个镜像大小之和,底层共享层已经被合并计算了。
2.2 容器是镜像的“运行实例”,本质是进程
当你用docker run基于某个镜像启动一个容器时,Docker 会在镜像的只读层之上,生成一个可写层,然后在独立的命名空间里启动进程。同一个镜像可以同时启动十个容器,它们彼此隔离,各自的写入互不干扰。
这里要扭转一个根深蒂固的理解:容器不是迷你虚拟机。虚拟机里有完整的操作系统,而容器只是一个进程,它共享宿主机的内核,只不过通过 namespace 让自己看起来拥有一套独立的系统环境。
理解了这个,很多命令就顺了。比如docker exec -it 容器名 bash,本质是往那个进程所在的命名空间里再塞一个进程;docker stop是给容器的 PID 1 进程发 SIGTERM 信号;容器里的主进程一退出,容器就进入 exited 状态。这套逻辑跟“进程”完全一致,而不是跟“虚机开机、关机”一致。
2.3 仓库和标签:镜像的“GitHub”
镜像要分发,就得有个地方存。Docker Hub 就是默认的公共镜像仓库。仓库名由命名空间和仓库名组成,例如library/nginx里的library是官方账号的命名空间,nginx是仓库名。标签则对应版本,比如nginx:1.27、nginx:latest,不写标签默认拉 latest。
docker pull nginx和docker pull nginx:latest是等价的,但不建议在生产环境依赖 latest,因为它的指向会随官方更新而变化。代码里也好,Compose 文件里也好,最好写明确的具体标签,保证每次拉取的行为可预期。
2.4 用一个例子验证你是否真的懂了
来做一个简单的脑筋急转弯:执行docker run -d --name web -p 8080:80 nginx后,发生了什么?
完整过程是这样的:Docker 客户端先在本地找nginx:latest镜像,没有就去仓库拉取;拉下来后基于这个镜像创建容器,容器启动 nginx 主进程,监听 80 端口;-p 8080:80把宿主机的 8080 端口流量转发到容器的 80 端口;-d让它后台运行;最终你访问宿主机 IP 的 8080 端口,就能看到 nginx 欢迎页。
如果你能把这个过程完整复述出来,说明你基本理解镜像和容器的关系了。如果你脑子里是“在容器里装了个 nginx”,那理解还需要调整:nginx 早就在镜像里了,容器只是在跑它。
3. Ubuntu 下安装 Docker 的完整过程与安装后的必改项
进入实操环节。Ubuntu 装 Docker 有几种路径,我推荐直接用官方 apt 仓库安装,这种方式的后续升级体验最好,也方便你用 systemd 管理服务。
3.1 安装方式对比:不要一上来就用脚本
常见的安装方式有四种:官方 apt 仓库、Convenience script 脚本、手动下载 deb 包、Docker Desktop。前三者都是社区版 Docker Engine,本质相同,但有细微差别。
| 方式 | 优点 | 缺点 |
|---|---|---|
| 官方 apt 仓库 | 升级方便、可用 systemctl 管理 | 需要手动添加 GPG key 和源 |
| Convenience script | 一条命令装完 | 官方不推荐生产环境用;遇到错误难排查 |
| 手动下载 deb | 可离线安装 | 依赖要自己处理,升级麻烦 |
| Docker Desktop | 带图形界面,开箱即用 | 虚拟机化,性能有损耗;个人试用免费但涉及授权细节 |
我个人建议老老实实走官方 apt 仓库,你还能借着安装过程理解 Docker 各组件的构成。很多教程让你直接复制脚本,装完出了问题你连 Docker 由哪几个进程组成都不知道,很被动。
3.2 从官方 apt 仓库安装 Docker Engine 的详细步骤
以下命令兼容当前主流 Ubuntu 版本(20.04 到 24.04)。先更新索引,并安装依赖包:
sudo apt update sudo apt install -y ca-certificates curl gnupg lsb-release添加 Docker 官方 GPG key:
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 软件源。下面这段根据你的 Ubuntu 版本动态生成配置,复制即可:
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:
sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin注意我特意加上了docker-buildx-plugin和docker-compose-plugin。新版 Docker 把 BuildX(增强镜像构建)和 Compose(多容器编排)都插件化了,不装的话后面跑docker compose会提示缺命令。
安装完成后查看服务状态:
sudo systemctl status docker看到 active (running),就可以跑sudo docker run hello-world验证。能打印出那句 “Hello from Docker!”(请以实际输出为准),环境基本就通了。
3.3 配置镜像加速器:提升体验的关键一步
这一步在搜索引擎里非常常见,理由也简单:默认的 Docker Hub 域名在国内访问不稳定,直接拉镜像经常超时。配置镜像加速器的本质,是让 Docker 在拉取公共镜像时,优先从更快的镜像源获取层数据。
修改/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然后sudo docker info,在输出里找Registry Mirrors字段,确认配置已经生效。
注意,镜像加速器的可用性随时间和网络环境变化很大。你配置了几个都不稳定时,多试几个公开源。这类源本质是公共缓存,并不保证所有镜像都一定加速成功,遇到某个镜像拉不下来的情况,换个源再试是常规操作。
3.4 让普通用户免 sudo 运行 Docker
每次敲sudo docker很烦,而且会带来一个隐患:用 sudo 运行时容器文件归属 root,你在宿主机的普通用户目录下想删容器日志或数据卷文件时,会碰到 Permission denied。
解决方案是把当前用户加入 docker 用户组:
sudo usermod -aG docker $USER newgrp docker重新登录终端后,直接敲docker version不需要 sudo 了。之所以能这样,是因为 Docker CLI 本质是通过/var/run/docker.sock这个 Unix Socket 与 Docker 守护进程通信,docker 用户组对该 socket 有访问权。
这里我要多说一句:加入 docker 用户组的用户,权限上等同于宿主机 root,因为 Docker 提供了挂载宿主机目录进容器的能力。你的机器如果只有自己用,没问题;如果在团队服务器上,给用户加 docker 组要谨慎,这不是普通“组权限”这么简单。
3.5 开机自启与服务管理
Ubuntu 上用 apt 安装的 Docker 一般默认已设置开机自启,但保险起见可以执行:
sudo systemctl enable docker sudo systemctl enable containerd后面排查问题时,常用的管理命令也一并记住。sudo systemctl stop docker停机,sudo systemctl start docker启动,sudo systemctl restart docker重启。跑 Kubernetes 或一些复杂容器环境时,containerd服务也可能需要单独重启,别把它忘了。
4. 学 Docker 先练熟这些命令:容器生命周期与日常操作
命令不需要背,但要建立“命令对应场景”的直觉。我按容器从创建到销毁的整个生命周期来讲,每一条都有明确的使用时机。
4.1 容器生命周期:run/start/stop/rm 的状态流转
创建并启动一个后台运行的 nginx 容器:
docker run -d --name web -p 8080:80 nginx:1.27这条命令里的-d表示 detached 后台运行,--name web给容器起名,-p 8080:80做端口映射。如果不加-d,容器会以前台方式运行,你 Ctrl+C 退出时容器也跟着停止,适合调试用。
容器启动后,查看当前状态:
docker psdocker ps只显示运行中的容器,要看所有状态加-a。停止、启动、重启容器分别对应:
docker stop web docker start web docker restart web删除容器,必须先在停止态或强制删:
docker rm web docker rm -f webdocker rm -f相当于先强制停止再删除。这里注意一个新手常犯的错:改了容器里的配置或代码,执行docker restart后一切恢复正常,就以为任务完成。实际上容器内的可写层还是原来的,真正的配置修改通常需要重新构建镜像或挂载配置文件,restart 只是重新执行启动命令。
4.2 进入容器与查看日志:exec/logs/attach 的差异
容器运行起来后,你常常需要进去看进程、看环境变量、手动执行命令。常用的是 exec:
docker exec -it web bash-it是由-i(交互式保持标准输入)和-t(分配伪终端)组成,缺一不可。进容器后可以用ps aux查看进程,echo $VAR查看环境变量,Ctrl+D 退出。
docker attach 也能进入容器,但它直接附着到容器的主进程上,效果等同于“接上了容器的标准输入输出”。对交互型程序(比如 redis-cli)有特定用途,但对多数 Web 类容器,attach 进去按 Ctrl+C 会直接把容器主进程杀掉,危险且不实用。日常建议只用 exec。
看容器日志是排错高频操作:
docker logs web docker logs -f web # 持续跟踪输出 docker logs --tail 50 web # 只看最后 50 行-f这个参数要重点记住,跟 Linux 下的tail -f完全一个思路。有些容器没写日志到 stdout,而是写到容器内文件,这时需要docker exec进去看,或把日志目录挂载出来。
4.3 端口映射和数据卷:容器与外界互通的两种方式
容器默认有一个隔离的网络空间,外部无法直接访问。端口映射就是把宿主机端口和容器端口绑定,翻译成人话:发给宿主机 8080 端口的请求,Docker 原样转发给容器里 80 端口正在监听的进程。
再看数据卷。容器一旦删除,里面所有写入内容都没了,这符合“不可变基础设施”的理念。要想数据持久化,必须挂数据卷。三种方式对比如下:
| 方式 | 写法 | 数据位置 | 场景 |
|---|---|---|---|
| 命名卷 | -v mydata:/var/lib/mysql | /var/lib/docker/volumes/mydata | 推荐,Docker 管理 |
| 绑定挂载 | -v /home/user/app:/app | 宿主机指定路径 | 开发调试,改代码即生效 |
| tmpfs 挂载 | --tmpfs /tmp | 内存 | 只要临时数据,不落盘 |
用起来很简单:
docker run -d --name db \ -v dbdata:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ mysql:8.0这个-v dbdata:/var/lib/mysql就是典型的命名卷用法,容器删掉重来,数据还在卷里。
4.4 镜像管理:pull/images/rmi/tag 的日常配合
镜像相关的命令没有容器那么繁琐,但有一个点要特别说明:镜像标签是可以随意起的,不要把 tag 当作唯一的身份标识。
docker pull mysql:8.0 docker images docker rmi mysql:8.0 docker tag mysql:8.0 my-mysql:backupdocker tag只是给镜像加了另一个名字,不会复制镜像数据。docker rmi删除的是镜像的标签引用,如果多个标签指向同一个镜像 ID,删除一个标签不会释放空间,镜像仍在。要真正释放空间,得把所有关联标签都删掉,或者用docker image prune清理悬空的镜像层。
导出导入镜像也偶尔用得到:
docker save -o mysql.tar mysql:8.0 docker load -i mysql.tarsave/load适合内网环境迁移镜像,但镜像一般很大,大量镜像最好还是用私有仓库分发。
4.5 常用命令速查表
| 场景 | 命令 |
|---|---|
| 运行容器 | docker run -d --name app -p 8080:80 nginx |
| 列出容器 | docker ps / docker ps -a |
| 停止容器 | docker stop app |
| 删除容器 | docker rm -f app |
| 进入容器 | docker exec -it app bash |
| 查看日志 | docker logs -f app |
| 构建镜像 | docker build -t myapp . |
| 删除镜像 | docker rmi myapp |
| 清理无用资源 | docker system prune -a |
| 查看资源占用 | docker stats |
docker stats是排查性能问题的利器,能看到每个容器的 CPU 和内存实时占用,新手尽早学会看它,能省很多日子找“为什么电脑变卡”的时间。
5. 一次完整的实战:用 Docker Compose 部署带数据库的 Web 应用
光练命令不够,得有一个串起来的实战。我选一个最经典、也最能覆盖 Docker 核心场景的组合:WordPress + MySQL,用 Docker Compose 一条命令全部起来。你自己玩的时候可以替换成任何技术栈,思路完全一致。
5.1 先选型再动手:最小可用的容器化方案
我故意选 WordPress,因为它不需要你写任何代码,却能把环境变量、数据卷、端口映射、服务依赖四个核心概念全部演示一遍。
最终架构是两台容器:MySQL 8.0 负责存数据,WordPress 应用容器读取数据库并对外提供 Web 服务。WordPress 容器启动时要连数据库,所以依赖 MySQL 先就绪。
在项目目录下创建一个docker-compose.yml:
services: db: image: mysql:8.0 container_name: wp-db restart: always environment: MYSQL_DATABASE: wordpress MYSQL_USER: wpuser MYSQL_PASSWORD: wppass MYSQL_ROOT_PASSWORD: rootpass volumes: - dbdata:/var/lib/mysql networks: - wpnet app: image: wordpress:6.4 container_name: wp-app restart: always depends_on: - db ports: - "8080:80" environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wpuser WORDPRESS_DB_PASSWORD: wppass WORDPRESS_DB_NAME: wordpress volumes: - wpdata:/var/www/html networks: - wpnet volumes: dbdata: wpdata: networks: wpnet:这个 Compose 文件信息量很大,拆开看几个关键点。
db服务里那个MYSQL_DATABASE、MYSQL_USER、MYSQL_PASSWORD环境变量,是 MySQL 官方镜像首次初始化数据库时使用的。镜像会自动执行一段初始化脚本,根据这些变量创建数据库和账号。MYSQL_ROOT_PASSWORD则是 root 用户的密码,本地测试可以随意,上生产必须换强密码。
app服务端口映射写的是"8080:80",这意味着你访问宿主机的 8080 端口就是访问容器里的 WordPress。还有个不起眼的restart: always,它的意思是容器异常退出时 Docker 会自动拉起,这是生产部署里非常实用的配置。
depends_on只保证 db 容器先启动,不保证数据库已经就绪。实际遇到 WordPress 容器先启动连不上数据库的情况,别慌,重启 app 容器就行。这属于编排工具都存在的“有状态等待”问题,生产环境要彻底解决需要上健康检查,入门阶段知道现象即可。
5.2 启动与验证:一条命令看到完整应用
在docker-compose.yml所在目录执行:
docker compose up -d-d让所有服务后台运行。看到Started或 container created/running 状态说明启动成功。查看状态:
docker compose ps然后在浏览器访问http://你的服务器IP:8080,能看到 WordPress 安装界面就说明整套部署已经通了。别急着点安装,先验证数据持久化:
docker compose down docker compose up -ddocker compose down会删除容器,但因为数据卷还在,再次启动后 WordPress 的配置和文章应该全部还在。能通过这个验证,就说明你对数据卷的理解到位了。
5.3 数据备份与恢复:持久化之后的必修课
容器化部署后,数据备份的方式变了,你不再是去登录机器拷贝 /var/lib/mysql 目录,而是借助容器来帮你导出。
MySQL 数据导出:
docker exec wp-db sh -c 'exec mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" wordpress' > wordpress_backup.sql恢复时,把备份文件拷入容器再导入:
cat wordpress_backup.sql | docker exec -i wp-db mysql -u root -p"$MYSQL_ROOT_PASSWORD" wordpress这种做法的优雅之处在于,mysqldump命令在镜像本身就存在,你不必在宿主机再装一份 MySQL 客户端,也不会污染宿主机的软件环境。
5.4 应用升级的正确姿势
WordPress 或 MySQL 发布新版本后,升级操作有讲究。直接改 Compose 文件里的镜像标签,然后执行:
docker compose pull docker compose up -ddocker compose pull会提前把新镜像拉下来,等镜像层面确认无误后再重建容器。重建过程中,WordPress 容器会短时间内不可用,但数据不会丢,因为在卷里。需要注意 MySQL 跨大版本升级有兼容性风险,比如从 5.7 升到 8.0,建议先在测试环境跑一遍再动生产。
6. 新手翻车重灾区:我在 Ubuntu 上踩过的五个 Docker 坑
下面说的五个问题,全是我自己或身边的同行真实遇到过的。每个我都会从现象出发,带一遍排查链路,而不是直接甩答案。
6.1 daemon.json 写错导致 Docker 服务起不来
现象:sudo systemctl start docker后,docker ps报“Cannot connect to the Docker daemon”。systemctl status docker显示 failed 状态。
我第一次遇到时很懵,其实这是 daemon.json 语法或路径问题。排查链路如下:
sudo systemctl status docker sudo journalctl -u docker --no-pager -n 50日志里会明确提示/etc/docker/daemon.json解析失败,或者配置的镜像加速器地址格式有问题。最稳的排错方法是把 daemon.json 临时改名,重启 Docker,确认能起来后,再一行行加回配置。
这里分享一个通用的 JSON 校验方法,不用装任何工具:
python3 -m json.tool /etc/docker/daemon.json如果 JSON 有问题,它会直接告诉你哪一行错了。这个命令我用了无数次,比肉眼找逗号靠谱一百倍。
6.2 容器启动后立刻退出,日志里却什么都没有
现象:docker run -d nginx后docker ps -a看到容器已经是 Exited (0) 状态。
这类问题十有八九是前台进程没有保持运行。Docker 容器活着的前提是 PID 1 进程还活着,一旦主进程退出,容器就退出。比如你运行docker run -d ubuntu,Ubuntu 镜像默认没有常驻进程,启动后立刻退出是正常现象,不是故障。
排查方法是看退出状态码和日志:
docker ps -a docker logs 容器名如果日志为空,说明主进程连输出都没产生,多半是镜像本身的启动命令与容器不匹配。你自定义镜像时,务必确保启动命令是前台运行的。以 nginx 为例,应该用nginx -g "daemon off;",而不是直接nginx,因为守护进程模式会让主进程 fork 到后台后立即退出,容器就跟着退出。
6.3 端口被占用的迷茫
现象:docker run -p 8080:80 nginx报错port is already allocated。
这个很好理解:宿主机 8080 端口已经有一个进程在监听。可能是另一个容器占用了,也可能是宿主机自己的服务占着。
查找占用端口的进程:
ss -lntp | grep 8080 sudo lsof -i:8080如果确认是宿主机进程,要么杀掉进程,要么换一个宿主端口映射。我习惯把端口规划写在 Compose 文件注释里,避免多个项目互相抢端口。
另一个常见情形是:你跑着容器 A 占用 8080,docker stop A后端口并没有马上释放。这种情况罕见但存在,通常是因为容器内进程有 TCP 连接处于 TIME_WAIT 状态。解决办法很简单:换个宿主机端口,或者稍等几秒重试,不要和内核较劲。
6.4 镜像拉取过慢、超时,加速器还是一样慢
现象:docker pull一个大镜像,进度条在某个层卡住,然后报 timeout。
反复配置多个镜像加速器仍然慢,可能的原因有几个,按优先级排查:
- 加速器本身不稳定。公共加速器的可用性谁也无法保证,可以换个时间再试,也可以换一个源。
- IPv6 优先问题。部分 Ubuntu 系统的 DNS 返回了 IPv6 地址,但你的网络 IPv6 路由不通,导致连接卡死。临时禁用 IPv6 试试:
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1- DNS 解析缓慢。检查
/etc/resolv.conf,必要时改成223.5.5.5这类公共 DNS。
总的来说,镜像拉取慢在特定网络环境下是个长期问题。我的习惯是:把常用的基础镜像(nginx、mysql、python、node 等)提前在网络顺畅时拉好,构建应用镜像时基础层不会再来一次。
6.5 /var/lib/docker 磁盘爆炸
现象:Ubuntu 系统盘报警,du -sh /var/lib/docker占了几十个 G。
Docker 默认把所有镜像层、容器可写层、数据卷都存在/var/lib/docker,没有自动清理机制。常见清理手段:
docker system df # 查看磁盘占用 docker system prune # 清理停止的容器、无用网络、悬空镜像 docker system prune -a # 再清理所有未被容器引用的镜像 docker volume prune # 清理无主数据卷docker system prune -a会把你本地所有未被运行的容器引用的镜像都删掉,即使这些镜像你只是构建时用了一下。第一次执行时它会征求你的确认,看清楚再按 y。
还有一个从源头控制的方法:给 Docker 的数据目录单独改位置,或把/var/lib/docker迁到大分区。迁移方法是先停止 Docker,再把整个目录 rsync 到新位置,最后修改 daemon.json 里的>