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。结果就是:新版本实际使用的数据目录没有正确持久化,或者初始化流程与旧挂载方式冲突,最终导致启动失败。

所以,比较稳妥的处理方式是:

  1. 先从旧容器导出逻辑备份。
  2. 停掉旧容器,但保留旧卷。
  3. 用新的挂载路径和新的卷名启动 postgres:18
  4. 把备份恢复进去。

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.sql

2. 停掉旧容器

podman stop pg17

这一步先不要删旧卷,保留现场,方便回滚。

3. 启动 PostgreSQL 18 新容器

podman run 方式

关键点有两个:

  1. 卷挂载目标从 /var/lib/postgresql/data 改成 /var/lib/postgresql
  2. 新容器使用新的命名卷,例如 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/postgresql

Compose 方式

如果你使用 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.sql

5. 验证迁移结果

恢复完成后,建议至少做一次基本检查:

podman exec -it pg18 psql -U postgres -c "\\l"

确认数据库列表正常后,再进入业务应用侧验证连接是否成功、表和数据是否完整。

6. 清理旧容器和备份

确认新容器运行正常后,再做清理:

# 删除旧容器
podman rm pg17

# 确认一切正常后,再手动删除旧卷
# podman volume rm pgdata

# 删除备份文件
rm backup.sql

旧卷一定要最后删。只要旧卷还在,回滚就容易很多。上面的 pgdata 指的是旧容器原来使用的那个卷;新容器示例里使用的是新的 pgdata18