Docker入门到实战:镜像容器、数据卷、Compose编排全栈项目详解
先把话说在前头:Docker 这东西,我最早看教程时也是一头雾水,什么镜像、容器、仓库,听着全是概念。直到有人跟我说了一句“你就把镜像当成一张光盘,容器就是你拿光盘放出来的 DVD 播放器里的画面”,我一下就通了。后来我在 Windows、Linux 上装了一遍,拿 MySQL、Redis 这些中间件练手,再把一个正经的前后端全栈项目塞进容器里跑起来,踩了一路的坑。这篇就按我自己从光盘到实战的路径来写,把 Docker 的安装、核心概念、镜像加速、常见中间件部署,以及全栈项目的 Compose 编排全串一遍,新手可以照着抄,有点基础的也能补上几个平时没人告诉你的细节。
1. 内容整体设计与思路拆解
1.1 为什么是“光盘与 DVD”?这个类比到底准不准
“光盘与 DVD”这个类比,核心解决的是 Docker 最容易劝退新人的概念门槛:镜像(Image)和容器(Container)到底是什么关系。
你可以这样理解:一张光盘(镜像)里刻的是标准化的内容,不管是《泰坦尼克号》还是 Linux 系统,内容本身是只读的。你把它塞进任何一台 DVD 播放器(Docker 引擎),放出来的画面就是一个正在运行的实例(容器)。同一个光盘,你可以在十台播放器上同时放,每一台放出来的内容一模一样,互不干扰。这就是 Docker 最核心的价值——环境一致性。
更深一层,光盘还有一个特点:它是按行业标准生产的,任何 DVD 播放器都能读。Docker 镜像也是这么设计的,它把应用代码、运行时、系统库、配置全部打成标准格式,只要有 Docker 引擎就能跑,不管底层是 Windows、macOS 还是 Linux。这就是容器化与虚拟机最大的区别:虚拟机虚拟的是整台硬件,而容器虚拟的是操作系统层,镜像里包含的是“应用 + 它需要的所有运行环境”,而不是一整套操作系统。
不过类比到这里就有点不够用了。光盘是一次性刻录的,但容器是动态的——你可以进到容器里执行命令、写文件、跑进程。所以更准确的说法是:镜像是一张“母盘”,容器是根据母盘复制出来的“可读写副本”。母盘永远不变,副本随便你怎么折腾,删了再复制一个就是。这个设计带来的好处非常实际:生产环境出问题了,直接把容器删掉,用同一个镜像重新起一个,环境瞬间恢复干净。这在以前虚拟机时代是不可想象的。
1.2 这个方案解决了什么问题,适合谁看
Docker 解决的最痛的问题,就是那句经典的“在我机器上是好的啊”。
传统部署流程是这样的:开发在本地配好环境,代码能跑;测试部署到服务器,结果缺个依赖、版本不对、系统库不一样,跑不起来;运维再上去调半天,好不容易通了,开发又改了一行代码,重新部署,又炸了。这个循环我经历过太多次,尤其是在 Java 项目里,JDK 版本、Maven 依赖、配置文件,任何一个不一致都能让你排查一下午。
容器化之后,开发把环境和代码一起打成镜像,测试和运维拿到的就是同一个“光盘”,上面是什么版本就是什么版本,不存在“你机器和我不一样”的问题。
那这篇文章适合谁看?我总结成三类:
- 零基础新手:完全没碰过 Docker,但听说过“容器化部署”这个词,想搞明白它到底是怎么回事。
- 用 Docker 装过一两次软件、但没系统梳理过的人:会
docker run命令,但不知道为什么数据一删就没了,也不知道 Compose 是干嘛的。 - 准备把全栈项目容器化的开发者:想把手里的前端、后端、数据库、中间件串起来做一键部署,需要一个完整可参考的模板。
下面所有内容,我都会按这个思路展开,从安装到实战再到排错,每一步都会告诉你“为什么这么做”,而不是只给命令。
2. 核心概念拆解:镜像、容器、数据卷与 Compose
2.1 镜像:只读的“母盘”与分层存储原理
镜像,Docker 里用 Image 表示,本质上是一个只读的模板,里面打包了应用代码、运行时、系统工具和依赖库。你可以用 docker pull 从镜像仓库拉一个现成的,也可以用 Dockerfile 自己构建一个。
镜像的分层存储是很多人忽略但非常重要的细节。一个镜像不是一个大文件,而是由很多只读层(Layer)堆叠组成的。每一层对应 Dockerfile 里的一条指令,比如 FROM、RUN、COPY。这样设计的好处是:多个镜像可以共享底层。
举个例子,你拉了 mysql:8.0 和 wordpress:php8.1 两个镜像,它们底层可能都基于 debian 或 ubuntu,这部分底层是共享的,不会重复占用磁盘。而且你更新镜像时,只有变化的层需要重新下载,没变的层直接复用本地缓存,速度飞快。
查看镜像用 docker images,删除镜像用 docker rmi,想省磁盘用 docker image prune清理悬空镜像(即那些已经没有容器引用的中间层镜像)。我自己的习惯是每过一段时间就执行一次:
这个命令会清理掉所有 dangling 镜像,也就是标签为 <none> 的中间层。第一次跑之前磁盘占用 20G,跑完之后可能只剩 8G,效果非常明显。
2.2 容器:镜像的运行实例,以及它的生命周期
容器是镜像的运行实例。你 docker run 一次,就基于镜像创建一个容器。容器可以被启动、停止、删除,也可以进入它的命令行执行操作。
这里有一个新手常常困惑的点:容器和镜像的区别到底是什么?我做一个表格帮你理清:
| 对比项 | 镜像(Image) | 容器(Container) |
|---|---|---|
| 本质 | 只读模板 | 镜像的运行实例 |
| 是否可写 | 不可写 | 可写,但写入内容只在容器生命周期内存在 |
| 生命周期 | 永久存在,除非主动删除 | 可启动、停止、删除 |
| 类比 | 母盘 | 放母盘读出来的可操作副本 |
| 常见命令 | docker images, docker pull, docker rmi |
docker run, docker ps, docker stop, docker rm |
容器生命周期管理有几个最常用的命令:
每次 docker run 其实都是“基于镜像新建一个容器”,所以如果你只是改了一下启动参数,不要想着“改”现有容器,直接删了重新 run 是更干净的做法。容器本身是瞬态的、可抛弃的,这恰恰是容器化的哲学。
2.3 数据卷:容器删了数据不丢的关键
这是新手最容易翻车的地方:Docker 容器一旦删除,里面写的文件全部消失。MySQL 数据、Redis 持久化文件、应用上传的图片,全部没。这不是 Docker 设计失误,而是它默认把容器当“无状态”的来处理。
解决这个问题的方式是数据卷(Volume)。数据卷本质上是宿主机上的一个目录,挂载到容器内部,容器写这个目录,实际上是写宿主机,容器删了数据还在。
我个人的经验:所有有状态的服务,启动时第一件事先把数据目录挂出来。MySQL 挂 /var/lib/mysql,Redis 挂持久化目录,应用挂上传目录。不要在容器里存任何重要数据。
数据卷的挂载有两种方式,一是 -v 参数指定宿主机目录和容器目录的映射关系,二是用 Docker Volume 管理。前者直观,后者更规范,我后面实战部分会分别演示。
2.4 Docker Compose:从单容器到多服务编排的桥梁
当你只需要跑一个 MySQL 容器时,一条 docker run 就够了。但当你的全栈项目要同时跑前端、后端、数据库、缓存、消息队列时,手动敲命令会疯掉,而且每次启动还要保证顺序正确、网络互通。
Docker Compose 就是干这个的。它用一个 docker-compose.yml 文件描述整个应用的服务组成,然后一条 docker compose up -d 把全部服务拉起来。
Compose 的核心价值有三个:可重复、可共享、可编排。
- 可重复:配置文件写好了,团队任何人拿到项目,执行一行命令就能把整套环境启动起来。
- 可共享:配置文件跟着代码仓库走,新同事入职不用再翻文档配环境。
- 可编排:依赖关系、网络、数据卷、端口映射都在一个文件里声明,不用手动一个个处理。
我后面全栈项目实战部分会给一个完整的 Compose 文件,包含构建镜像、环境变量、数据卷、网络配置等,照着抄就能用。
3. Docker 安装与启动:三大平台实操与 Windows 虚拟化问题排查
3.1 Windows 安装 Docker Desktop 与虚拟化报错处理
Windows 上装 Docker 走的是 Docker Desktop,这是官方出的带图形界面的客户端。安装包直接去官网 Docker Hub 下载,选对应 Windows 版本即可。安装过程基本上是下一步下一步,但装完之后很多人会遇到一个坎儿:
Docker Desktop failed to start because virtualisation support wasn't detected.
这个报错的意思是 Docker Desktop 检测不到虚拟化支持。你机器可能装了 VMware 或 VirtualBox 的虚拟机,或者 CPU 虚拟化没开,也可能 Windows 的 Hyper-V 组件没启用。我按排查顺序说:
第一,检查 BIOS/固件设置里的虚拟化开关。重启电脑,进 BIOS(不同品牌按键不一样,通常是 DEL 或 F2),找到 Intel Virtualization Technology 或 AMD SVM Mode,确认是 Enabled。现在很多新电脑默认就是开的,但也有品牌机默认关闭的情况,这个得自己看一眼。
第二,确认 Windows 的虚拟化平台组件没被关掉。在“控制面板—程序—启用或关闭 Windows 功能”里,勾选“适用于 Linux 的 Windows 子系统”和“虚拟机平台”两项。Docker Desktop 从较新的版本开始依赖 WSL2,这两项不勾上,Docker 是起不来的。勾选后重启电脑。
第三,如果还不行,在管理员 PowerShell 里手动执行:
然后重启。这两条命令是强行把 WSL 和虚拟化平台的系统组件启用,比在面板里勾选更彻底。
如果用的是 Windows 10 家庭版,可能会出现没有 Hyper-V 选项的情况。这时候不用慌,Docker Desktop 走 WSL2 后端是不需要完整 Hyper-V 的,只要虚拟化平台和 WSL 正常,就能跑。我之前给一台老 Windows 10 笔记本排查过,就是 BIOS 里虚拟化关了,打开就解决了。
3.2 Linux 安装 Docker Engine 与权限问题
Linux 上装 Docker 就是装 Docker Engine,通过系统的包管理器来装。以 Ubuntu 为例,官方安装方式是添加 Docker 的 apt 源:
CentOS 7 之类的老系统,网络热词里也有人提到 “centos7升级docker”,这个需要注意:CentOS 7 默认 yum 源的 docker 版本比较老,要升级的话,先卸载旧版本:
然后添加 Docker 官方 yum 源,再安装 docker-ce。CentOS 7 的内核比较老,Docker 新版本对内核有要求,最好确认内核版本不低于 3.10。实际装完 dockerd 起不来的话,优先查 systemctl status docker 和 /var/log/docker 日志。
装完 Docker 之后,普通用户直接执行 docker 命令通常会报权限错误:
Got permission denied while trying to connect to the Docker daemon socket.
原因是 Docker 守护进程默认监听在 /var/run/docker.sock 这个 socket 上,普通用户没有访问权限。解决办法是把当前用户加入 docker 组:
然后重新登录(或者 newgrp docker 再刷新会话),就正常了。我不建议图省事直接 sudo chmod 666 /var/run/docker.sock,这会让任何用户都能操作 Docker,等于把服务器最高权限敞开了,非常危险。
3.3 macOS 与“安装到 D 盘”的需求
macOS 上同样用 Docker Desktop,装完就是图形界面的 Docker 守护进程,日常操作跟 Windows 版几乎一样,没什么特别的坑,唯一要注意的是 Apple Silicon 芯片要选对安装包,有专门的 arm64 版本。
Windows 上“docker 安装到 D 盘”这个需求我也见过不少。Docker Desktop 默认把镜像和数据放在 C 盘用户目录下,C 盘空间不足的时候,很多人想把整个 Docker 数据目录挪到 D 盘。操作路径是:
打开 Docker Desktop — Settings — Resources — Advanced — Disk image location,把路径改成 D 盘某个目录,然后 Apply & Restart。改完之后,C 盘上的旧数据不会自动迁移,你可以手动把旧的 %LOCALAPPDATA%\Docker\wsl 目录清理掉,或者先把需要的镜像重新 pull 一遍。
这个方法只适用于 Docker Desktop 的 WSL2 后端,如果是用 Docker Toolbox 那种老方案,数据目录是 VirtualBox 虚拟机磁盘文件,迁移逻辑完全不同,这里不展开。
安装完 Docker,无论哪个平台,先验证一下:
能输出版本信息、能跑起来 hello-world 容器并打印提示文本,说明引擎工作正常。接下来就该处理一个每天都躲不开的问题——镜像下载慢。
4. 镜像加速与仓库配置:解决下载慢的常见手段
4.1 为什么新装完的 Docker 下载镜像特别慢
Docker 默认从 Docker Hub 拉取镜像,而 Docker Hub 的服务器在海外,国内网络环境下直连速度极不稳定。拉一个几百 MB 的镜像等十几分钟是常有的事,失败重试也频繁。这不是你网速的问题,是网络链路的问题。
解决思路是给 Docker 配置镜像加速器,也就是国内的一些 Docker 镜像仓库。原理是这些镜像仓库做了 Docker Hub 的同步缓存,你从离你近、链路快的仓库拉,速度会好很多。
不过这里我要说明一点:国内加速源本身也在频繁变动,有的需要注册账号才能用,有的过段时间就关了。所以下面的配置方法我会写清楚原理,具体地址你需要用当前可用的,不要死记某一个。
4.2 配置镜像加速:Linux 的 daemon.json 与 Docker Desktop 的设置
Linux 上配置镜像加速,需要修改 Docker 守护进程配置文件 /etc/docker/daemon.json。文件默认不存在,自己创建即可:
写完以后执行:
然后可以验证一下配置是否生效:
能看到你填的加速地址列表,就说明配置成功了。
Windows 和 macOS 的 Docker Desktop 有图形界面:Settings — Docker Engine,里面会显示一份 JSON 配置,把上面的 registry-mirrors 数组合并进去,点 Apply & Restart 即可。注意直接改界面里的 JSON 就行,不要乱动其他默认字段。
配置完加速器之后,原来十几分钟拉不下来的 mysql:8.0 可能一分钟内就拉完了。如果还是慢,可以试试换几个加速地址,或者避开高峰时段。镜像下载速度这事没有一劳永逸的解法,我在服务器上就放了两三个可用的加速源,哪个通走哪个。
4.3 内网环境的镜像仓库方案
如果你是在内网部署,不能访问外网镜像源,那就需要搭一个私有镜像仓库。最轻量的是用 Docker 官方提供的 registry 镜像,一条命令搞定:
然后其他机器拉取镜像时,把镜像 tag 改成 内网IP:5000/镜像名:tag 就能从内网仓库拉。全栈项目如果涉及多台服务器,建议搞一个私有仓库,不然每台机器都往公网拉,既慢又不稳定。
5. 实战一:用 Docker 安装 MySQL 8.0 并完成基本使用
5.1 拉取镜像与容器启动参数详解
装 MySQL 8.0 是 Docker 入门最经典、也最有代表性的实战,因为它涉及环境变量、数据卷、端口映射、网络四个最核心的要素。
先拉镜像:
拉完看一下:
启动容器,我给出一个推荐写法,并逐步解释:
逐行解释:
-d:后台运行,不占用当前终端。--name mysql8:给容器起个名字,之后操作都用这个名字。-p 3306:3306:端口映射。宿主机 3306 端口映射到容器 3306 端口,外部访问就是宿主机 IP:3306。-e MYSQL_ROOT_PASSWORD=123456:环境变量,告诉 MySQL 镜像初始化时把 root 密码设为 123456。首次启动有效,数据目录已初始化后,这个变量不再生效。-e MYSQL_DATABASE=appdb:额外创建一个名为 appdb 的数据库。这个可以留空,但初创阶段加上很方便。-v /data/mysql/data:/var/lib/mysql:把宿主机/data/mysql/data目录挂到容器的 MySQL 数据目录。容器删了,数据都在这。-v /data/mysql/conf:/etc/mysql/conf.d:挂载自定义配置目录,MySQL 容器会自动读取该目录下的.cnf配置文件。--restart=always:容器异常退出或 Docker 重启时自动拉起。生产环境必备。
启动之后,检查容器状态:
第一次启动会有一段初始化输出,看到 “ready for connections” 就说明 MySQL 起来了。
5.2 连接测试与容器外管理 MySQL
容器起来了,接下来验证能不能连。在宿主机上用 MySQL 客户端连(如果没有客户端,先进容器里连也行):
进到 MySQL 里可以执行 show databases; 验证。
如果是从局域网其他机器连,要注意几个问题。一是防火墙,宿主机 3306 端口要放行;二是 MySQL 8.0 默认的认证插件是 caching_sha2_password,老客户端可能连不上,需要用 mysql_native_password 重设密码:
三是 MySQL 8.0 默认 root 用户只允许 localhost 登录,如果你需要远程连 root,需要自己创建用户并授权。我建议不要直接远程用 root,而是创建一个业务专用账号:
启动 MySQL 时你创建了 appdb 数据库,这里再把一个账号授权给它,业务代码就用这个账号连,权限收得越小越安全。
5.3 常见失败场景:启动失败、端口占用与认证问题
启动失败。最常见的原因是自己加的 -v 挂载目录权限不对,MySQL 容器内进程用 mysql 用户跑,对挂载目录没有写权限,初始化就失败。排查方式是:
然后删掉容器重来。生产环境不要用 777,更适合的做法是 chown -R 999:999 /data/mysql,因为 MySQL 镜像内部 uid 是 999。权限问题不解决,你怎么启动都是失败,还会在日志里看到 Permission denied 之类的字样。
端口占用。宿主机上已经有 MySQL 在跑,3306 被占了。这种情况 docker logs 会显示端口绑定失败,把 -p 3306:3306 改成 -p 3307:3306,外部访问就用 3307。你本机没装 MySQL 但端口被占,用 netstat -tlnp | grep 3306 查是谁。
镜像里 MySQL 容器初始化时间很长。有些机器磁盘慢,初始化可能要两三分钟,不要一看到日志没输出就以为失败了。也有可能是内存不足,MySQL 8.0 启动内存要求不低,在低配机器上建议给容器加个内存限制,比如 -m 1g,或者调低 innodb_buffer_pool_size。
6. 实战二:用 Docker 部署 Redis 主从与网络隔离
6.1 Redis 主从的容器化设计思路
Redis 主从复制是经典的高可用方案,主节点负责写,从节点负责读,数据实时同步。用 Docker 部署的时候就涉及一个新问题:多个容器之间的通信。
默认情况下,容器和容器之间可以通过 Docker 内部网络互通,但是 IP 是不固定的,每次重建容器 IP 都会变。这显然不能用于服务发现,所以 Docker 提供了自定义网络(Custom Network)的功能。在同一个自定义网络里的容器,可以通过容器名互相访问,不需要关心 IP 变化。
Redis 主从部署的时候,从节点配置里需要指定主节点的地址,这时候直接用容器名解析即可。如果不做自定义网络,你得去查容器 IP,写死进去,容器一重建就失效,这个坑我替你先踩了。
6.2 创建网络并启动主从容器
先创建一个自定义网络:
启动 Redis 主节点:
redis:7.0 后面的部分是覆盖容器默认启动命令,用 redis-server 启动并开启 AOF 持久化。--appendonly yes 让 Redis 每次写操作都追加到持久化文件里,容器重启数据不丢。
再启动从节点:
关键参数是 --slaveof redis-master 6379,它告诉从节点“你的主节点在 redis-master 这个地址的 6379 端口”。因为两个容器在同一个自定义网络里,这里直接写容器名就行。
从节点端口映射我用的是 6380:6379,目的是不跟主节点的 6379 冲突。你不映射 6380 也可以,因为从节点通常不需要外部直接访问,内网通信走的是 Docker 网络,不经过端口映射。
6.3 验证主从复制是否生效
启动完成后,验证一下主从状态。进入主节点容器写入数据:
然后进从节点查:
能返回 hello,说明从节点已经同步了主节点的数据。再查看复制信息:
输出里的 master_link_status 字段如果是 up,就说明主从连接正常。
这里有个细节值得说:从节点默认是只读的,你往从节点写数据会报错,这是对的。Redis 主从本身就是一主多从、读写分离的架构,想要高可用还需要引入哨兵(Sentinel)集群,但那个复杂度就上去了。作为容器化入门,能把主从复制跑通就足够理解 Docker 网络和数据卷的精髓了。
6.4 数据持久化的选择:RDB vs AOF
用 Docker 跑 Redis,数据持久化文件放哪里、用哪种方式,也是要注意的。
Redis 持久化有两种方式。RDB 是定期全量快照,恢复快但是可能丢最近几分钟的数据;AOF 是追加写日志,最多丢 1 秒的数据(取决于 appendfsync 策略),恢复慢一点但数据更安全。我建议数据比较重要的场景开 AOF,不重要的可以只开 RDB 甚至都关掉,当纯缓存用。
我在命令里用了 --appendonly yes 开启 AOF,持久化文件就在宿主机 /data/redis/master 和 /data/redis/slave 目录下。容器删了重建,只要挂载目录还在,数据就还在。
有一点要注意,容器里的 Redis 默认跑在 /data 目录做持久化,你挂载宿主机目录的时候也要对应挂 /data,不要挂错成别的路径。我之前就是挂到 /home,Redis 写文件时提示权限异常,折腾了十几分钟才反应过来。
7. 全栈项目容器化实战:用 Docker Compose 编排完整应用
7.1 全栈项目为什么需要 Compose
全栈项目跟单个中间件最大的区别在于组件多。一个典型的中小型全栈项目,通常包含:
- 前端:Nginx 托管静态资源,反向代理 API
- 后端:Spring Boot 或 Node.js 应用,提供 REST API
- 数据库:MySQL
- 缓存:Redis
- 消息队列:RabbitMQ(可选)
如果一个个手动 docker run,你需要记住每个容器的端口、网络、数据卷、环境变量,还要保证启动顺序:先数据库,再后端,最后前端。一旦中间某个容器重建,IP 还会变,连锁问题一堆。
Docker Compose 把这一切固化为代码。你写一个 docker-compose.yml,把每个服务的信息描述清楚,然后在项目根目录执行一条命令,整条链路全部起来。
7.2 一个可以直接抄的 docker-compose.yml 模板
我以一个经典的 “Nginx + Node.js + MySQL + Redis” 全栈结构为例,给出一份 Compose 文件。这份文件我自己在项目里改一改就能用,读者拿到手改几个环境变量即可。
这个文件里值得拆开讲的细节有几个。
depends_on 控制启动顺序,但它只保证“先启动依赖服务”,不保证“依赖服务已经就绪”。比如 MySQL 容器启动了,但初始化还没完成,后端直接连数据库就会失败。解决办法是在应用代码里加数据库连接重试逻辑,或者用健康检查,这是初学 Compose 最容易忽略的点。
build.context 和 dockerfile 表示该服务不是从远程镜像直接跑,而是根据当前目录下的 Dockerfile 构建镜像。后端代码写在 ./backend 目录,编写 Dockerfile 把项目打包成镜像。这样整个团队拿到 Compose 文件后,执行 docker compose up --build 就能跑起来,不用手动先 build 镜像。
./init:/docker-entrypoint-initdb.d 这个挂载是 MySQL 镜像的特性:它在首次初始化数据库时,会自动执行挂载目录里的 .sql 脚本。我习惯把建表语句放在这个目录下,这样新环境第一次启动,数据库表结构就自动创建好了。
7.3 后端 Dockerfile 的写法与多阶段构建
后端如果是 Node.js 项目,Dockerfile 可以这么写:
这里用到了多阶段构建(Multi-stage Build)。第一阶段用完整 node 环境安装依赖并构建产物,第二阶段只复制构建产物和运行时依赖,镜像体积会小很多。一个包含 devDependencies 的镜像可能 1G 多,多阶段构建后能压到 300M 以内。
Java 后端的 Dockerfile 大体类似,常见的是基于 Maven 构建,再用 JRE 运行:
注意 COPY pom.xml . 先执行 mvn dependency:go-offline,这一步是把依赖先拉下来打缓存,之后源码变动重建时不会重复下载依赖,构建速度快很多。这是一个很常用的 Docker 构建优化技巧。
7.4 前端 Nginx 配置与反向代理
前端在容器里托管静态文件后,还要处理 API 请求,否则浏览器访问页面时,接口调用因为跨域问题就断了。
我的做法是用 Nginx 做反向代理,把 /api 开头的请求转发到后端服务。因为前端容器和后端容器在同一个 Compose 网络里,Nginx 配置里可以直接写后端容器名:
这段配置放在 nginx.conf 文件里,通过 Compose 文件挂载到了容器内 /etc/nginx/conf.d/default.conf。proxy_pass http://backend:8080/ 里的 backend 就是 Compose 里后端服务的容器名,Docker 内部的 DNS 会自动解析到它的 IP。
try_files $uri $uri/ /index.html; 是前端路由必需的,否则刷新页面时 Nginx 会去找真实的文件路径,找不到就 404,而你的 Vue 或 React 应用的路由需要始终回到 index.html 由前端路由接管。
整体起来后,访问 http://服务器IP 就能看到前端页面,接口请求自动转发到后端,后端连接 MySQL 和 Redis,一个完整的全栈应用就在一台机器上全部容器化跑起来了。
7.5 Compose 常用运维命令速查
Compose 日常使用频率最高的几个命令,我放在这里:
用 down -v 要极度谨慎,-v 会把数据卷一起删掉,MySQL 和 Redis 的数据全没。我在测试环境用过一次,当场数据清空,幸好是测试库。
8. 常见问题排查与避坑指南
8.1 高频报错与排查方法速查表
我把实际使用中遇到的高频问题整理成一张表,按出现频率排序,基本涵盖了大家搜索量最高的那几类问题:
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
| Docker Desktop 启动失败,提示虚拟化不支持 | BIOS 虚拟化未开启 / Windows 功能组件缺失 | 开 BIOS 虚拟化,启用 WSL 和虚拟机平台,必要时用 dism 命令强制启用 |
docker 服务启动失败,dockerd 报错 |
配置错误 / 内核版本过低 | systemctl status docker 看具体错误,核对 daemon.json 语法 |
| 镜像下载慢或超时 | 默认连 Docker Hub 慢 | 配置国内镜像加速,换可用加速地址 |
| 普通用户执行 docker 命令权限不足 | 用户不在 docker 组 | sudo usermod -aG docker $USER 后重新登录 |
| 容器网络不通,容器间无法互相访问 | 不在同一个自定义网络 | 创建自定义网络并让相关容器加入同一网络 |
| MySQL 容器启动后立刻退出 | 挂载目录权限 / 环境变量不完整 | 查看 docker logs mysql8,修复目录权限,确认初始化参数 |
| 容器删掉后数据丢失 | 没有挂载数据卷 | 启动时加 -v 挂载宿主机目录到容器数据目录 |
| docker compose 服务启动顺序不对 | 只依赖 depends_on |
应用层加重试逻辑,或配置健康检查 |
| 端口被占用导致容器启动失败 | 宿主机端口已被监听 | netstat -tlnp 查端口占用,换映射端口 |
| Nginx 刷新页面 404 | 前端为 SPA 路由,Nginx 没有回退 | 配置 try_files $uri $uri/ /index.html; |
8.2 容器网络不通的排查思路
“docker 网络不通”是热词里出现频率极高的一个问题。我讲一个最典型的场景:你写了一个应用容器想连本机另一个容器,发现连不上。
排查的顺序是这样的:
第一步,确认容器在同一条网络。运行 docker inspect <容器名> | grep -A 100 "Networks",看看两个容器的网络归属。如果不一样,把它们加入同一个自定义网络,或者用容器 IP 确认能 ping 通。
第二步,确认你用的连接地址是正确的。在自定义网络里,要用容器名而不是 localhost,因为在容器里 localhost 指的是容器自己,不是宿主机,更不是其他容器。
第三步,确认目标服务的监听地址。很多应用默认监听 127.0.0.1,比如 MySQL 在容器里如果只配置了 bind-address 为 127.0.0.1,其他容器是访问不到的,要改成 0.0.0.0 或在启动命令中指定绑定所有地址。
最后,进入容器做连通性测试:
能通,说明网络没问题;不能通,按上述步骤逐层排查。
8.3 镜像下载和构建的几个实用技巧
镜像下载慢的问题前面讲过了,补充几个实用技巧。
一是给镜像打标签时注意仓库地址。如果你从 docker.m.technicalurl.com/library/mysql:8.0 拉了一个镜像,它的全名就是这个,直接 docker run mysql:8.0 会找不到,因为 Docker 会去默认仓库找。要么用全名跑,要么 docker tag 重新打个短标签。
二是构建镜像时注意缓存失效问题。Dockerfile 里 COPY 的文件如果频繁变动,缓存容易失效。把依赖安装步骤放在 COPY 源码之前,能最大限度利用缓存。这就是我在 Node 和 Java Dockerfile 里先复制 package.json / pom.xml 再安装依赖的原因。
三是清理无用资源。容器、镜像、数据卷、网络会不断积累,磁盘空间不知不觉就满了。一套清理命令:
docker system prune -a 会把所有未运行的容器、未使用的镜像全部删掉,如果之后还要用,就得重新拉取,所以慎用。
8.4 内存与磁盘吃紧时的容器资源限制
服务器配置不高的话,多个容器一起跑很容易把内存占满。Docker 允许在启动时限制容器资源:
-m 1g 限制内存上限 1G,--memory-swap 1g 表示不额外使用 swap,防止容器把内存打到宿主机爆掉。Compose 文件里对应的写法是:
N100 这种低功耗小主机跑 20 个容器,核心就是严格控制每个容器的资源占用。镜像尽量选 alpine 版本,体积小、内存占用低,启动也快。一个 node:18-alpine 镜像 70M 左右,而完整版 node 镜像要 350M 以上。
9. 写在最后的实际操作体会
Docker 学了和用了是两回事。我在实际项目里最大的收获,是它彻底改变了“部署”这个动作的体验:以前部署一个全栈项目,要先装 MySQL、配 Redis、部署 Nginx、拉代码、装依赖、改配置,一套下来至少折腾大半天;现在写一个 Compose 文件,新服务器上一条 docker compose up -d,十分钟之内整套环境就绪。
最后再分享几个我踩过之后特别想告诉你的点。
第一,数据卷能挂就挂。我刚开始用 Docker 时图省事,MySQL 数据直接放容器里,后来电脑重启容器异常,数据全丢,当时心态直接炸了。从那次以后,任何有状态的服务,启动参数里第一件事就是挂数据卷。
第二,容器名和网络规划要提前想。项目一多,容器名如果乱起,后面维护的时候根本分不清谁是谁。我现在的习惯是 项目名-服务名 的格式,比如 blog-mysql、blog-backend。容器之间通信一律用自定义网络加容器名,不用 IP,省得容器重建后 IP 变了抓瞎。
第三,日志才是排错的第一入口。任何容器启动失败、连接失败,先跑 docker logs <容器名>,它给你的信息往往比搜索引擎准确得多。启动失败不等于 Docker 坏了,大多数时候是你自己的参数配错了。
这篇从光盘类比讲起,把 Docker 的核心概念、安装、镜像加速、中间件部署、全栈 Compose 编排到常见问题排查全部串了一遍。东西不少,但只要跟着实操一遍,你就会发现容器化这件事,真的没有想象中那么难。