数据卷与持久化
1. 概念
容器的文件系统是 临时的:容器销毁数据丢失。需要持久化的数据(DB、日志、上传文件、配置)必须挂载到容器外。
三种方式:
| 方式 | 管理 | 存储位置 | 适合 |
|---|---|---|---|
| volume | Docker 管理 | /var/lib/docker/volumes/ | 持久化数据(DB、Redis) |
| bind mount | 用户管理 | 宿主机任意路径 | 开发时源码热更 |
| tmpfs | 内存 | RAM | 临时、不落盘(密钥缓存) |
2. volume(推荐)
# 创建
docker volume create mydata
docker volume ls
docker volume inspect mydata
# 使用
docker run -v mydata:/var/lib/data myimage
docker run --mount source=mydata,target=/var/lib/data myimage
# 删除
docker volume rm mydata
docker volume prune # 删除未使用的
compose 里:
services:
db:
image: postgres
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata: # 声明 named volume
2.1 volume 特性
- 容器间共享
- 容器停/删数据仍在
- 可以用 volume driver 挂 NFS、cloud storage
- 性能优于 bind mount(macOS Docker Desktop 上差距巨大)
3. bind mount
直接挂载宿主目录到容器内:
docker run -v /host/path:/container/path myimage
docker run -v $(pwd)/src:/app/src myimage
docker run --mount type=bind,source=$(pwd),target=/app myimage
3.1 开发常用模式
services:
web:
volumes:
- .:/app # 源码热更
- /app/node_modules # 匿名 volume 隔离 node_modules
为什么隔离 node_modules:宿主(macOS)和容器(Linux)的 native 模块不兼容。让容器自己装 node_modules 在匿名 volume 里,不被宿主 node_modules 覆盖。
3.2 只读挂载
docker run -v /host/config:/etc/app/config:ro myimage
:ro 只读,容器不能修改。Nginx 挂配置文件用。
3.3 性能问题(macOS / Windows)
macOS Docker Desktop 的 bind mount 极慢(osxfs / gRPC-FUSE),npm install 可能慢 5-10 倍。解决:
- 使用 Docker Desktop VirtioFS(新版默认)
- 只挂源码,不挂 node_modules
- 用 volume 存 build cache
- 用
docker compose watch(File sync 功能)
4. tmpfs
docker run --tmpfs /tmp:size=100m myimage
docker run --mount type=tmpfs,destination=/tmp,tmpfs-size=100m myimage
内存文件系统,容器停数据消失。适合 token 缓存、临时文件。
5. 数据卷备份与恢复
# 备份 volume 到 tar
docker run --rm -v pgdata:/data -v $(pwd):/backup alpine \
tar czf /backup/pgdata-$(date +%Y%m%d).tar.gz -C /data .
# 恢复
docker run --rm -v pgdata:/data -v $(pwd):/backup alpine \
tar xzf /backup/pgdata-20260618.tar.gz -C /data
compose 定义的 volume 名有前缀(项目名_卷名):
docker volume ls | grep pgdata
# myproject_pgdata
6. 数据卷权限陷阱
容器内进程以 node(uid 1000)跑,但 volume 目录属主是 root(uid 0),写入 Permission denied。
解法:
# 方法 1:Dockerfile 里创建目录并 chown
RUN mkdir -p /data && chown node:node /data
VOLUME /data
USER node
# 方法 2:entrypoint 脚本修权限
COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/sh
# entrypoint.sh
chown -R node:node /data
exec gosu node "$@"
7. 在 CI 里复用 cache 卷
GitHub Actions:
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: docker-$}} hashFiles('**/package-lock.json') }}
- name: Build
uses: docker/build-push-action@v6
with:
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new
8. 常见反模式
- DB 数据存容器内:容器重建数据全丢
- bind mount 含 node_modules:平台不兼容
- 不清理 dangling volumes:磁盘被占。定期
docker volume prune - 日志写容器内 /var/log:销毁即丢。写 stdout 或挂 volume
- volume 属主不对:Permission denied
- macOS 全量 bind mount:慢到无法开发
- 不备份 named volume:等同不备份数据