05 数据卷与网络
这一章解决两个实际问题:容器删了数据怎么办(数据卷),以及容器之间怎么互相访问(网络)。
1. 为什么需要数据卷
容器的文件系统是随容器走的:容器一删,里面写的数据全没。数据库容器如果不做持久化,docker rm 一下数据就灰飞烟灭了。
解决办法:把数据存到容器外面,有两种方式。
2. 两种挂载方式
2.1 绑定挂载(Bind Mount):挂宿主机目录
把宿主机的某个目录直接挂进容器:
# -v 宿主机路径:容器路径
docker run -d --name web -p 8080:80 \
-v /data/html:/usr/share/nginx/html \
nginx:1.25
宿主机 /data/html 里的文件,容器里 /usr/share/nginx/html 立刻可见,双向实时同步。
适合场景:挂配置文件、挂代码、挂前端静态资源——你需要直接在宿主机上编辑这些文件。
还可以加 :ro 设为只读,防止容器改动宿主机文件:
-v /data/nginx.conf:/etc/nginx/nginx.conf:ro
2.2 命名卷(Named Volume):交给 Docker 管理
# 创建一个卷(也可以不创建,docker run 时自动建)
docker volume create mysql-data
# 挂载卷启动 MySQL
docker run -d --name db \
-e MYSQL_ROOT_PASSWORD=123456 \
-v mysql-data:/var/lib/mysql \
mysql:8.0
数据实际存在宿主机的 /var/lib/docker/volumes/mysql-data/_data,由 Docker 统一管理。
适合场景:数据库数据目录这类「不需要人肉直接编辑,但必须持久化」的数据。
2.3 怎么选
| 绑定挂载 | 命名卷 | |
|---|---|---|
| 写法 | -v /宿主机绝对路径:/容器路径 | -v 卷名:/容器路径 |
| 数据位置 | 你指定的目录 | Docker 管理的目录 |
| 适合 | 配置文件、代码、静态资源 | 数据库数据、应用产生的数据 |
区分技巧:
-v冒号前面带/的是路径(绑定挂载),不带/的是卷名(命名卷)。
2.4 卷的管理命令
docker volume ls # 列出所有卷
docker volume inspect mysql-data # 查看卷详情(含实际存储路径)
docker volume rm mysql-data # 删除卷(数据会丢,慎重)
docker volume prune # 清理没有被任何容器使用的卷
3. 验证数据持久化
做个实验,感受数据卷的作用:
# 1. 带卷启动 MySQL,建个库
docker run -d --name db -e MYSQL_ROOT_PASSWORD=123456 -v mysql-data:/var/lib/mysql mysql:8.0
docker exec -it db mysql -uroot -p123456 -e "CREATE DATABASE demo;"
# 2. 删掉容器!
docker rm -f db
# 3. 用同一个卷重新起一个容器
docker run -d --name db2 -e MYSQL_ROOT_PASSWORD=123456 -v mysql-data:/var/lib/mysql mysql:8.0
# 4. demo 库还在
docker exec -it db2 mysql -uroot -p123456 -e "SHOW DATABASES;"
4. 容器网络
网络是多容器部署的地基。后面用 Docker Compose 一波部署 MySQL + Redis + 后端 + Nginx 时,服务之间怎么互相访问、端口该怎么写,答案全在这一节。
4.1 问题:后端容器怎么连数据库容器?
假设后端应用和 MySQL 各跑一个容器,后端连数据库时写 localhost:3306?不行——容器里的 localhost 指的是容器自己,不是宿主机,更不是别的容器。
正确做法:把两个容器放进同一个自定义网络,然后用容器名当主机名访问。
4.2 先建立画面感:网络就是虚拟交换机
把每个容器想象成一台独立的小电脑:有自己的 IP、自己的 localhost。而 Docker 网络就是一台虚拟交换机——插在同一台交换机上的容器互相能连通,不在同一个网络里的互相看不见。
两个关键事实:
- 自定义网络自带 DNS:容器名会自动解析成对应容器的 IP。容器的 IP 每次重启可能变,但名字不变,所以互访永远写名字、不写 IP。
- 网络就是隔离边界:不同网络里的容器互相不通。这既是坑(忘了加同一个网络就连不上),也是安全手段(数据库只放内网,外面摸不到)。
4.3 自定义网络实战
# 1. 创建网络
docker network create mynet
# 2. 启动 MySQL,加入网络
docker run -d --name db --network mynet \
-e MYSQL_ROOT_PASSWORD=123456 \
-v mysql-data:/var/lib/mysql \
mysql:8.0
# 3. 启动应用容器,加入同一个网络
docker run -d --name api --network mynet -p 8000:8000 myapi:1.0
现在在 api 容器里,直接用 db 这个名字就能访问 MySQL:
# 应用里的数据库连接串写:
mysql+pymysql://root:123456@db:3306/demo
# ^^ 容器名即主机名
这就是 4.2 说的内置 DNS 在起作用:db 被自动解析成 db 容器的 IP。这就是容器互联的标准姿势。
验证一下:
# 在 api 容器里 ping db
docker exec -it api ping db
4.4 最容易搞混的问题:该连哪个端口?
假设 MySQL 容器这样启动(宿主机 3306 被占了,映射到 13306):
docker run -d --name db --network mynet -p 13306:3306 mysql:8.0
现在出现了两个端口,谁用哪个?
- 同一网络里的容器互访:用服务的内部端口(
db:3306),跟-p映射成多少完全无关——容器之间走的是虚拟交换机,根本不经过宿主机端口 - 宿主机(或外部)访问容器:才走映射端口(
127.0.0.1:13306)
记一句话:-p 是给「网络外面的人」开的门,网络里面的容器串门不走这个门。 反过来也成立——像 4.3 里的 db 根本没写 -p,api 照样能连它,只是宿主机连不上而已。
4.5 默认网络的坑
不指定 --network 时,容器会进默认的 bridge 网络。默认网络里不支持用容器名互相访问(只能用 IP,而 IP 每次重启可能变)。所以:
只要涉及容器互联,就创建自定义网络。
4.6 到了 Compose,这一切全自动
上面手动建网络、逐个 --network 的操作,Docker Compose 会全包了。执行 docker compose up 时它自动做三件事:
- 建一个专属网络,名字是
项目目录名_default - 把 compose.yaml 里的所有服务都加进这个网络
- 服务名自动成为主机名——这就是为什么 Compose 项目里连接串直接写
mysql:3306、redis:6379
services:
api:
build: .
ports:
- "8000:8000" # 只有需要被浏览器/外部访问的服务才写 ports
environment:
- DB_URL=mysql+pymysql://root:123456@mysql:3306/demo # 主机名 = 服务名
mysql:
image: mysql:8.0 # 没写 ports:只有同网络的 api 能访问它,最安全
redis:
image: redis:7
对照 4.4 的结论看:mysql、redis 不写 ports 一点问题没有,api 走的是容器网络;而 nginx/api 这类要接待外部流量的服务才需要 ports。**「只有入口服务映射端口,其余全藏在网络里」**就是 Compose 一波部署 MySQL + Redis + 前后端时的标准布局,后面的 Compose 全栈实战 会完整走一遍。
需要更细的隔离(比如 nginx 不允许直连数据库)时,才在 Compose 里手写多个网络,这个在 Compose 配置详解的 networks 一节讲。
4.7 网络管理与排查命令
docker network ls # 列出网络
docker network inspect mynet # 查看网络详情(哪些容器在里面、各自的 IP)
docker network connect mynet 容器名 # 把已运行的容器加进网络
docker network rm mynet # 删除网络
容器 A 连不上容器 B 时,按这个顺序排查:
# 1. 两个容器在同一个网络里吗?看 Containers 列表
docker network inspect mynet
# 2. 名字能解析、网络能通吗?
docker exec -it api ping db
# 3. 网络通但服务没响应?多半是 B 里的应用没起来或没监听对
docker exec -it api curl http://web:8000/health # B 是 HTTP 服务就 curl 它
docker logs db # B 是数据库就看它的启动日志
第 3 步很常见:MySQL 容器状态是 Up,但内部还在初始化,连接会被拒。这不是网络问题,是「启动了 ≠ 就绪了」,到 Compose 里用 healthcheck 解决。
4.8 容器访问宿主机
有时容器里的应用要访问跑在宿主机上的服务(比如宿主机直接装的 MySQL):
- Windows / macOS:用特殊域名
host.docker.internal - Linux:启动时加
--add-host=host.docker.internal:host-gateway,然后同样用这个域名
5. 端口映射回顾
-p 参数本质也是网络知识,完整格式:
-p 8080:80 # 宿主机所有网卡的 8080 → 容器 80
-p 127.0.0.1:3306:3306 # 只允许本机访问,不对外网开放(数据库推荐)
-p 80:80 -p 443:443 # 可以写多个
安全提醒:
-p 3306:3306会把数据库直接暴露到公网(Docker 还会自动绕过部分防火墙规则)。生产环境数据库要么不映射端口(只走容器网络),要么绑定127.0.0.1。
小结
- 数据卷解决持久化:配置/代码用绑定挂载,数据库数据用命名卷
- 删容器不删卷,数据就不会丢
- 容器互联:创建自定义网络 + 用容器名当主机名,容器里的
localhost是容器自己 - 端口分两种:容器互访走内部端口,外部访问才走
-p映射端口,两者无关 - Compose 会自动建网络、自动把服务名变成主机名——只有入口服务映射端口,其余全藏在网络里
- 数据库端口不要随便暴露到公网
Docker 基础到此完成。但你可能已经感觉到:一个应用要敲好几条长长的 docker run,太麻烦了。下一部分的 Docker Compose 就是来解决这个问题的:01-认识Compose