07_dockerfile/7.8_volume.md
VOLUME ["/路径1", "/路径2"]
VOLUME /路径
VOLUME 指令创建挂载点,并标记为外部挂载的卷。
核心原则:容器存储层应该保持无状态,任何运行时数据都应该存储在卷中。
flowchart LR
subgraph NoVolume ["没有 VOLUME:"]
direction TB
subgraph Container1 ["容器存储层"]
direction TB
Files["数据库文件 (问题)
日志文件
上传文件"]
end
Result1["容器删除 = 数据丢失"]
Container1 ~~~ Result1
end
subgraph UseVolume ["使用 VOLUME:"]
direction TB
Container2["容器存储层
(只读/无状态)"]
subgraph Volume ["数据卷"]
Data["持久化数据 (安全)"]
end
Container2 --> Volume
Result2["容器删除,数据保留"]
Volume ~~~ Result2
end
FROM mysql:8.4
VOLUME /var/lib/mysql
FROM myapp
VOLUME ["/data", "/logs", "/config"]
如果运行时未指定挂载,Docker 会自动创建匿名卷:
$ docker run mysql:8.4
$ docker volume ls
DRIVER VOLUME NAME
local a1b2c3d4e5f6... # 自动创建的匿名卷
## 使用命名卷替代匿名卷
$ docker run -v mysql_data:/var/lib/mysql mysql:8.4
## 使用宿主机目录替代
$ docker run -v /my/data:/var/lib/mysql mysql:8.4
⚠️ 重要:VOLUME 之后对该目录的修改会被丢弃!
FROM ubuntu
VOLUME /data
## ❌ 这个文件不会出现在镜像中!
RUN echo "hello" > /data/test.txt
原因:在构建过程中,VOLUME 指令会为该目录创建一个临时的匿名卷。后续 RUN 指令对该目录的写入实际发生在这个临时卷中,而非镜像层。当该 RUN 指令结束后,临时卷被丢弃,因此写入的内容不会保存到最终镜像中。注意:这与容器运行时创建的匿名卷是不同的——运行时创建的卷会在容器生命周期内持续存在。
FROM ubuntu
## ✅ 先写入文件
RUN mkdir -p /data && echo "hello" > /data/test.txt
## 再声明 VOLUME
VOLUME /data
# 建议使用 postgres:16 或 postgres:latest,具体版本号根据数据库兼容性需求选择
FROM postgres:16
VOLUME /var/lib/postgresql/data
FROM nginx
VOLUME /var/log/nginx
FROM myapp
VOLUME /app/uploads
## 查看镜像定义的 VOLUME
$ docker inspect mysql:8.4 --format '{{json .Config.Volumes}}' | jq
{
"/var/lib/mysql": {}
}
## 查看容器挂载的卷
$ docker inspect mycontainer --format '{{json .Mounts}}' | jq
| 特性 | Dockerfile VOLUME | docker run -v |
|---|---|---|
| 定义时机 | 镜像构建时 | 容器运行时 |
| 默认行为 | 创建匿名卷 | 可指定命名卷或路径 |
| 灵活性 | 低 (固定路径) | 高 (可任意指定) |
| 适用场景 | 定义必须持久化的路径 | 灵活的数据管理 |
在 Compose 中配置如下:
services:
db:
# 建议使用 postgres:16 或其他具体版本,避免 latest
image: postgres:16
volumes:
# 命名卷(推荐)
- postgres_data:/var/lib/postgresql/data
# Bind Mount
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
volumes:
postgres_data: # 声明命名卷
## 使用 --rm 运行的容器,匿名卷会在容器删除时一起删除
$ docker run --rm mysql:8.4
## 容器停止后,数据丢失!
...
解决:始终使用命名卷
$ docker run -v mysql_data:/var/lib/mysql mysql:8.4
## 数据库必须使用卷
FROM postgres:16
VOLUME /var/lib/postgresql/data
## ❌ 避免
VOLUME /app/data
RUN cp init-data.json /app/data/
## ✅ 正确
RUN mkdir -p /app/data && cp init-data.json /app/data/
VOLUME /app/data
## 持久化用户上传的文件
VOLUME /app/uploads
## 持久化数据库数据
VOLUME /var/lib/mysql