我再也不敢随手 `docker compose down -v` 了

0 阅读3分钟

这次复盘一个很典型的 Compose 维护事故。

一个小团队的内部工具用 Docker Compose 跑,里面有 apppostgresredisnginx。升级前大家习惯性执行:

docker compose pull
docker compose up -d

结果应用起来了,数据库却像新装的一样。排查到最后,不是 Postgres 镜像坏了,也不是应用迁移脚本漏跑,而是数据卷管理一直很随意:旧环境有匿名卷,新环境项目名变了;另一次清理环境时还执行过 docker compose down -v

这篇按工程复盘写,重点是 Compose 数据卷、数据库备份和恢复验证。

先看最终配置

维护前第一条命令不是 pull,而是:

docker compose config

我主要看三块:

  • 数据库服务的 volumes
  • image 是否固定 tag。
  • 环境变量里数据库名、用户、密码是否清楚。

一个更稳的 Postgres 写法:

services:
  db:
    image: docker.1ms.run/postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: change-me
      POSTGRES_DB: app
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:

不一定所有项目都要这么写,但数据库目录不能飘在匿名卷里,至少要知道数据卷叫什么。

down 不等于删数据,down -v 才危险

这个误区很常见。

docker compose down 默认不会删除命名卷。真正要小心的是:

docker compose down -v

它会把 Compose 文件里声明的命名卷和容器使用的匿名卷一起移除。对无状态服务来说,这可能只是清理;对数据库来说,这就是销毁状态。

匿名卷更麻烦。它默认不会被删,但名字不稳定。项目目录、项目名、服务定义变了以后,新环境可能直接生成新卷,你还以为数据库初始化失败。

我现在升级前固定查这几条

列卷:

docker volume ls

查卷详情:

docker volume inspect 项目名_db-data

查容器实际挂载:

docker inspect 容器名 --format '{{json .Mounts}}'

这三条能回答一个问题:数据库到底在写哪里?

如果这个问题答不上来,后面不要升级。

备份要能恢复

只说“我有备份”没用。备份必须在临时环境里恢复一次。

MySQL:

docker compose exec db sh -c 'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" app' > mysql-app.sql
cat mysql-app.sql | docker compose exec -T db sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" app'

Postgres:

docker compose exec -T db pg_dump -U app app > pg-app.sql
cat pg-app.sql | docker compose exec -T db psql -U app app

Postgres 跨大版本时还要看 PGDATA 和数据目录说明,不要把旧路径习惯直接套到新镜像上。

Redis 不一定只是缓存

Redis 容器最容易被一句“缓存丢了没事”带过去。

但实际项目里,Redis 可能放了:

  • 登录会话。
  • 任务队列。
  • 限流状态。
  • 延迟任务。
  • 临时业务状态。

如果这些状态对业务有影响,就要明确持久化:

services:
  redis:
    image: docker.1ms.run/redis:7
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis-data:/data

volumes:
  redis-data:

同时不要把无密码 Redis 暴露到公网。

镜像预检不是第一步

升级前我会预拉镜像,但它不是第一步。我的顺序现在固定成:

查配置 -> 查卷 -> 备份 -> 恢复演练 -> 预拉镜像 -> 升级 -> 验证

镜像预检这一步可以这样做:

docker pull docker.1ms.run/postgres:16
docker pull docker.1ms.run/mysql:8.4
docker pull docker.1ms.run/redis:7
docker pull docker.1ms.run/adminer:latest

这里用 docker.1ms.run 只是为了把镜像拉取这层提前排掉。镜像拉得下来,不代表数据安全;但镜像拉不下来,维护窗口也会被拖住。

复盘清单

问题检查方式
数据在哪个卷里docker volume inspect
Compose 最终配置是什么docker compose config
有没有匿名卷docker volume ls + docker inspect
down -v 会不会误删看脚本和维护手册
MySQL/Postgres 能否恢复临时环境导入一次
Redis 是否需要状态看业务是否依赖持久化
镜像是否可拉固定 tag 预拉

这次之后,我对 Compose 有状态服务的态度很简单:容器重建是日常操作,数据恢复必须提前证明。