Error: in 18+, these Docker images are configured to store database data in a format which
is compatible with "pg_ctlcluster" — and the container stops. The postgres:18
image has moved its data directory, and your compose file is still mounting the old one.
Before 18, the official image kept the cluster at /var/lib/postgresql/data and
everyone mounted a volume there. From 18 the data lives in a major-version subdirectory,
/var/lib/postgresql/18/docker, and the entrypoint refuses to start when it finds a
database sitting on the old mount. Mount the volume one level up, at
/var/lib/postgresql, and the image puts its data inside it.
The error
The season's build log only paraphrased the message; this is the text from the image's own docker-entrypoint.sh, trimmed:
Error: in 18+, these Docker images are configured to store database data in a
format which is compatible with "pg_ctlcluster" (specifically, using
major-version-specific directory names).
Counter to that, there appears to be PostgreSQL data in:
/var/lib/postgresql/data (unused mount/volume)
The suggested container configuration for 18+ is to place a single mount
at /var/lib/postgresql which will then place PostgreSQL data in a
subdirectory, allowing usage of "pg_upgrade --link" without mount point
boundary issues.
Why it happens
The change is deliberate. PostgreSQL's own tooling (pg_ctlcluster,
pg_upgrade --link) expects one cluster directory per major version, so an upgrade can
lay 19 next to 18 inside the same volume and hard-link the files across. A volume mounted at
/var/lib/postgresql/data makes that impossible: the next version's directory would be
outside the mount.
So the 18 image's default PGDATA became /var/lib/postgresql/18/docker,
and the entrypoint gained a guard: if the new data directory is empty and there is a database on
the old mount, stop and explain, rather than silently initialize a fresh, empty cluster beside
your real data. That guard is what you're seeing. It reads as "worked forever, broke today"
because postgres:18 is a moving tag — nothing in the compose file changed; the image
underneath it did.
The fix
The one-line change in docker-compose.yml, with the reason committed beside it:
volumes:
# postgres:18 images keep data in a major-version subdirectory
# (/var/lib/postgresql/18/docker), so the volume mounts one level up.
# Mounting at /var/lib/postgresql/data — the pre-18 habit — makes the
# container refuse to start.
- cliniclive-pgdata:/var/lib/postgresql
Then docker compose down and docker compose up -d. Be clear about what
that means for a development database: the old volume's data is at a path the new layout doesn't
read, so you are starting a fresh cluster — run migrations and re-seed. If you must keep the old
layout instead, the image still honors an explicit PGDATA environment variable
pointing at the old path, at the cost of the in-place upgrade story later.
Where it bit us
Season three, Part 3: the app needs a
door, the first docker compose up of the season. The compose file dated from
season one's Part 5 and had not been
touched since; the fix is in tag pocket-03 of
the repo. The fresh database also had
to re-seed the demo codes for today — demo data has a shelf life, a season-two lesson back for a
second visit. The lesson: a major-version tag is not a pin.
Frequently asked
- Why does the postgres:18 Docker container exit immediately with a volume at /var/lib/postgresql/data?
- Because PostgreSQL 18 images store the cluster in a major-version subdirectory, /var/lib/postgresql/18/docker, and the entrypoint refuses to start when it finds database data on the old /var/lib/postgresql/data mount. Change the volume to mount at /var/lib/postgresql instead.
- Where should the Docker volume be mounted for PostgreSQL 18?
- At /var/lib/postgresql, one level above the old path. The image then creates its data directory inside the volume, and future major-version upgrades with pg_upgrade --link can place the next version alongside it in the same mount.
- Will I lose my data when I change the mount path for postgres:18?
- The old data stays in the volume but at a path the new layout does not read, so the container starts a fresh cluster. For a development database, run your migrations and seed again. For real data, upgrade the cluster with pg_upgrade rather than just moving the mount.
More decoded errors in the Fixes category; the series that wrote this compose file starts at Part 1.