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.