PostgreSQL 18 Docker official image per the Docker Hub: data directory is /var/lib/postgresql (changed in v18 from /var/lib/postgresql/data). Use env vars for password, expose port 5432, mount a volume for data persistence. The team that uses named volumes has restart-safe Postgres. The team that uses bind mounts has direct file access for backups.
Table of contents
- Quick start
- The data path change (v18)
- Migrating from v17 to v18
- Environment variables
- Health check
- Backup from the host
- Performance tuning
- Postgres 18 new features
- FAQ
Quick start
docker run -d --name postgres18 \
-e POSTGRES_PASSWORD=mysecretpassword \
-p 5432:5432 \
-v postgres_data:/var/lib/postgresql \
postgres:18
The team that runs this has Postgres 18 in 30 seconds. Named volume postgres_data persists data across container removal.
The data path change (v18)
Postgres 18 changed the data directory layout:
- v17 and earlier:
/var/lib/postgresql/data - v18+:
/var/lib/postgresql(or/var/lib/postgresql/18/dockerfor major-version-specific paths)
The team that upgrades from v17 to v18 needs to either migrate the data or do a pg_upgrade. The Reddit r/selfhosted thread covers this in detail.
Migrating from v17 to v18
The cleanest path:
# Dump from v17
docker exec postgres17 pg_dumpall -U postgres > backup.sql
# Restore to v18
docker exec -i postgres18 psql -U postgres < backup.sql
Or use pg_upgrade for in-place migration (faster but more complex). The team that has small DBs uses dump/restore; large DBs use pg_upgrade.
Environment variables
POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypassword
POSTGRES_DB=mydb
PGDATA=/var/lib/postgresql/data # override data dir (rare)
POSTGRES_INITDB_ARGS=--encoding=UTF8 --locale=C
The team that sets these at container start has the right config. The team that uses defaults (user=postgres, db=postgres) should change for production.
Health check
docker run -d --name postgres18 \
-e POSTGRES_PASSWORD=mysecretpassword \
--health-cmd "pg_isready -U postgres" \
--health-interval 10s \
--health-timeout 5s \
--health-retries 5 \
postgres:18
The team that uses health checks has proper container lifecycle (Docker Compose depends_on: condition: service_healthy).
Backup from the host
The team that has named volumes uses a backup container:
docker run --rm -v postgres_data:/var/lib/postgresql \
-v $(pwd):/backup postgres:18 \
pg_dumpall -U postgres > /backup/postgres-backup.sql
The team that has scheduled backups has disaster recovery.
Performance tuning
For production, tune Postgres via env vars or config:
-e POSTGRES_INITDB_ARGS="--encoding=UTF8 --locale=C"
And mount a postgresql.conf:
-v ./postgresql.conf:/etc/postgresql/postgresql.conf
-c "config_file=/etc/postgresql/postgresql.conf"
The team that has shared_buffers, work_mem, etc. tuned has production-grade performance.
Postgres 18 new features
Postgres 18 (released 2025) features per the official docs:
- Performance improvements (2-3x faster on some workloads).
- Better replication and HA features.
- Enhanced security defaults.
- Improvements to partitioning and logical replication.
The team that uses Postgres 18 on new deployments has the latest features; the team that uses 16 or 17 has stability + extensions.
FAQ
Where does Postgres 18 store data in Docker?
/var/lib/postgresql (the new layout in v18). Mount a volume here. The team that uses the v17 path (/var/lib/postgresql/data) has the wrong path on v18.
Can I run multiple Postgres versions side by side?
Yes - use different ports. docker run -p 5433:5432 postgres:17 and docker run -p 5432:5432 postgres:18. The team that migrates runs both during transition.
How do I upgrade Postgres 17 to 18 in Docker?
Dump and restore: pg_dumpall from v17, restore to v18. Or use pg_upgrade (faster for large DBs). The team that uses dump/restore has simpler upgrade path.
What about the docker-entrypoint.sh script?
The official image’s entrypoint handles initdb (first-time setup), user creation, and config. The team that uses custom Dockerfiles based on the official image has this baked in.
Should I use bind mounts or named volumes?
Named volumes: managed by Docker, easier. Bind mounts: direct file access, can be backed up directly. The team that uses bind mounts has more ops control.
If you are sizing the infrastructure for the kind of project this post covers, the RunxBuild hosting calculator is the right place to model the line items. The compute, the memory, the storage, the bandwidth, the database - each one is a separate number, and the team’s mental model for the platform is the sum of those numbers. The RunxBuild dashboard is where the team sees the actual usage in one place.
Useful related references: