PostgreSQL 18 Docker 升级:从 17 迁移
PostgreSQL 18 官方 Docker 镜像有一个很容易踩坑的变更:默认数据目录不再是旧版常见的 /var/lib/postgresql/data,而是变成了带版本号的新路径。
如果你升级时只把镜像标签从 postgres:17 改成 postgres:18,但仍然沿用旧的卷挂载目标,容器很可能直接启动失败。本文整理一个最稳妥的迁移流程:先逻辑备份,再重建 18 容器,最后恢复数据。
本文命令使用
podman演示;如果你用的是 Docker,把命令中的podman替换成docker即可。
要点
PostgreSQL 官方镜像默认数据目录的变化如下:
| 版本 | 默认 PGDATA 路径 |
|---|---|
| 17 及更早 | /var/lib/postgresql/data |
| 18 | /var/lib/postgresql/18/docker |
| 未来 19 | /var/lib/postgresql/19/docker |
所以卷挂载目标也需要跟着调整。
这里说的首先是冒号右边的容器内挂载路径。但在实际迁移时,冒号左边的命名卷名称也建议一起换掉,例如从 pgdata 改成 pgdata18,不要让新旧容器共用同一个卷。
旧写法:
-v pgdata:/var/lib/postgresql/data迁移到 18 后,更稳妥的写法是同时做两件事:改挂载目标,并换一个新的卷名:
-v pgdata18:/var/lib/postgresql这样由镜像内部自行使用 18/docker 这个版本化子目录,后续升级到 19 时也更一致;同时旧卷仍然完整保留,便于回滚和清理。
另外还要注意一件事:PostgreSQL 跨大版本本来就不能直接复用旧数据目录。也就是说,这次迁移不只是“改挂载路径”,还应该配合逻辑备份恢复,或者使用 pg_upgrade 之类的正式升级手段。本文采用的是最直观、最通用的 pg_dumpall 方案。
为什么会启动失败
很多现有配置都是这么写的:
podman run -d --name pg17 \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:17如果升级时只是把最后一行改成 postgres:18:
podman run -d --name pg18 \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:18那么容器仍然把卷挂在旧位置,但 PostgreSQL 18 默认会去使用 /var/lib/postgresql/18/docker。结果就是:新版本实际使用的数据目录没有正确持久化,或者初始化流程与旧挂载方式冲突,最终导致启动失败。
所以,比较稳妥的处理方式是:
- 先从旧容器导出逻辑备份。
- 停掉旧容器,但保留旧卷。
- 用新的挂载路径和新的卷名启动
postgres:18。 - 把备份恢复进去。
1. 备份旧数据
最简单的方式是直接在旧容器里执行 pg_dumpall:
podman exec -t pg17 pg_dumpall -c -U postgres > backup.sql参数说明:
-t:分配伪终端。这里备份阶段可以这样用。-c:在导出内容中加入DROP语句,恢复时会先清理旧对象,减少冲突。-U postgres:使用postgres超级用户执行导出。
如果你只想导出单个数据库,也可以使用:
podman exec -t pg17 pg_dump -U postgres dbname > dbname.sql2. 停掉旧容器
podman stop pg17这一步先不要删旧卷,保留现场,方便回滚。
3. 启动 PostgreSQL 18 新容器
podman run 方式
关键点有两个:
- 卷挂载目标从
/var/lib/postgresql/data改成/var/lib/postgresql。 - 新容器使用新的命名卷,例如
pgdata18。
podman run -d --name pg18 \
-e POSTGRES_PASSWORD=你的密码 \
-e POSTGRES_DB=你的数据库 \
-p 5432:5432 \
-v pgdata18:/var/lib/postgresql \
postgres:18这里故意没有继续使用旧的 pgdata。如果新旧容器共用同一个命名卷,旧的 17 数据和新的 18 数据会落在同一个卷里,虽然不一定立刻报错,但会让回滚、排错和后续清理都变得很混乱。
对比一下:
# 旧容器常见写法
-v pgdata:/var/lib/postgresql/data
# PostgreSQL 18 建议改成
-v pgdata18:/var/lib/postgresqlCompose 方式
如果你使用 compose.yml,通常只需要改两处:
services:
db:
image: postgres:18
volumes:
- pgdata18:/var/lib/postgresql
volumes:
pgdata18:如果原来写的是 /var/lib/postgresql/data,这里一定要同步改掉。并且如果你原来的命名卷叫 pgdata,迁移时也建议临时改成新卷名,例如 pgdata18,不要直接复用旧卷。
4. 恢复数据
方式一:直接通过管道恢复
中小型数据库用这个最省事:
cat backup.sql | podman exec -i pg18 psql -U postgres这里要注意:恢复时用的是 -i,不是 -t。
-i:保持标准输入,让psql能读取管道里的 SQL。- 不要加
-t:恢复阶段分配 TTY 反而容易带来多余问题。
方式二:先复制文件再恢复
如果备份比较大,先把文件复制进容器再执行会更稳一些:
podman cp backup.sql pg18:/tmp/backup.sql
podman exec -i pg18 psql -U postgres -f /tmp/backup.sql5. 验证迁移结果
恢复完成后,建议至少做一次基本检查:
podman exec -it pg18 psql -U postgres -c "\\l"确认数据库列表正常后,再进入业务应用侧验证连接是否成功、表和数据是否完整。
6. 清理旧容器和备份
确认新容器运行正常后,再做清理:
# 删除旧容器
podman rm pg17
# 确认一切正常后,再手动删除旧卷
# podman volume rm pgdata
# 删除备份文件
rm backup.sql旧卷一定要最后删。只要旧卷还在,回滚就容易很多。上面的 pgdata 指的是旧容器原来使用的那个卷;新容器示例里使用的是新的 pgdata18。