Database Files Are Incompatible With Server
Chat2DB TeamAfter a package upgrade or a new Docker image, PostgreSQL refuses to start:
FATAL: database files are incompatible with server
DETAIL: The data directory was initialized by PostgreSQL version 14, which is not compatible with this version 18.6.Your data is fine. The message means that the server binary you started belongs to a different major version than the one that created the data directory. PostgreSQL's on-disk format changes between major versions (14, 15, 16, 17, 18...), so a server only opens data directories of its own major version. Minor updates such as 18.5 to 18.6 never cause this error.
With PostgreSQL 14 reaching end of life on November 12, 2026, many teams are upgrading right now, and this message is what you see when the binaries move ahead of the data. This article explains how to read the message, how to get the database running again immediately, and how to do the actual upgrade with pg_upgrade or dump and restore, including the Docker case. All commands were run with PostgreSQL 14.24 and 18.6 built from source.
Reading the Message
The DETAIL names both versions: the one in the data directory (from its PG_VERSION file) and the one of the running binary. We reproduced both directions:
initdb -D /tmp/inc14 # with the PostgreSQL 14 binaries
/usr/local/pgsql-18/bin/postgres -D /tmp/inc14FATAL: database files are incompatible with server
DETAIL: The data directory was initialized by PostgreSQL version 14, which is not compatible with this version 18.6.And an older binary on a newer directory:
FATAL: database files are incompatible with server
DETAIL: The data directory was initialized by PostgreSQL version 18, which is not compatible with this version 14.24.The second case happens when a package downgrade, a wrong PATH or an older Docker tag picks up data that has already been upgraded. Never try to force it: the old server cannot read the new format.
You can check the data directory's version without starting anything:
cat /tmp/inc14/PG_VERSION14Two related variants exist. If the major version is the same but the internal catalog version differs, which happens between beta or release candidate builds and the final release, the detail reads "The database cluster was initialized with CATALOG_VERSION_NO ..., but the server was compiled with CATALOG_VERSION_NO ...". The PG_CONTROL_VERSION variant appears if the control file itself has a different layout. The fix is the same as for a major version change.
pg_ctl reports the problem less clearly. When we started the 14 directory with the 18 pg_ctl, it printed:
pg_ctl: control file appears to be corruptThe server log written by pg_ctl -l contains the real FATAL line. If you see "control file appears to be corrupt" right after an upgrade, check versions before you suspect corruption.
Step 1: Get the Old Version Running Again
The quickest way back to a working database is to start it with the binaries of the version that created it. Nothing has been changed on disk by the failed start.
- Homebrew (macOS): formulas are versioned.
brew services stop postgresql@18, thenbrew install postgresql@14if needed andbrew services start postgresql@14. Make sure your shellPATHpoints at the 14 binaries forpsqlandpg_dumpagainst this server. - Debian and Ubuntu (PGDG packages): each major version is its own package (
postgresql-14,postgresql-18) and its own cluster.pg_lsclusterslists clusters with their versions. The data of the 14 cluster lives in/var/lib/postgresql/14/mainand is started withpg_ctlcluster 14 main start. - RHEL, Rocky, Alma (PGDG packages): binaries live in
/usr/pgsql-14/binand/usr/pgsql-18/bin. Start the old service unit,postgresql-14. - Docker: go back to the old image tag (for example
postgres:14) with the same volume. See the Docker section below.
Once it is running, take a backup before you try anything else: pg_dumpall for a logical copy, or a file system copy of the data directory while the server is stopped.
Step 2: Choose an Upgrade Method
There are two standard ways to move the data to the new major version.
| Method | Downtime | Needs | Best for |
|---|---|---|---|
pg_upgrade | Minutes, mostly independent of data size with --link | Old and new binaries on the same host | Most production upgrades |
pg_dumpall / pg_dump + restore | Proportional to data size | Only the new server, plus a dump from the old one | Small databases, moving hosts, changing settings like encoding or locale |
Logical replication from the old to the new version is a third option for near-zero downtime, at the cost of more setup.
Upgrading with pg_upgrade
pg_upgrade needs both sets of binaries and an empty data directory initialized by the new version. One detail matters for upgrades to 18: initdb in PostgreSQL 18 enables data checksums by default, and pg_upgrade requires both clusters to match. A 14 cluster usually has checksums off, so initialize the new one without them (or enable checksums on the old cluster first with pg_checksums):
/usr/local/pgsql-18/bin/initdb -D /tmp/up18 -U postgres --no-data-checksums
cd /tmp/upgrade-work
/usr/local/pgsql-18/bin/pg_upgrade \
-b /usr/local/pgsql-14/bin -B /usr/local/pgsql-18/bin \
-d /tmp/inc14 -D /tmp/up18 -U postgres --checkChecking for presence of required libraries ok
Checking database user is the install user ok
Checking for prepared transactions ok
Checking for new cluster tablespace directories ok
*Clusters are compatible*--check changes nothing. Fix whatever it reports, such as missing extensions in the new installation, then run the same command without --check, with both servers stopped:
Upgrade Complete
----------------
Some statistics are not transferred by pg_upgrade.
Once you start the new server, consider running these two commands:
/usr/local/pgsql-18/bin/vacuumdb -U postgres --all --analyze-in-stages --missing-stats-only
/usr/local/pgsql-18/bin/vacuumdb -U postgres --all --analyze-onlypg_upgrade writes its logs to the current directory, which is why we ran it from a scratch directory. Copy postgresql.conf and pg_hba.conf settings to the new cluster before starting it, because the new data directory has fresh default files. Add --link (hard links) or, on PostgreSQL 18, --swap to avoid copying the data files. With those modes, the old cluster must not be started again after the new one has run.
The PostgreSQL 14 end-of-life upgrade guide walks through a full 14 to 18 upgrade, including checksums, extension versions and post-upgrade checks, and the pg_upgrade guide covers the options in more depth.
Upgrading with Dump and Restore
With the old server running and the new one running on another port, a pipe copies everything, including roles:
pg_dumpall -h old-host -p 5432 -U postgres | psql -h new-host -p 5433 -U postgres -d postgresUse the pg_dumpall and psql from the new version. Newer pg_dump can read older servers, but older pg_dump cannot produce dumps for features it does not know, and it refuses to dump a newer server. Expect one harmless error during the restore: role "postgres" already exists. For large databases, dump each database with pg_dump -Fd -j 4 and restore with pg_restore -j 4 to use several cores.
Docker: Why the Container Stops After Changing the Tag
Docker users meet this error most often, because changing image: postgres:14 to postgres:latest or postgres:18 swaps the binaries while the volume keeps the old data. The official image does not run pg_upgrade for you.
With images up to 17, the volume is mounted at /var/lib/postgresql/data, and starting a newer image on an older volume fails with the "database files are incompatible with server" message.
PostgreSQL 18 images changed the layout. Per the image documentation, PGDATA is now version-specific, /var/lib/postgresql/18/docker, and the declared volume moved to /var/lib/postgresql. The 18 image's entrypoint looks for old data in the previous location and stops with its own error instead of silently creating an empty database. From the entrypoint script in the docker-library repository:
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
This is usually the result of upgrading the Docker image without
upgrading the underlying database using "pg_upgrade" (which requires both
versions).So depending on the tag and how the volume is mounted, a Docker upgrade gives you either the PostgreSQL message or this one. The cause and the fix are the same.
A safe Docker upgrade with dump and restore:
- Pin the old tag again (
postgres:14) and start the old container with its volume. - Dump everything:
docker exec old-pg pg_dumpall -U postgres > all.sql. - Start a new
postgres:18container with a new, empty volume mounted at/var/lib/postgresql. - Restore:
docker exec -i new-pg psql -U postgres -d postgres < all.sql. - Point the application at the new container, and keep the old volume until you have verified the data.
For pg_upgrade inside Docker you need a container that has both versions' binaries and both data directories mounted. The single mount at /var/lib/postgresql in 18+ images is designed for that, because the old and new data directories can then sit on the same file system, which pg_upgrade --link requires. Whatever you do, never point a newer image at the old volume and delete "the broken files" to make the container start. An empty database would start, and the old data would be gone.
Preventing It Next Time
- Pin major versions. Use
postgres:18rather thanpostgres:latestin Compose files, and versioned packages such aspostgresql@18in Homebrew. Upgrade on purpose, not as a side effect ofdocker pullorbrew upgrade. - Check before restarting after package updates.
pg_lsclusterson Debian and Ubuntu, orcat $PGDATA/PG_VERSIONanywhere, shows the data version.postgres --versionshows the binary version. - Test the upgrade on a copy. Restore last night's backup on a spare machine, run
pg_upgrade --checkor a full dump and restore, and time it.
After an upgrade, a quick look at the new server confirms that schemas, row counts and extensions came across. Chat2DB (opens in a new tab) can hold connections to the old and new servers side by side, so you can compare tables and run the same checks against both before you switch the application over.
Summary
- "Database files are incompatible with server" means the binary's major version differs from the data directory's
PG_VERSION. The data is intact. - Start the data with its original version's binaries first, and take a backup.
- Upgrade with
pg_upgrade(both binaries, matching checksum settings,--checkfirst) or withpg_dumpalland restore using the new version's tools. - Docker images do not upgrade data. On 18+, the image uses
/var/lib/postgresql/18/dockerand reports old data in/var/lib/postgresql/datawith its own error. - Pin major versions so the next upgrade is planned.
