# 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`，不要让新旧容器共用同一个卷。

旧写法：

```bash
-v pgdata:/var/lib/postgresql/data
```

迁移到 18 后，更稳妥的写法是同时做两件事：改挂载目标，并换一个新的卷名：

```bash
-v pgdata18:/var/lib/postgresql
```

这样由镜像内部自行使用 `18/docker` 这个版本化子目录，后续升级到 19 时也更一致；同时旧卷仍然完整保留，便于回滚和清理。

另外还要注意一件事：**PostgreSQL 跨大版本本来就不能直接复用旧数据目录**。也就是说，这次迁移不只是“改挂载路径”，还应该配合逻辑备份恢复，或者使用 `pg_upgrade` 之类的正式升级手段。本文采用的是最直观、最通用的 `pg_dumpall` 方案。

## 为什么会启动失败

很多现有配置都是这么写的：

```bash
podman run -d --name pg17 \
  -e POSTGRES_PASSWORD=secret \
  -v pgdata:/var/lib/postgresql/data \
  postgres:17
```

如果升级时只是把最后一行改成 `postgres:18`：

```bash
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`：

```bash
podman exec -t pg17 pg_dumpall -c -U postgres > backup.sql
```

参数说明：

- `-t`：分配伪终端。这里备份阶段可以这样用。
- `-c`：在导出内容中加入 `DROP` 语句，恢复时会先清理旧对象，减少冲突。
- `-U postgres`：使用 `postgres` 超级用户执行导出。

如果你只想导出单个数据库，也可以使用：

```bash
podman exec -t pg17 pg_dump -U postgres dbname > dbname.sql
```

## 2. 停掉旧容器

```bash
podman stop pg17
```

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

## 3. 启动 PostgreSQL 18 新容器

## `podman run` 方式

关键点有两个：

1. **卷挂载目标从 `/var/lib/postgresql/data` 改成 `/var/lib/postgresql`**。
2. **新容器使用新的命名卷，例如 `pgdata18`**。

```bash
podman run -d --name pg18 \
  -e POSTGRES_PASSWORD=你的密码 \
  -e POSTGRES_DB=你的数据库 \
  -p 5432:5432 \
  -v pgdata18:/var/lib/postgresql \
  postgres:18
```

这里故意没有继续使用旧的 `pgdata`。如果新旧容器共用同一个命名卷，旧的 17 数据和新的 18 数据会落在同一个卷里，虽然不一定立刻报错，但会让回滚、排错和后续清理都变得很混乱。

对比一下：

```bash
# 旧容器常见写法
-v pgdata:/var/lib/postgresql/data

# PostgreSQL 18 建议改成
-v pgdata18:/var/lib/postgresql
```

## Compose 方式

如果你使用 `compose.yml`，通常只需要改两处：

```yaml
services:
  db:
    image: postgres:18
    volumes:
      - pgdata18:/var/lib/postgresql

volumes:
  pgdata18:
```

如果原来写的是 `/var/lib/postgresql/data`，这里一定要同步改掉。并且如果你原来的命名卷叫 `pgdata`，迁移时也建议临时改成新卷名，例如 `pgdata18`，不要直接复用旧卷。

## 4. 恢复数据

## 方式一：直接通过管道恢复

中小型数据库用这个最省事：

```bash
cat backup.sql | podman exec -i pg18 psql -U postgres
```

这里要注意：恢复时用的是 `-i`，不是 `-t`。

- `-i`：保持标准输入，让 `psql` 能读取管道里的 SQL。
- 不要加 `-t`：恢复阶段分配 TTY 反而容易带来多余问题。

## 方式二：先复制文件再恢复

如果备份比较大，先把文件复制进容器再执行会更稳一些：

```bash
podman cp backup.sql pg18:/tmp/backup.sql
podman exec -i pg18 psql -U postgres -f /tmp/backup.sql
```

## 5. 验证迁移结果

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

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

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

## 6. 清理旧容器和备份

确认新容器运行正常后，再做清理：

```bash
# 删除旧容器
podman rm pg17

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

# 删除备份文件
rm backup.sql
```

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


---

> 作者: Nite  
> URL: https://www.nite07.com/zh-cn/posts/postgres-docker-18-migration/  

