PostgreSQL: inplace upgrade in Docker container

PostgreSQL: inplace upgrade in Docker container

As I wrote in a older blog entry, we cannot just change the PostgreSQL image of a Docker container to a higher major version. Restarting the container like that will lead to a not running PostgreSQL container, as older data will not start using newer binaries.

But there's a great project that will help to do an inplace upgrade.

This is a PostgreSQL container running PostgreSQL 9.5:

  psql_mig:
    image: postgres:9.5
    hostname: psql_mig
    restart: always
    environment:
      POSTGRES_PASSWORD: XxXxXxXx
    ports:
      - 5442:5432

We will change the image to pgautoupgrade/pgautoupgrade:

  psql_mig:
    image: pgautoupgrade/pgautoupgrade
    hostname: psql_mig
    restart: always
    environment:
      POSTGRES_PASSWORD: XxXxXxXx
    ports:
      - 5442:5432

After that, we pull the latest image and restart the container using docker compose:

# docker compose pull && docker compose up -d

To see what happens, we execute

# docker compose logs -f
psql_mig-1  | PostgreSQL Database directory appears to contain a database; Skipping initialization
psql_mig-1  |
psql_mig-1  | ************************************
psql_mig-1  | PostgreSQL data directory: /var/lib/postgresql/data
psql_mig-1  | ************************************
psql_mig-1  | Creating upgrade lock file at /var/lib/postgresql/data/upgrade_in_progress.lock
psql_mig-1  | *******************************************************************************************
psql_mig-1  | Performing PG upgrade on version 9.5 database files.  Upgrading to version 17
psql_mig-1  | *******************************************************************************************
psql_mig-1  | ----------------------------------------------------------------------
psql_mig-1  | Checking for left over artifacts from a failed previous autoupgrade...
psql_mig-1  | ----------------------------------------------------------------------
psql_mig-1  | -------------------------------------------------------------------------------
psql_mig-1  | No artifacts found from a failed previous autoupgrade.  Continuing the process.
psql_mig-1  | -------------------------------------------------------------------------------
psql_mig-1  | ---------------------------------------
psql_mig-1  | Creating OLD temporary directory /var/lib/postgresql/data/old
psql_mig-1  | ---------------------------------------
psql_mig-1  | --------------------------------------------
psql_mig-1  | Creating OLD temporary directory is complete
psql_mig-1  | --------------------------------------------
psql_mig-1  | -------------------------------------------------------
psql_mig-1  | Moving existing data files into OLD temporary directory
psql_mig-1  | -------------------------------------------------------
psql_mig-1  | '/var/lib/postgresql/data/PG_VERSION' -> '/var/lib/postgresql/data/old/PG_VERSION'

[...]

psql_mig-1  | INFO:  "updates": scanned 1 of 1 pages, containing 83 live rows and 0 dead rows; 83 rows in sample, 83 estimated total rows
psql_mig-1  | VACUUM
psql_mig-1  | -------------------------------------
psql_mig-1  | Finished updating query planner stats
psql_mig-1  | -------------------------------------
psql_mig-1  | ------------------------
psql_mig-1  | Reindexing the databases
psql_mig-1  | ------------------------
psql_mig-1  | reindexdb: reindexing database "postgres"
psql_mig-1  | 2025-01-09 13:29:29.225 UTC [223] FATAL:  role "root" does not exist
psql_mig-1  | 2025-01-09 13:29:59.303 UTC [232] FATAL:  role "root" does not exist
psql_mig-1  | 2025-01-09 13:30:22.856 UTC [193] LOG:  checkpoint starting: wal
psql_mig-1  | 2025-01-09 13:30:29.365 UTC [244] FATAL:  role "root" does not exist
psql_mig-1  | 2025-01-09 13:30:59.424 UTC [254] FATAL:  role "root" does not exist
psql_mig-1  | 2025-01-09 13:31:06.765 UTC [193] LOG:  checkpoint complete: wrote 512 buffers (3.1%); 0 WAL file(s) added, 1 removed, 32 recycled; write=37.035 s, sync=6.680 s, total=44.273 s; sync files=174, longest=6.676 s, average=0.039 s; distance=544950 kB, estimate=544950 kB; lsn=0/C54439D8, redo lsn=0/A642D920
psql_mig-1  | 2025-01-09 13:31:07.295 UTC [193] LOG:  checkpoint starting: wal
psql_mig-1  | 2025-01-09 13:31:08.021 UTC [193] LOG:  checkpoint complete: wrote 4 buffers (0.0%); 0 WAL file(s) added, 0 removed, 33 recycled; write=0.309 s, sync=0.011 s, total=0.727 s; sync files=4, longest=0.010 s, average=0.003 s; distance=540616 kB, estimate=544516 kB; lsn=0/C8464638, redo lsn=0/C741FCD8
psql_mig-1  | 2025-01-09 13:31:29.469 UTC [264] FATAL:  role "root" does not exist
psql_mig-1  | reindexdb: reindexing database "template1"
psql_mig-1  | -------------------------------
psql_mig-1  | End of reindexing the databases
psql_mig-1  | -------------------------------
psql_mig-1  | *************************************************************************************************
psql_mig-1  | Post upgrade tasks have finished successfully.  PostgreSQL should now be fully updated and online
psql_mig-1  | *************************************************************************************************

The PostgreSQL container is now upgraded to the latest version provided by pgautoupgrade/pgautoupgrade.

We can now revert the container image back to vanilla PostgreSQL:

psql_mig:
    image: postgres:17
    hostname: psql_mig
    restart: always
    environment:
      POSTGRES_PASSWORD: XxXxXxXx
    ports:
      - 5442:5432

Please keep in mind: You should always have a backup! Especially before upgrading, you should have a valid - and also tested! - backup you could use for a restore if something goes wrong.

Subscribe to Martin's Blog

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe