我一直觉得,Docker 这个词在开发圈子里被严重“神化”了。很多人一提到容器、镜像、编排就发怵,其实真上手之后你会发现,它最难的从来不是技术本身,而是被一堆英文术语绕晕了头。image、container、volume、compose、registry……单独拎出来每个单词都认识,放到一起就不知道谁是谁了。我当初从 Docker 入门到能在生产环境里熟练部署服务,中间踩了无数坑,后来琢磨出一个特别管用的思路:先不急着敲命令,把这套英文术语背后的原始含义搞清楚,Docker 的使用逻辑就自己通了。这篇文章我就按这个思路,把 Docker 的日常使用从头到尾捋一遍,从安装配置到常用命令,再到多容器编排和问题排查,争取让刚接触 Docker 的读者也能少走几周弯路。
1. 先搞懂 Docker 的“英文单词”,再谈使用
1.1 image 为什么叫“镜像”
接触 Docker 的第一个高频词就是 image,中文普遍翻译成“镜像”。很多人会困惑:这东西和镜像文件有什么关系?其实把它理解成“只读模板”更合适。image 是一个打包好的、包含了你运行应用所需要的一切的文件集合,里面可能有操作系统的基础文件、运行环境、依赖库、代码和启动命令。
我个人喜欢用一个类比:image 就像做月饼的模具,你拿同一个模具能压出无数个月饼,每个月饼长得都一模一样。Docker 的 image 也一样,它是只读的、分层的,你不能直接修改一个 image,只能基于它去创建容器。image 的内部采用分层存储,每一层都是一些文件系统的变更记录,就像 Git 的 commit 一样,层与层之间可以复用。这也是为什么你拉取一个基础镜像后,再拉基于它的镜像时会发现下载很快,因为那些公共层已经存在本地了。
1.2 container 的“集装箱”隐喻
container 这个词更妙,它的原义就是“集装箱”。海运领域的集装箱标准统一、隔离严密、可搬运到任何港口,装上任何有标准的货轮就能运走。Docker 的容器设计的正是这个思想:把应用及其运行环境打包成一个标准化单元,无论底层是 Windows、Linux 还是 macOS,拿起来就能跑。
和虚拟机相比,容器最大的区别是共享宿主机的操作系统内核。虚拟机里装的是完整的客户机操作系统,所以启动要按分钟计算;容器直接调用宿主机内核,只额外封装了应用需要的运行库和依赖,所以启动速度是毫秒级。你可以把容器理解成“轻量级的虚拟机”,但它隔离的不是硬件,而是操作系统层面的进程和文件系统。
如果还想再深一层,可以把 image 和 container 的关系类比成“类与实例”的关系。image 是类,是静态定义;container 是 new 出来的实例,是真正跑起来的东西。你基于同一个镜像可以启动多个容器,彼此互不干扰,每个容器都有自己独立的文件系统、网络栈和进程空间。
1.3 registry 和 repository 的关系
每次执行 docker pull 的时候,实际上就是从远程仓库拉取镜像,这个远程仓库在英文里叫 registry,而镜像存放的具体位置叫 repository。这两个词用 Git 来类比特别好懂:registry 就像 GitHub,repository 就是一个具体的项目仓库。Docker 官方的公共 registry 叫 Docker Hub,你可以理解成镜像界的“应用商店”。
repository 下面往往会有多个 tag,也就是版本标签。比如 mysql 这个 repository 里面有 8.0、5.7、latest 等 tag,冒号后面的就是 tag。拉取时 docker pull mysql:8.0 其实拉的是名为 mysql 的 repository 里 tag 为 8.0 的那个镜像版本。我在实际工作中经常看到有人把 repository 和 registry 混着说,一旦理解了它们的层级关系,沟通就顺畅很多。
1.4 volume、network、compose 这些词的原义
volume 英文原义是“卷宗、册”,后来引申为“卷、容量”。在 Docker 里 volume 就是用来做数据持久化的“外部存储”,相当于给容器外挂了一个U盘,容器删了数据还在。network 很简单,就是容器之间的网络连接配置,默认的桥接网络相当于给每个容器发了个局域网 IP。compose 原义是“作曲、构成”,Docker Compose 的意思就是把多个容器“编排”成一个整体,有点像用乐谱把各种乐器组合成交响乐。
说实话,这些英文术语理解到位之后,Docker 的文档看起来完全是两种样子。官方文档里的英文词汇不再是生词,而是帮助你理解设计意图的路标。这也是为什么我总是建议新手先花半小时过一遍常见的 Docker 术语,这比盲目敲命令高效得多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Docker 怎么装:Windows 和 Linux 两条路线
2.1 Windows 环境安装 Docker Desktop
Windows 上使用 Docker 的主流方式是安装 Docker Desktop。它自带图形界面,还集成了 Docker Engine、Docker Compose、Kubernetes 等组件,日常开发完全够用。但 Docker Desktop 有两个硬性前提:一个是系统版本要支持 WSL2 或者 Hyper-V,另一个是 BIOS 里必须开启虚拟化。
我第一次在 Windows 上装 Docker Desktop 时,就卡在了最常见的报错上:Docker Desktop failed to start because virtualization support wasn't detected,提示“virtualization support not detected”。这个报错的核心原因有三个:一是 BIOS 没开启虚拟化,需要进 BIOS 把 Intel VT-x 或 AMD-V 打开;二是 Windows 的 Hyper-V 功能没启用;三是系统里装了其他占用虚拟化功能的软件,比如某些安全软件或者旧版虚拟机软件,需要先卸载或关闭。
确认虚拟化是否开启的方法很简单:打开任务管理器,切到“性能”标签,看左下角有没有“虚拟化: 已启用”。如果没有,就重启进 BIOS 设置界面,找到 Intel Virtualization Technology 或 SVM Mode,改成 Enabled。这一步是 Windows 上安装 Docker 最容易翻车的地方,一定要先确认好再继续。
2.2 WSL2 和 Docker Desktop 的配合
现在新版 Docker Desktop 默认使用 WSL2 作为后端,因为 WSL2 比 Hyper-V 更轻量,启动更快,资源占用更可控。安装 Docker Desktop 之前,最好先装好 WSL2。在管理员权限的 PowerShell 里执行 wsl --install 可以一键安装默认发行版,装完重启后执行 wsl --set-default-version 2 确保用的是 WSL2。
装好 WSL2 之后再装 Docker Desktop,一路默认下一步就行。完成之后打开 Docker Desktop,如果右下角小鲸鱼图标稳定不变灰,说明 Docker Engine 已经跑起来了。这里常见的一个问题是,安装完成后在命令行执行 docker version 会报 failed to connect to the docker api at npipe:////./pipe/docker_desktop_linux,这个 npipe 是 Windows 上 Docker 的命名管道地址,一般出现在 Docker Desktop 没有真正启动、或者版本不匹配的时候。解决方法是:先确认 Docker Desktop 右下角图标是正常状态,如果还是不行,重启 Docker Desktop 或者重启电脑,基本都能解决。
2.3 Linux 下用命令行安装 Docker Engine
Linux 上没有 Docker Desktop 这个概念,主流做法是安装 Docker Engine。以 CentOS 为例,安装过程主要是配置仓库、安装软件包、启动服务三步。Debian/Ubuntu 系统则更简单,直接 sudo apt install docker.io 就能安装,但版本可能偏旧,所以生产环境我更推荐用官方脚本安装,然后手动启动。
以 Ubuntu 为例,我常用的安装步骤是:
bash复制sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
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
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
安装完成后,启动服务并设置开机自启:
bash复制sudo systemctl start docker
sudo systemctl enable docker
之后执行 docker --version 确认能正常输出版本号。如果执行 docker ps 出现 permission denied 或 cannot connect to the Docker daemon,通常是当前用户不在 docker 用户组里。把当前用户加进去就行:
bash复制sudo usermod -aG docker $USER
注意,这条命令执行后需要重新登录终端才能生效。这个权限问题特别常见,我见过很多同事在 Linux 上装了 Docker 之后一直用 sudo 前缀执行命令,虽然能用但很麻烦,而且容易引起后续脚本权限混乱,建议一开始就把 docker 组加好。
2.4 镜像源配置:解决下载慢的问题
镜像下载慢是从入门到放弃的一大元凶。默认情况下,Docker 从 Docker Hub 拉取镜像,国内网络环境拉大镜像时经常卡到怀疑人生。解决办法是配置 registry mirror,也就是公共镜像源。常见的做法是修改 /etc/docker/daemon.json 文件,把公共镜像源地址加进去。比如:
json复制{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://dockerproxy.com"
]
}
修改之后重启 Docker:
bash复制sudo systemctl daemon-reload
sudo systemctl restart docker
这里有一点要提醒:镜像源地址的稳定性谁也不能保证,可能过一段时间某个源就失效了。我的经验是配置两到三个备用的公共镜像源,拉取时 Docker 会自动按顺序尝试。如果拉某个镜像还是慢,可以试试手动指定 registry 地址,比如从其他公共镜像仓库拉取,或者干脆用代理环境来拉取。关键是不要在一条路上死磕,灵活切换才是正道。
3. 常用命令一条链路:从拉取、运行到清理
3.1 镜像操作:search、pull、images、tag
Docker 的命令体系非常有规律,只要掌握了一条从“找镜像”到“跑容器”的链路,日常使用基本就够用了。第一步是搜索镜像,docker search 可以按名称关键词在 Docker Hub 上查找镜像,这个命令平时用得不多,更多时候我们是直接知道镜像名去拉取。
bash复制docker search mysql
第二步是拉取镜像:
bash复制docker pull mysql:8.0
它会自动从配置好的镜像源拉取指定 tag 的镜像。如果省略 tag,默认拉取 latest,但生产环境我强烈建议不要用 latest,因为版本漂移会让你某天突然发现环境行为变了,排查起来很痛苦。
第三步是查看本地镜像:
bash复制docker images
这个命令会列出本地所有镜像,包括镜像名、tag、镜像 ID、创建时间和大小。镜像 ID 是 SHA256 的前 12 位,后续操作镜像时可以用 ID 代替名字。还可以用 docker tag 给镜像打标签:
bash复制docker tag mysql:8.0 myregistry/mysql:8.0.33
这个操作本质上只是给镜像加了一个别名,并不会复制镜像内容。当你要把镜像推送到自己的私有仓库时,就需要先打上对应仓库地址的 tag,再执行 docker push。
3.2 容器生命周期:run、ps、exec、stop、rm
镜像拉下来之后,最核心的命令就是 docker run。它负责基于镜像创建并启动一个容器。我举个例子,部署一个 MySQL 8.0 的容器:
bash复制docker run -d \
--name mysql-test \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=123456 \
-v /data/mysql:/var/lib/mysql \
mysql:8.0
这一条命令里的参数很有代表性。-d 表示后台运行,--name 指定容器名,-p 做端口映射,将宿主机的 3306 端口映射到容器的 3306 端口,-e 是传入环境变量,-v 是挂载数据卷。解释一下为什么需要 -p:容器有自己的网络命名空间,宿主机不能直接访问容器 IP,必须做端口映射才能从外面访问容器里提供的服务。这就像公寓楼里每户有自己的门牌号,但外人要进来必须经过小区大门登记。
查看正在运行的容器:
bash复制docker ps
加 -a 参数可以查看所有容器,包括已停止的。如果想进入容器内部查看情况,用 docker exec:
bash复制docker exec -it mysql-test bash
-i 和 -t 是组合使用,-i 保持标准输入打开,-t 分配一个伪终端。进入容器后,你就可以像在一台普通的 Linux 机器上一样操作,比如执行 mysql -uroot -p 登录数据库。
停止和删除容器:
bash复制docker stop mysql-test
docker rm mysql-test
这里有一个细节要提醒:docker stop 是优雅停止,它会先给容器内的主进程发送 SIGTERM 信号,等它自己处理完退出;如果超时没退出才发 SIGKILL。而 docker kill 是直接强制杀掉。生产环境能 stop 就不要 kill,给应用留一点善后的时间。
常用命令的组合使用可以靠 docker container prune 清理所有已停止的容器,docker image prune 清理悬空镜像。如果本地积累了大量无用镜像和容器,可以定期用 docker system prune -a 全面清理,但注意这个命令会删除所有不被容器使用的镜像,执行前务必看清楚。
3.3 多容器的数据持久化和端口映射
容器的文件系统是临时的,容器一删,里面写的数据就全没了。这是 Docker 新手最容易踩的大坑。解决方案就是挂载数据卷或绑定宿主机目录。刚才 MySQL 例子里的 -v /data/mysql:/var/lib/mysql,就是把宿主机的 /data/mysql 目录绑定到容器的 /var/lib/mysql 目录,MySQL 写库文件时实际上是写到宿主机磁盘上,容器销毁重建数据依然还在。
还有一种方式是使用具名卷:
bash复制docker volume create mysql-data
docker run -d -v mysql-data:/var/lib/mysql mysql:8.0
具名卷的好处是 Docker 会管理具体的存储位置,你可以用 docker volume ls 查看所有卷。究竟是绑定目录还是使用具名卷?我的实践是:开发环境图省事就直接绑定目录,改配置文件方便;生产环境我倾向使用具名卷,统一由 Docker 管理,备份和迁移都更规范。
端口映射方面,-p 参数支持多个协议,比如 -p 8080:80 表示把宿主机 8080 端口映射到容器的 80 端口。还有一种 -P 参数是随机映射,Docker 会随机从宿主机挑一个高端口映射到容器的暴露端口,这个在测试时偶尔用一下,生产环境基本不用。
4. docker compose:从单容器到多容器编排
4.1 为什么需要 Compose
单容器用 docker run 一条命令就能搞定,但一旦涉及多个服务,比如一个 Web 应用要连数据库、连缓存、连消息队列,你再一个一个 docker run 就非常痛苦了。你需要记下每个容器的启动参数、网络连接方式、数据卷挂载位置,还要考虑启动顺序,稍有不慎连错网络,服务之间就互相找不到。这时候就需要 Docker Compose。
Docker Compose 允许你用一份 YAML 文件描述整个应用的服务、网络、卷等资源,然后用 docker compose up -d 一条命令启动所有服务。你可以把这看成“docker run 的声明式版本”,把命令行的各种参数写成配置文件,好处是清晰、可版本化、可复用。团队协作时,新同事拉下代码后只需要一份 docker-compose.yml,就能一键把整个环境的依赖服务跑起来,这是效率的巨大提升。
4.2 实战:使用 Compose 部署一个 Redis 主从环境
举个实际例子,用 docker compose 快速搭一套 Redis 主从复制环境。这里的思路是:主节点正常启动提供服务,从节点配置 replicaof 指向主节点,自动同步数据。
docker-compose.yml 文件长这样:
yaml复制version: "3.8"
services:
redis-master:
image: redis:7.0
container_name: redis-master
command: ["redis-server", "--appendonly", "yes"]
ports:
- "6379:6379"
volumes:
- master-data:/data
redis-slave:
image: redis:7.0
container_name: redis-slave
command: ["redis-server", "--replicaof", "redis-master", "6379"]
ports:
- "6380:6379"
depends_on:
- redis-master
volumes:
master-data:
重点看几个地方:services 下面定义了 redis-master 和 redis-slave 两个服务,使用的是同一个 redis:7.0 镜像,但启动命令不同。主节点开启了 appendonly,用于持久化;从节点通过 --replicaof redis-master 6379 指定主节点的服务名和端口。Compose 会在内部创建一个默认网络,服务之间可以直接用服务名互相访问,所以要联通的只有端口映射。
在配置文件所在目录执行:
bash复制docker compose up -d
稍等几秒,执行 docker compose ps 查看状态。如果两个服务都是 running,就可以用 redis-cli 验证主从状态:
bash复制docker exec -it redis-master redis-cli info replication
docker exec -it redis-slave redis-cli info replication
你会在从节点上看到 role:slave,以及 master_link_status:up,说明主从同步已经生效。整个搭建过程不超过一分钟,一条命令启动两个容器,这就是 Compose 的价值所在。
4.3 使用 Compose 部署 MySQL 8.0 和数据初始化
Redis 的例子比较简单,再来一个生产环境常用的 MySQL 8.0 部署,顺便演示一下初始化脚本的作用。实际项目中我们经常需要预设数据库、创建用户,如果每次启动容器后手动执行 SQL 就太麻烦了。MySQL 官方镜像支持把 .sql 脚本放到 /docker-entrypoint-initdb.d 目录下,首次启动容器时会自动执行。
对应的 docker-compose.yml 可以这样写:
yaml复制version: "3.8"
services:
mysql:
image: mysql:8.0
container_name: mysql8
restart: always
environment:
MYSQL_ROOT_PASSWORD: root123456
MYSQL_DATABASE: app_db
MYSQL_USER: app_user
MYSQL_PASSWORD: app_pass
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
- ./init:/docker-entrypoint-initdb.d
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
volumes:
mysql-data:
这里用 environment 设置了数据库实例的初始配置,root 密码、自动创建的数据库名和应用账号都在这里配置。volumes 挂载了两个目录:一个是数据目录,一个是本地 init 目录,里面放初始化 SQL 脚本。command 里指定了 MySQL 的启动参数,强制使用 utf8mb4 字符集,避免中文乱码。
启动后执行:
bash复制docker compose logs -f mysql
可以看到 MySQL 初始化、执行初始化脚本的完整日志。第一次启动初始化完成后,后面再启动容器不会重复执行初始化脚本。这个机制非常实用,相当于把数据库的初始化工作也声明化、版本化了。
4.4 Compose 常用命令和注意事项
Compose 的命令和 docker 命令高度类似,最常用的几个是:
bash复制docker compose up -d # 启动所有服务
docker compose down # 停止并删除所有服务、网络
docker compose ps # 查看服务状态
docker compose logs -f # 实时查看日志
docker compose exec mysql bash # 进入某个服务容器
docker compose restart # 重启所有服务
有几个容易踩的坑我还是要专门提一下。第一个是 depends_on 只是控制启动顺序,不保证“服务已经可用”。比如 Web 服务依赖 MySQL,depends_on 可以保证 MySQL 容器先启动,但 MySQL 初始化可能需要一段时间,Web 容器启动时可能连不上数据库。解决方法是加健康检查,或者在应用里做重试机制。
第二个是不要在 docker-compose.yml 里写死容器 IP。Compose 默认会创建自己的网络,服务名就是动态 DNS,容器 IP 是随时可能变的。连接其他服务统一用服务名,不要用 IP。
第三个是 .env 文件。Compose 会自动读取同目录下的 .env 文件,你可以在里面定义变量,然后在 docker-compose.yml 里用 ${VAR_NAME} 引用。比如把密码放到 .env 里,避免敏感信息直接写在 compose 文件中。
5. 常见问题与排查技巧实录
5.1 虚拟化没检测到的常见处理流程
前面已经详细说过 Windows 下 virtualizaion support not detected 这个报错,这里把它放进一个统一的排查流程里。报这个错时,按顺序检查三个地方:
第一,硬件虚拟化是否开启。任务管理器 -> 性能 -> CPU,看“虚拟化”是否为“已启用”。如果没启用,重启进 BIOS,找到 Intel VT-x、Intel Virtualization Technology 或 AMD SVM,改成 Enabled 后保存退出。
第二,Windows 功能里 Hyper-V 是否启用。在“启用或关闭 Windows 功能”里勾选 Hyper-V 和“适用于 Linux 的 Windows 子系统”,然后重启。注意 Windows 家庭版默认没有 Hyper-V 选项,这种情况优先考虑用 WSL2。
第三,是否有其他软件占用虚拟化。部分安全软件、沙箱软件、旧版虚拟机工具会冲突,装上 Docker Desktop 后直接报错。这种需要排查软件列表,禁用或卸载冲突软件。这一套流程走完,绝大多数虚拟化报错都能解决。
5.2 docker 权限错误和 API 连接问题
Linux 环境下执行 docker 命令报 permission denied 的解决办法前面已经提过,就是把自己加入 docker 组。但这里有一个安全上的细节要说明:加入 docker 组的用户等同于拥有 root 权限,因为 docker 命令本身可以挂载宿主机目录、执行容器内操作等,所以生产环境给谁加 docker 组要非常谨慎,不要把普通开发账号随便加进去。如果团队里有人只是临时需要,可以让他用 sudo docker 命令,而不是直接加组。
Windows 下常见的 failed to connect to the docker api at npipe 这个报错,本质是 Docker 客户端连不上 Docker Engine。原因一般是 Docker Desktop 没有启动成功、启动过程中报错了、或者配置了不存在的上下文。处理办法是先打开 Docker Desktop 看鲸鱼图标是否稳定不灰,再执行 docker context ls 检查当前上下文是否是 default,如果都不行,建议先重启 Docker Desktop,还不行就重启系统。根据我多年的经验,Windows 上 Docker 的大多数“灵异问题”都能靠重启解决。
5.3 容器网络不通的排查思路
把两个容器部署起来了,结果发现互相访问不通,这是常见问题。排查思路要自底向上,一步一步来。首先检查宿主机能不能 ping 通容器的 IP,也就是 docker inspect 容器名 查看 IP 后 ping 一下。如果宿主机 ping 容器不通,多半是网络驱动或防火墙问题。如果宿主机 ping 得通,但容器内 ping 不通其他容器,先确认两个容器是否在同一个 Docker 网络里,可以用 docker network ls 查看网络,然后用 docker network connect 把容器加入指定网络。
还有一种情况是容器能 ping 通 IP,但访问容器内的服务不通。这时候要检查服务是否只听 127.0.0.1,而不是 0.0.0.0。比如容器里的应用启动时绑定了 localhost,外部当然访问不到,需要修改应用配置监听所有网卡。我在排查中遇到最多的其实就是这个原因:明明端口映射都配好了,但应用只监听了 127.0.0.1,从宿主机就是连不上。
另外要检查宿主机防火墙。Linux 上如果开启了 firewalld,即使端口映射正常,宿主机也会拦截外部的访问。需要执行 firewall-cmd --add-port=8080/tcp --permanent 放行端口,或者关闭防火墙。Docker 默认的 bridge 网络本身就提供了容器间的通信能力,但如果自定义网络模式是 none 或 host,规则就要重新理解。host 模式虽然网络性能好,但容器和宿主机共享网络栈,端口冲突风险也高,一般不建议新手优先使用。
5.4 镜像下载慢和磁盘占用问题
镜像下载慢的问题,配置 registry mirror 是首要手段。此外还有一个思路是选择更小的基础镜像,比如用 alpine 版本替代默认版本,很多官方镜像都提供 -alpine 后缀的版本。alpine 采用 musl libc,体积小很多,一个 nginx:alpine 才 20MB 左右,而默认版本要 50MB 以上。镜像小了,拉取自然更快,还能节省磁盘和内存资源。但要注意 alpine 用的是自己的包管理器,某些编译后的二进制可能不兼容,比如某些依赖 glibc 的 Java 应用,用 alpine 镜像反而会出问题。所以基础镜像的选型要结合应用实际情况,不要一味追求小。
运行时间长了,Docker 的磁盘占用也会让人头疼。Docker 的 overlay2 目录动辄几十 GB,数据卷、镜像、容器层、日志文件都会占空间。常用的清理命令如下:
bash复制docker system df # 查看空间占用情况
docker system prune # 清理悬空镜像、停止的容器、无用网络和构建缓存
docker system prune -a --volumes # 全量清理,慎用,会删掉未使用的卷
在清理前建议先用 docker system df 看清楚各大类占用情况,有的放矢地清理。还要注意容器日志文件默认是无上限增长的,长时间运行的容器日志可能撑爆磁盘,尤其是 nginx、Java 应用这类日志大户。设置日志轮转可以加在 daemon.json 里:
json复制{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
这样单个日志文件最大 10MB,保留 3 个,能有效避免日志无限增长。这是我运维 Docker 半年来觉得最值得加的一个配置。
5.5 容器启动后又退出的排查方法
每次碰到容器启动后立刻退出,很多新手就慌了。先别急着删容器,正确的做法是查看容器日志,但一个已经退出的容器你直接 docker logs 也能看到之前的输出。比如执行 docker logs 容器名 后,如果看到类似 exec: "xxx": executable file not found in $PATH 的报错,说明镜像里没有对应的启动命令,或者命令路径不对。如果看到 permission denied,则可能是启动脚本没有执行权限。
还有一种很常见的场景是启动命令依赖后台运行,但容器的主进程没有前台驻留。比如你启动一个 Redis 容器,命令是 redis-server &,那个主进程变成后台任务后,容器发现没有前台进程就会立刻退出。Docker 的容器必须有一个前台进程长期运行,否则生命周期终结。所以写 Dockerfile 的 CMD 或者 docker run 的 command 时,要确保命令在前台运行,不要加后台符号 &。
如果日志输出了一些错误信息但内容不直观,可以再执行 docker inspect 容器名 查看容器的 State 和 ExitCode。ExitCode 是 0 代表进程正常退出,非 0 则是程序自身报错,常见的是 1 或 127。拿到退出码和日志基本就能锁定问题。建议平时养成习惯,每个容器启动后先 docker ps 确认状态,再 docker logs -f 盯一下日志输出,能省去很多排查时间。
6. 我的一些使用习惯
最后分享一点实际操作层面的心得。我在本地开发时,习惯把项目的依赖服务统一用 docker compose 管理,比如数据库、Redis、消息队列这些中间件全部用容器跑,开发机干干净净,切换项目时只需要切换目录,然后 docker compose down 再 up 就行,彻底告别了“本地装了一堆服务导致环境混乱”的噩梦。
镜像和容器的命名我也坚持规范化。容器名用项目名加角色,比如 project-mysql、project-redis,镜像 tag 尽量不用 latest,而是具体的版本号,这样部署时能精确复现。之前有一次生产环境升级,仅仅因为一个依赖镜像的 latest 从 8.0 跳到了 8.1,应用的兼容性就出了问题,排查了一整天最后发现是镜像版本漂移。从那以后,凡是生产环境用到的镜像,tag 必须锁死。
排查问题时的思路也分享一下:先看日志,再看状态,最后改配置。很多人一上来就删容器重建,其实日志里早就写明了原因。另外善用 docker inspect 和 docker exec 这两个命令,一个负责静态信息查看,一个负责动态进容器操作,两者配合能覆盖 90% 的日常排查场景。
Docker 这个东西,说难是真的不难,核心无非是镜像、容器、网络、数据卷那几个概念,配合 Compose 做编排,再加上命令熟练度。说简单也不简单,它把部署这件事标准化了,反而要求你对操作系统、网络、存储这些基础知识有更扎实的理解。但只要你愿意花点时间把底层逻辑打通,之后用起来会非常顺手。希望这篇从英文术语讲到实际排障的文章,能帮你少走一些我当年走过的弯路。
