跳到主要内容

数据卷与持久化

1. 概念

容器的文件系统是 临时的:容器销毁数据丢失。需要持久化的数据(DB、日志、上传文件、配置)必须挂载到容器外。

三种方式:

方式管理存储位置适合
volumeDocker 管理/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 倍。解决:

  1. 使用 Docker Desktop VirtioFS(新版默认)
  2. 只挂源码,不挂 node_modules
  3. 用 volume 存 build cache
  4. 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:等同不备份数据

9. 延伸阅读