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
podmanin the examples. If you use Docker, simply replacepodmanwithdocker.
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:
-v pgdata:/var/lib/postgresql/dataFor PostgreSQL 18, a safer approach is to change both the mount target and the volume name:
-v pgdata18:/var/lib/postgresqlThis 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:
podman run -d --name pg17 \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:17If you upgrade by only changing the last line to postgres:18:
podman run -d --name pg18 \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:18the 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:
- Take a logical backup from the old container.
- Stop the old container, but keep the old volume.
- Start PostgreSQL 18 with the new mount path and a new volume name.
- Restore the backup.
1. Back Up the Old Data
The simplest method is to run pg_dumpall inside the old container:
podman exec -t pg17 pg_dumpall -c -U postgres > backup.sqlWhat the options mean:
-t: allocate a pseudo-TTY. Using it here for backup is fine.-c: includeDROPstatements in the dump so existing objects are removed before restore.-U postgres: run the dump as thepostgressuperuser.
If you only want to export a single database, you can use:
podman exec -t pg17 pg_dump -U postgres dbname > dbname.sql2. Stop the Old Container
podman stop pg17Do 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:
- Change the volume mount target from
/var/lib/postgresql/datato/var/lib/postgresql. - Use a new named volume, such as
pgdata18.
podman run -d --name pg18 \
-e POSTGRES_PASSWORD=your_password \
-e POSTGRES_DB=your_database \
-p 5432:5432 \
-v pgdata18:/var/lib/postgresql \
postgres:18This 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:
# Common old setup
-v pgdata:/var/lib/postgresql/data
# Recommended for PostgreSQL 18
-v pgdata18:/var/lib/postgresqlUsing Compose
If you use compose.yml, you usually only need to change two things:
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:
cat backup.sql | podman exec -i pg18 psql -U postgresNote the important detail here: use -i, not -t.
-i: keeps standard input open sopsqlcan 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:
podman cp backup.sql pg18:/tmp/backup.sql
podman exec -i pg18 psql -U postgres -f /tmp/backup.sql5. Verify the Migration
After the restore, at minimum, check that the databases are present:
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:
# 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.sqlDelete 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.