这次复盘一个很典型的 Compose 维护事故。
一个小团队的内部工具用 Docker Compose 跑,里面有 app、postgres、redis、nginx。升级前大家习惯性执行:
bash
docker compose pull
docker compose up -d
结果应用起来了,数据库却像新装的一样。排查到最后,不是 Postgres 镜像坏了,也不是应用迁移脚本漏跑,而是数据卷管理一直很随意:旧环境有匿名卷,新环境项目名变了;另一次清理环境时还执行过 docker compose down -v。
这篇按工程复盘写,重点是 Compose 数据卷、数据库备份和恢复验证。
先看最终配置
维护前第一条命令不是 pull,而是:
bash
docker compose config
我主要看三块:
- 数据库服务的
volumes。 image是否固定 tag。- 环境变量里数据库名、用户、密码是否清楚。
一个更稳的 Postgres 写法:
yaml
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 默认不会删除命名卷。真正要小心的是:
bash
docker compose down -v
它会把 Compose 文件里声明的命名卷和容器使用的匿名卷一起移除。对无状态服务来说,这可能只是清理;对数据库来说,这就是销毁状态。
匿名卷更麻烦。它默认不会被删,但名字不稳定。项目目录、项目名、服务定义变了以后,新环境可能直接生成新卷,你还以为数据库初始化失败。
我现在升级前固定查这几条
列卷:
bash
docker volume ls
查卷详情:
bash
docker volume inspect 项目名_db-data
查容器实际挂载:
bash
docker inspect 容器名 --format '{{json .Mounts}}'
这三条能回答一个问题:数据库到底在写哪里?
如果这个问题答不上来,后面不要升级。
备份要能恢复
只说"我有备份"没用。备份必须在临时环境里恢复一次。
MySQL:
bash
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:
bash
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 可能放了:
- 登录会话。
- 任务队列。
- 限流状态。
- 延迟任务。
- 临时业务状态。
如果这些状态对业务有影响,就要明确持久化:
yaml
services:
redis:
image: docker.1ms.run/redis:7
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redis-data:/data
volumes:
redis-data:
同时不要把无密码 Redis 暴露到公网。
镜像预检不是第一步
升级前我会预拉镜像,但它不是第一步。我的顺序现在固定成:
text
查配置 -> 查卷 -> 备份 -> 恢复演练 -> 预拉镜像 -> 升级 -> 验证
镜像预检这一步可以这样做:
bash
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 有状态服务的态度很简单:容器重建是日常操作,数据恢复必须提前证明。