Docket

TROUBLESHOOTING

Postgres will not start: database files are incompatible with server

The image tag rolled to a new major version, and Postgres cannot open a data directory the previous one wrote.

7 MIN READ Last updated 23 August 2026

Your Postgres container is restarting in a loop and the log ends with FATAL: database files are incompatible with server. The image tag has moved to a new major version, and Postgres will not open a data directory that an older major version wrote. Nothing is lost. Pin the tag back, bring the board up, then migrate the data deliberately.

Every command and error string on this page was checked on 23 August 2026 against PostgreSQL's own source tree, its manual, and the official image's documentation.

Why does the new image refuse to open the old data directory?

Postgres ties its on-disk layout to a major version. Each major release is free to change the shape of the system catalogues, so a version 16 server handed a directory that version 15 initialised does not attempt a guess, it stops. The message comes from the database, not from Docker. It is emitted in src/backend/utils/init/miscinit.c as database files are incompatible with server, with the detail line "The data directory was initialized by PostgreSQL version 15, which is not compatible with this version 16."

Docker is only how you arrived. docker compose pull follows a tag to whatever the registry serves today, and a compose file that says image: postgres with no tag at all follows latest straight across a major boundary. The volume outlives the container, which is the entire point of a volume, so the new binary starts, finds the old directory, and refuses it.

The official image does not upgrade that directory for you. This is a known gap rather than an oversight: the request for it has been open on the docker-library/postgres repository since November 2014, carries 138 comments and 276 thumbs-up, and was last touched on 6 July 2026, which makes it the most-upvoted unresolved issue on the image. The crash-on-restart symptom also has its own Stack Overflow question, at 25,964 views.

How do you get the board back up right now?

Backwards is the fast way. Do not delete the volume, and in particular do not reach for docker compose down -v, because -v removes the one thing holding your data.

1. Stop the stack, leaving the volume alone.

docker compose down

2. Find out which major version wrote the directory. Every Postgres data directory carries a one-line PG_VERSION file naming it. Read it straight off the volume with a throwaway container:

docker volume ls
docker run --rm -v board_pgdata:/d alpine cat /d/PG_VERSION

3. Pin the image to that major version. An exact tag, not latest, and not a bare postgres:

services:
  db:
    image: postgres:15

4. Bring it back and read the log rather than assuming.

docker compose up -d db
docker compose logs --tail=20 db

You are looking for database system is ready to accept connections. At that point the outage is over and the upgrade has become a planned job instead of an incident.

How do you move the data into the new major version?

There are two honest routes, and the slower one is the one most people should take.

Dump and restore. Take the dump using the old server's own binaries, from inside the container that is already running them, so the client and the server can never disagree about the format:

docker compose exec -T db pg_dumpall -U postgres > all.sql

Then point the compose file at the new major version, give it a new and empty volume, and load the file in:

docker compose up -d db
docker compose exec -T db psql -U postgres < all.sql

Keep the old volume until you have finished checking the new one. It costs disk space and nothing else.

pg_upgrade. The PostgreSQL manual describes it as a way to upgrade the data files "to a later PostgreSQL major version without the data dump/restore typically required for major version upgrades". It is dramatically faster on a large database. The awkward part under Docker is that it wants the old and the new binaries in one place, which no single official image provides. The community answer is tianon/postgres-upgrade, tagged in the form 17-to-18, and its own README calls it a proof of concept and tells you not to expect it to work as-is. Read that sentence before pointing it at a live database.

One change in PostgreSQL 18 will catch you on the way through. The official image made PGDATA version specific from 18 onwards, at /var/lib/postgresql/18/docker, and moved the declared volume up to /var/lib/postgresql. For 17 and below the volume still belongs at /var/lib/postgresql/data, and the image's documentation warns that mounting at /var/lib/postgresql on those versions "WILL NOT PERSIST database data when the container is re-created". Get the mount path wrong on the way to 18 and the container starts perfectly, with nothing in it.

How do you tell it actually worked?

Check the version the server reports, rather than the tag you think you set:

docker compose exec db psql -U postgres -c "select version();"

Then check the data is present, not just the database:

docker compose exec db psql -U postgres -d board -c "\dt"

Then open the board itself and look at a post, a vote count and a comment thread. A restore that loads without error and drops a table's contents is rare, but the only way to know is to look at the thing your customers look at. If you have a restore drill written down already, this is the moment it pays for itself, and if you have not, backing up a request board covers what one looks like.

Your own support centre, in your own repository

One payment, no subscription, unlimited products.

What if the symptom is slightly different?

Same upgrade, different failure. These are the neighbouring cases worth recognising before you start changing things.

What you seeWhat it usually is
PostgreSQL Database directory appears to contain a database; Skipping initialization, then the incompatibility errorThe same problem. The entrypoint found existing data, correctly declined to initialise, and handed over to a server that cannot read it
The container starts cleanly but the board is empty and asks you to create an admin accountThe data was never on the volume you think it was, either an anonymous volume or the PostgreSQL 18 mount path change above
initdb: error: directory "/var/lib/postgresql/data" exists but is not emptyA fresh initialisation aimed at a populated directory
FATAL: data directory "/var/lib/postgresql/data" has wrong ownershipThe files were copied or restored as the wrong user. The server must be started by the user that owns the directory
FATAL: data directory "/var/lib/postgresql/data" has invalid permissionsThe mode is wrong. Postgres wants 0700, or 0750 if you have set up group read access
pg_restore: error: unsupported version (1.14) in file header during the restoreA client and server mismatch on the restore side rather than a data directory problem

Does a support centre have to run Postgres at all?

Not necessarily, and it is worth saying plainly rather than leaving it implied. Docket has no Postgres in it. Its runtime is static files plus two small endpoints, so there is no database container, no volume to pin and no major version to cross. That is a design choice with real costs elsewhere, and it does not make this page less true for anyone running Fider, LogChimp or any other Docker board, all of which are solving a harder problem than Docket is. If you are weighing that trade rather than fighting a crash right now, what breaks when you self-host is the wider version of the argument.

Frequently asked questions

Can I just delete the volume and start again?

Only if you genuinely have a dump you have restored at least once. Deleting the volume deletes every post, vote, comment and account on the board, and there is no undo. Roll the image tag back first, get the old server running, and take the dump from it. The volume can go afterwards, once the new one is verified.

Does this happen on a minor version upgrade too?

No. Minor upgrades, for example 16.4 to 16.5, keep the same on-disk format and the same data directory, and the PostgreSQL manual is explicit that pg_upgrade is not required for them. Only a major version boundary, such as 15 to 16, changes the format and triggers this error.

Why does the official image not upgrade the directory automatically?

Because an automatic in-place major upgrade would rewrite a live data directory with no supervision and no easy way back. The request has been open on the image's repository since 2014, with 138 comments, and the maintainers have not shipped one. In practice you are expected to run pg_upgrade yourself, or dump and restore.

How do I stop this from happening again?

Pin an exact major version in your compose file, so postgres:16 rather than postgres or postgres:latest, and treat a major version bump as a scheduled change with a dump taken first. docker compose pull on a floating tag is the mechanism that turns a routine update into an outage.

Is the data actually still there while the container is crash-looping?

Yes. The server refuses to start precisely so that it does not touch files it cannot safely read, which is protective behaviour rather than damage. As long as the volume still exists, starting the old major version again gives you the database back exactly as it was.