To upgrade PostgreSQL in Docker from version 11 to 16, you must perform a manual data migration because major versions use different internal storage formats and cannot be upgraded simply by changing the image tag.
Upgrading from PostgreSQL 11 to 16 offers substantial performance improvements—doubling query speed in some cases—improved logical replication, and significantly faster maintenance through enhanced VACUUM and parallelized operations. It provides better security control, modern SQL features, and improved I/O. Key benefits include faster pg_upgrade via --link and avoiding risks of running an unsupported version
This is the safest method and works by exporting your data to a SQL file and importing it into a fresh version 16 container.
docker exec -t <old_container_name> pg_dumpall -U <username> > /tmp/full_backup.sql
docker stop <old_container_name>
docker run --name postgres16 -e POSTGRES_PASSWORD=your_password -v postgres_16_data:/var/lib/postgresql/data -d postgres:16
cat /tmp/full_backup.sql | docker exec -i postgres16 psql -U <username>
I had this wiki running with postgres:11-alpine and migrate to postgres:16-alpine
services:
db:
image: postgres:11-alpine
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_PASSWORD: ${DB_PASS}
POSTGRES_USER: ${DB_USER}
logging:
driver: "none"
restart: unless-stopped
volumes:
- ./db-data:/var/lib/postgresql/data
wiki:
image: ghcr.io/requarks/wiki:2
depends_on:
- db
environment:
DB_TYPE: ${DB_TYPE}
DB_HOST: ${DB_HOST}
DB_PORT: ${DB_PORT}
DB_USER: ${DB_USER}
DB_PASS: ${DB_PASS}
DB_NAME: ${DB_NAME}
restart: unless-stopped
ports:
- ${EXPOSE_PORT}:3000
# Database connection
DB_TYPE=postgres
DB_HOST=db
DB_PORT=5432
DB_USER=my_wiki_user
DB_PASS=my_wiki_pass
DB_NAME=my_wiki_db
# Wiki configurations
EXPOSE_PORT=3000
I added a new PostgreSQL version 16 configuration
services:
db:
image: postgres:11-alpine
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_PASSWORD: ${DB_PASS}
POSTGRES_USER: ${DB_USER}
logging:
driver: "none"
restart: unless-stopped
volumes:
- ./db-data:/var/lib/postgresql/data
db2:
image: postgres:16-alpine
restart: unless-stopped
volumes:
- ./pg-data:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_PASSWORD: ${DB_PASS}
POSTGRES_USER: ${DB_USER}
healthcheck:
interval: 30s
retries: 5
start_period: 20s
test:
- CMD-SHELL
- pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}
timeout: 5s
wiki:
image: ghcr.io/requarks/wiki:2
depends_on:
- db
environment:
DB_TYPE: ${DB_TYPE}
DB_HOST: ${DB_HOST}
DB_PORT: ${DB_PORT}
DB_USER: ${DB_USER}
DB_PASS: ${DB_PASS}
DB_NAME: ${DB_NAME}
restart: unless-stopped
ports:
- ${EXPOSE_PORT}:3000
┌─[root@oracle-04]─[~/docker/wiki-tiozaodolinux-com]─[Tue Apr 28 15:43:35]
└──╼ # docker compose up -d
[+] up 3/3
✔ Container wiki-tiozaodolinux-com-db2-1 Started
✔ Container wiki-tiozaodolinux-com-db-1 Running
✔ Container wiki-tiozaodolinux-com-wiki-1 Running
┌─[root@oracle-04]─[~/docker/wiki-tiozaodolinux-com]─[Tue Apr 28 15:43:44]
└──╼ # docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
wiki-tiozaodolinux-com-db-1 postgres:11-alpine "docker-entrypoint.s…" db 24 hours ago Up 24 hours 5432/tcp
wiki-tiozaodolinux-com-db2-1 postgres:16-alpine "docker-entrypoint.s…" db2 3 seconds ago Up 3 seconds 5432/tcp
wiki-tiozaodolinux-com-wiki-1 ghcr.io/requarks/wiki:2 "docker-entrypoint.s…" wiki 24 hours ago Up 24 hours 0.0.0.0:3000->3000/tcp, [::]:3000->3000/tcp, 3443/tcp
┌─[root@oracle-04]─[~/docker/wiki-tiozaodolinux-com]─[Tue Apr 28 15:45:27]
└──╼ # docker exec -t wiki-tiozaodolinux-com-db-1 pg_dumpall -c -U ${DB_USER} > /tmp/dump-wikijs.sql
┌─[root@oracle-04]─[~/docker/wiki-tiozaodolinux-com]─[Tue Apr 28 15:47:51]
└──╼ # cat /tmp/dump-wikijs.sql | docker exec -i wiki-db-pg psql ${DB_NAME} ${DB_USER}
┌─[root@oracle-04]─[~/docker/wiki-tiozaodolinux-com]─[Tue Apr 28 15:50:10]
└──╼ $ docker exec -it wiki-db-pg psql ${DB_NAME} ${DB_USER}
psql (16.13)
Type "help" for help.
wiki=# ALTER USER $DB_USER WITH PASSWORD $DB_PASS;
ALTER ROLE
wiki=# quit
In PostgreSQL 11, it was still common to use MD5
In PostgreSQL 14+, the standard became SCRAM-SHA-256
Passwords stored in the old database were hashed using MD5
When the same password is entered, it doesn't change the password itself, but it updates the hash format