# PostgreSQL 18 Docker Upgrade: Migrating From 17


The official PostgreSQL 18 Docker image includes an easy-to-miss change: the default data directory is no longer the familiar `/var/lib/postgresql/data`, but a new versioned path instead.

If you upgrade by only changing the image tag from `postgres:17` to `postgres:18` while keeping the old volume mount target, the container may fail to start. This article shows a safer migration path: take a logical backup, recreate the container with the new layout, then restore the data.

> This article uses `podman` in the examples. If you use Docker, simply replace `podman` with `docker`.

## The Short Version

The default data directory used by the official PostgreSQL image now looks like this:

| Version        | Default PGDATA Path             |
| -------------- | ------------------------------- |
| 17 and earlier | `/var/lib/postgresql/data`      |
| 18             | `/var/lib/postgresql/18/docker` |
| Future 19      | `/var/lib/postgresql/19/docker` |

That means your volume mount target needs to change too.

This mainly refers to the **container-side mount path** on the right side of the colon. But during migration, it is also a good idea to change the **named volume name** on the left side, for example from `pgdata` to `pgdata18`, so the old and new containers do not share the same volume.

Old style:

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

For PostgreSQL 18, a safer approach is to change both the mount target and the volume name:

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

This lets the image manage its own versioned subdirectory such as `18/docker`, which is also more consistent for future upgrades like 19. At the same time, the old volume remains intact, making rollback and cleanup easier.

There is one more important point: **you should not directly reuse a PostgreSQL 17 data directory with PostgreSQL 18 anyway**. This migration is not just about changing the mount path. You should also use a proper upgrade method such as logical backup and restore, or `pg_upgrade`. This article uses `pg_dumpall` because it is simple and broadly applicable.

## Why the Container Fails to Start

Many existing setups look like this:

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

If you upgrade by only changing the last line to `postgres:18`:

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

the volume is still mounted at the old path, while PostgreSQL 18 expects to use `/var/lib/postgresql/18/docker` by default. As a result, the new version may not persist data where it expects to, or initialization may conflict with the old mount layout, causing startup failure.

So the safer migration flow is:

1. Take a logical backup from the old container.
2. Stop the old container, but keep the old volume.
3. Start PostgreSQL 18 with the new mount path and a new volume name.
4. Restore the backup.

## 1. Back Up the Old Data

The simplest method is to run `pg_dumpall` inside the old container:

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

What the options mean:

- `-t`: allocate a pseudo-TTY. Using it here for backup is fine.
- `-c`: include `DROP` statements in the dump so existing objects are removed before restore.
- `-U postgres`: run the dump as the `postgres` superuser.

If you only want to export a single database, you can use:

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

## 2. Stop the Old Container

```bash
podman stop pg17
```

Do not delete the old volume yet. Keep it for rollback.

## 3. Start a New PostgreSQL 18 Container

## Using `podman run`

There are two key changes:

1. **Change the volume mount target from `/var/lib/postgresql/data` to `/var/lib/postgresql`.**
2. **Use a new named volume, such as `pgdata18`.**

```bash
podman run -d --name pg18 \
  -e POSTGRES_PASSWORD=your_password \
  -e POSTGRES_DB=your_database \
  -p 5432:5432 \
  -v pgdata18:/var/lib/postgresql \
  postgres:18
```

This example deliberately does not reuse the old `pgdata` volume. If PostgreSQL 17 and 18 share the same named volume, old 17 data and new 18 data may end up in the same place. It may not fail immediately, but it makes rollback, troubleshooting, and cleanup much more confusing.

For comparison:

```bash
# Common old setup
-v pgdata:/var/lib/postgresql/data

# Recommended for PostgreSQL 18
-v pgdata18:/var/lib/postgresql
```

## Using Compose

If you use `compose.yml`, you usually only need to change two things:

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

volumes:
  pgdata18:
```

If your old config used `/var/lib/postgresql/data`, make sure to update it here as well. And if your old named volume was called `pgdata`, it is a good idea to switch to a new name such as `pgdata18` during migration rather than reusing the old one directly.

## 4. Restore the Data

## Method 1: Restore Through a Pipe

For small or medium-sized databases, this is the simplest option:

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

Note the important detail here: use `-i`, not `-t`.

- `-i`: keeps standard input open so `psql` can read the SQL from the pipe.
- Do not use `-t`: allocating a TTY during restore can introduce unnecessary problems.

## Method 2: Copy the File First, Then Restore

For larger backups, copying the file into the container first is usually more reliable:

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

## 5. Verify the Migration

After the restore, at minimum, check that the databases are present:

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

Once the database list looks correct, verify the application side as well: confirm connections work and that tables and data are intact.

## 6. Clean Up the Old Container and Backup

Only after the new container is working properly should you clean up the old resources:

```bash
# Remove the old container
podman rm pg17

# After everything is confirmed, manually remove the old volume
# podman volume rm pgdata

# Remove the backup file
rm backup.sql
```

Delete the old volume last. As long as it still exists, rollback is much easier. Here, `pgdata` refers to the volume originally used by the old container, while the new example container uses `pgdata18`.

## Common Notes

### I. Do Not Mount a PostgreSQL 17 Data Directory Directly into 18

Even aside from the path change, a PostgreSQL major version upgrade should not directly reuse the old data directory. The safer approaches are logical backup and restore or `pg_upgrade`.

### II. Both Named Volumes and Bind Mounts Are Affected

This is not specific to named volumes, and it is not specific to bind mounts either. If your old setup hard-coded `/var/lib/postgresql/data` as the target path, you should review it before moving to 18.

Named volumes do have one extra risk, though: **do not let PostgreSQL 17 and 18 share the same volume name**. During migration, clearly separate the old and new volumes, for example `pgdata` and `pgdata18`.

### III. For Fresh PostgreSQL 18 Deployments, Use the New Mount Style Directly

If you are deploying PostgreSQL 18 for the first time, do not keep using old tutorials that mount `/var/lib/postgresql/data`. Mount `/var/lib/postgresql` directly instead.


---

> Author: Nite  
> URL: https://www.nite07.com/en/posts/postgres-docker-18-migration/  

