PostgreSQL 14 gets its last update on 12 November 2026. The PostgreSQL versioning policy lists that date as the final release for version 14, and the rule behind it is short: each major version is supported for five years, then it gets one final minor release and becomes unsupported. Any security bug found in 14 after that day stays unfixed.
The calendar around it is tight. The PostgreSQL 19 RC 1 announcement of 15 October says the 12 November update release "will include a large number of fixes", and it puts general availability of PostgreSQL 19 on 19 November 2026, one week after 14 drops out of support. If you are still on 14, you have to choose a target now, before 19 has any track record.
Managed databases get a little more room. The Amazon RDS for PostgreSQL release calendar keeps 14 in standard support until 28 February 2027, with paid Extended Support starting on 1 March 2027. A self-hosted container gets no grace period at all. The postgres:14 image you pulled in 2022 keeps running exactly the code it shipped with.
Why so many stacks are still on 14
Postgres is the line in a compose file people are most nervous about touching, and with reason. Changing the image tag across a major version does not upgrade the data, because the on-disk format changes between majors. The versioning page says it plainly: a dump/reload or pg_upgrade is required. So a stack set up in 2021 or 2022 tends to get its app images rebuilt every month while the database tag sits still.
The official Docker image moves on regardless. The versions.json file in the docker-library/postgres repository, which lists the versions the image maintainers build, currently covers 14 through 19. PostgreSQL 13, which reached end of life in November 2025, is no longer in it.
Step 1: find every Postgres 14 you run
Image names are not reliable. A container may run PostGIS, TimescaleDB, a vendor image or something built in-house, all with Postgres inside. Ask the binary instead:
for c in $(docker ps --format '{{.Names}}'); do
v=$(docker exec "$c" postgres --version 2>/dev/null) && echo "$c: $v"
done
That only sees running containers, so also search compose files on disk for stacks that are stopped or live on other hosts:
grep -rnE 'image: *"?postgres' --include='*compose*.y*ml' /srv /opt 2>/dev/null
For every 14 instance, write down three facts before planning anything. Each one decides a step later on (examples below use a container called pg14):
# data checksums: "version: 0" means off
docker exec -u postgres pg14 pg_controldata | grep -i checksum
# bootstrap superuser: empty output means "postgres"
docker exec pg14 printenv POSTGRES_USER
# where the data volume is mounted
docker inspect -f '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}' pg14
Step 2: pick the target version
Our recommendation is PostgreSQL 18, not 19. Version 18 is the current stable line (18.6 on the versioning page today) and is supported until 14 November 2030. 19.0 will be a week old when 14 expires, and a migration forced by an end-of-life date is a poor moment to also be an early adopter. Move to 18 now and plan 19 for next year, once a few minor releases are out.
You do not need to step through 15, 16 and 17. The pg_upgrade documentation says it supports upgrades "from 9.2.X and later", and the versioning page confirms you can skip versions, while recommending that you read the release notes of every version in between. For 14 to 18 that means the notes for 15, 16, 17 and 18, starting with the incompatibilities list at the top of each.
Step 3: take a backup you have restored once
docker exec pg14 pg_dumpall -U app | gzip > pg14-$(date +%F).sql.gz
Restore it into a scratch container and run a few queries against it. Until you have done that, it is a file, not a backup.
Option A: dump and restore
For databases up to a few gigabytes this is the simplest path, and it clears out table bloat as a side effect. Start a postgres:18 container next to the old one with its own empty volume, then stream the data across:
docker exec pg14 pg_dumpall -U app | docker exec -i pg18 psql -U app -q
When we ran this, psql printed two errors:
ERROR: role "app" already exists
ERROR: database "app" already exists
Both are harmless. The new container created that role and database on first start, and the rest of the dump loads into them. The cost of this method is downtime: writes have to stop for the whole dump and restore, so it grows with the size of the data.
Option B: pg_upgrade with hard links
For larger databases, pg_upgrade --link recreates the schema in the new cluster and hard-links the data files instead of copying them. The pg_upgrade documentation says link mode is "much faster (no file copying)" and uses less disk space. It needs the binaries of both versions in one place, which the official image does not provide. The tianon/docker-postgres-upgrade project publishes images tagged OLD-to-NEW, including 14-to-18. Its README calls it a proof of concept to "learn from" and adapt, not a finished tool, and that matches what we saw.
We upgraded a throwaway 14.24 cluster created with POSTGRES_USER=app, which is how most compose files set it up, to 18.6. It failed three times before it worked, and none of the three is specific to our setup.
Error 1: data checksums
old cluster does not use data checksums but the new one does
Failure, exiting
The PostgreSQL 18 release notes list this under incompatibilities: initdb now enables data checksums by default, and "pg_upgrade requires matching cluster checksum settings". A cluster created by the postgres:14 image has checksums off unless you asked for them. The fix is to create the new cluster with --no-data-checksums. If you want checksums, turn them on afterwards with pg_checksums on the stopped cluster. Its documentation warns that on a large cluster this "can potentially take a long time", and nothing else may write to the data directory while it runs.
Error 2: the superuser name
FATAL: role "postgres" does not exist
could not connect to source postmaster started with the command: ...
The helper image runs initdb as the default postgres superuser and connects to the old cluster with that name. If your compose file set POSTGRES_USER=app, the old cluster has no role called postgres. Pass the real name to both initdb and pg_upgrade. With both fixes in place, run the check mode first. It changes no data:
# old cluster stopped, its files moved to /srv/pg/14/docker
docker run --rm \
--mount type=bind,src=/srv/pg,dst=/var/lib/postgresql \
-e PGDATAOLD=/var/lib/postgresql/14/docker \
-e PGDATANEW=/var/lib/postgresql/18/docker \
-e POSTGRES_INITDB_ARGS='--no-data-checksums --username=app' \
tianon/postgres-upgrade:14-to-18 --check --username=app
When it prints *Clusters are compatible*, run the same command with --link --username=app in place of --check --username=app.
Error 3: the app can no longer connect
The upgrade finished and postgres:18 started on the new data. Then a client in another container got this:
FATAL: no pg_hba.conf entry for host "172.26.0.3", user "app", database "app", no encryption
The official image adds a line to pg_hba.conf that allows password logins from other hosts, but only when it initializes an empty data directory. Here initdb ran inside the helper image, so the new pg_hba.conf only trusts local connections. The pg_upgrade documentation warns about this in its "Restore pg_hba.conf" step and adds that postgresql.conf and postgresql.auto.conf may need the same treatment. Copy your old files over if you changed them. If you only had the image defaults, add the line back:
docker exec pg18 sh -c 'echo "host all all all scram-sha-256" >> "$PGDATA/pg_hba.conf"'
docker exec pg18 psql -U app -c 'SELECT pg_reload_conf()'
The volume path changed in the 18 image
Starting with 18, the official image keeps data in a version-specific directory, /var/lib/postgresql/18/docker, and declares its volume at /var/lib/postgresql. The image documentation says mounts should target the new location, and pull request #1259 explains why: with one mount for both versions, pg_upgrade --link works without crossing filesystems. Update the service definition to match:
db:
image: postgres:18
volumes:
- /srv/pg:/var/lib/postgresql # not /var/lib/postgresql/data
If you just change the tag and leave the old mount, the 18 entrypoint script refuses to start and prints:
Error: in 18+, these Docker images are configured to store database data in a
format which is compatible with "pg_ctlcluster" ...
That is a safe failure. It is still better to see it in a test run than at 2 a.m.
After the upgrade
PostgreSQL 18's pg_upgrade keeps optimizer statistics, according to its release notes, but our run still ended with "Some statistics are not transferred by pg_upgrade" and suggested two vacuumdb commands. Run them, then look for passwords stored as MD5:
docker exec pg18 vacuumdb -U app --all --analyze-in-stages --missing-stats-only
docker exec pg18 vacuumdb -U app --all --analyze-only
docker exec pg18 psql -U app -Atc "SELECT rolname FROM pg_authid WHERE rolpassword LIKE 'md5%'"
The PostgreSQL 14 release notes switched the default password hashing to SCRAM-SHA-256, but roles carried over from 13 or older can still hold MD5 hashes. PostgreSQL 18 deprecates MD5 authentication and says support will be removed in a future major release, so reset those passwords while you are already in a maintenance window.
Two more things. In link mode the old cluster is unsafe to start once the new one has run, as the pg_upgrade docs warn, so your rollback is the backup from step 3, not the old directory. And keep that old directory until the application has run a full business cycle on 18, then delete it with the delete_old_cluster.sh script pg_upgrade leaves behind.
The same habit that keeps Postgres current applies to every base image you run. We covered it for PHP in PHP2Shell: patch the PHP underneath WordPress, and the planning side of a hard end-of-support date in our Exchange 2016/2019 exit plan. If you are cleaning up a compose stack anyway, sending container logs to stdout is another small fix worth doing in the same pass.
Frequently asked questions
Will my PostgreSQL 14 database stop working on 12 November 2026?
No. It keeps running. End of life means the project stops releasing fixes, so security and data corruption bugs found after the final 14 release will not be patched.
Can I upgrade from 14 straight to 18?
Yes. pg_upgrade supports any source from 9.2 onward, and dump/restore works across any versions. Read the release notes of 15, 16, 17 and 18 for incompatibilities first.
Should I wait for PostgreSQL 19?
We would not. 19 is planned for 19 November 2026, after 14 loses support. Upgrading to 18 now and to 19 later is two planned jobs instead of one rushed one.
Is it enough to change the image tag from 14 to 18?
No. The data directory has to be upgraded with pg_upgrade or dump/restore. With an old-style mount, the 18 image refuses to start and points you to pg_upgrade.
Sources: PostgreSQL versioning policy, PostgreSQL 19 RC 1 announcement, PostgreSQL 18 release notes, PostgreSQL 14 release notes, pg_upgrade documentation, Amazon RDS for PostgreSQL release calendar, postgres image documentation and tianon/docker-postgres-upgrade. Commands tested on Docker with postgres:14 (14.24) and postgres:18 (18.6) on 1 November 2026.



