PostgreSQL in Docker is one command and a volume. The official postgres image handles initialization, user/database creation, and exposes the right port; you just need to set the password env var and mount a named volume on the data directory so the container can come and go without taking your data with it.
How do I run PostgreSQL in Docker?
docker run -d --name postgres \
-e POSTGRES_PASSWORD=change-me \
-p 5432:5432 \
-v pgdata:/var/lib/postgresql/data \
postgres:17Postgres 17 in the background, password set, port 5432 published, named volume pgdata on the data directory so you can docker rm the container without losing tables.
Configure the version, ports, password, and volume.
Pick a version
postgres:17— Postgres 17, current major (released September 2024). Recommended for new projects.postgres:16— Postgres 16, still supported, very common in production.postgres:15,postgres:14,postgres:13— older majors, all still receiving security fixes through their end-of-life dates (13 EOL November 2025, 14 EOL November 2026, 15 EOL November 2027, 16 EOL November 2028).postgres:17-alpine— Alpine variant, smaller but musl libc.
Postgres has a fixed 5-year support lifecycle per major. Pin to the major (postgres:17); the patch level auto-updates on docker pull.
The basic run command
docker run -d --name :container_name \
-e POSTGRES_PASSWORD=:password \
-p :host_port:5432 \
-v :volume:/var/lib/postgresql/data \
postgres::pg_versionThe flags:
POSTGRES_PASSWORD— required; the image refuses to start without it (unless you use thetrusthost-auth-method, which is the next thing).-p :host_port:5432— publishes port 5432 on the host.-v :volume:/var/lib/postgresql/data— the data directory volume.
Optional init env vars (only honored on first run, empty data directory):
docker run -d --name :container_name \
-e POSTGRES_PASSWORD=:password \
-e POSTGRES_USER=appuser \
-e POSTGRES_DB=myapp \
-p :host_port:5432 \
-v :volume:/var/lib/postgresql/data \
postgres::pg_versionPOSTGRES_USERcreates a superuser with that name (instead of the defaultpostgres). The image then uses this user for anyPOSTGRES_DBit creates.POSTGRES_DBcreates an initial database. Default is the same name as the user.
For local testing without a password, use:
docker run -d --name :container_name \
-e POSTGRES_HOST_AUTH_METHOD=trust \
-p :host_port:5432 \
-v :volume:/var/lib/postgresql/data \
postgres::pg_versiontrust means "no password needed for any connection." Do this only on a local dev machine; do not expose port 5432 to a network.
Why you need a volume
Postgres stores everything (tables, indexes, WAL, configuration files) under /var/lib/postgresql/data inside the container. Without a volume mounted there, the data lives in the container's writable layer and is destroyed by docker rm. With -v pgdata:/var/lib/postgresql/data, Docker manages a persistent volume that survives container removal.
# Stop and remove the container
docker stop :container_name && docker rm :container_name
# Start a new Postgres container with the same volume — your data is intact
docker run -d --name :container_name \
-e POSTGRES_PASSWORD=:password \
-p :host_port:5432 \
-v :volume:/var/lib/postgresql/data \
postgres::pg_versionImportant: the data directory format changes between major Postgres versions. A volume initialized by Postgres 16 cannot be mounted by Postgres 17 directly — Postgres 17 will refuse to start with a "incompatible data directory" error. To upgrade across majors, use pg_dump from the old version, start a new container with the new version on a fresh volume, and pg_restore. Or use pg_upgrade in a more involved migration. Pinning to a specific major (postgres:17 not postgres:latest) is what keeps this from biting you on a docker pull.
Bind mount alternative:
mkdir -p ~/docker-data/pg
docker run -d --name :container_name \
-e POSTGRES_PASSWORD=:password \
-p :host_port:5432 \
-v ~/docker-data/pg:/var/lib/postgresql/data \
postgres::pg_versionBind mounts to host paths sometimes hit permission issues on Mac and Windows (Docker Desktop's virtual filesystem). For databases, named volumes are easier.
Practical usage: connecting
With psql from the container:
docker exec -it :container_name psql -U postgres-U postgres matches the default user. If you set POSTGRES_USER=appuser, use that name.
From the host with psql:
psql -h 127.0.0.1 -p :host_port -U postgres
# password promptConnection string for an app:
postgres://postgres::password@127.0.0.1::host_port/postgres
postgres at the end is the default database name. Replace with POSTGRES_DB value if you set one.
From another container on the same Docker network:
docker network create app-net
docker run -d --name :container_name --network app-net \
-e POSTGRES_PASSWORD=:password \
-v :volume:/var/lib/postgresql/data \
postgres::pg_version
# From another container:
docker run -it --rm --network app-net postgres::pg_version \
psql -h :container_name -U postgresThe Postgres container is reachable as :container_name on port 5432 from anything else on app-net; no port publishing needed for in-network traffic.
Healthcheck with pg_isready
The image ships pg_isready for readiness probing — useful in Compose to wait for the database before starting dependent services:
services:
db:
image: postgres::pg_version
environment:
POSTGRES_PASSWORD: :password
volumes:
- :volume:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
web:
image: my-app
depends_on:
db:
condition: service_healthyWithout condition: service_healthy, web starts before the database is actually accepting connections and fails. See Docker Restart Policies and Health Checks.
Backups
Logical backup with pg_dump:
docker exec :container_name \
pg_dump -U postgres -Fc postgres > backup-$(date +%Y%m%d).dump-Fc is the custom format — compressed and works with pg_restore. Restore:
docker exec -i :container_name \
pg_restore -U postgres -d postgres --clean --if-exists < backup-$(date +%Y%m%d).dumpLogical backup of all databases:
docker exec :container_name \
pg_dumpall -U postgres > all-dbs-$(date +%Y%m%d).sqlVolume snapshot (requires stopping for consistency):
docker stop :container_name
docker run --rm -v :volume:/source:ro -v "$(pwd):/backup" alpine \
tar czf /backup/pg-data-$(date +%Y%m%d).tar.gz -C /source .
docker start :container_nameDocker Compose version
services:
db:
image: postgres::pg_version
environment:
POSTGRES_PASSWORD: :password
POSTGRES_USER: appuser
POSTGRES_DB: myapp
ports:
- "::host_port:5432"
volumes:
- :volume:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d myapp"]
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
volumes:
:volume:Common pitfalls
POSTGRES_PASSWORDnot set — image refuses to start. Either set it or setPOSTGRES_HOST_AUTH_METHOD=trustfor local-only use.- Init env vars (
POSTGRES_USER,POSTGRES_DB) ignored on second run. Only honored on first init of an empty data directory. If the volume already has data, create users/databases with SQL. - Major version bump breaking the volume. Going from
postgres:16topostgres:17on the same volume fails — Postgres won't read a data directory from a previous major. Always pin to a major, and migrate explicitly withpg_dump/pg_restoreorpg_upgrade. - Locale errors on Alpine.
postgres:17-alpineis smaller but the locale data is minimal. If your app needs specific locales (Turkish text sort, etc.), use the regular Debian-based image. - Connecting from the host with
localhost. Use127.0.0.1. Some clients interpretlocalhostas the Unix socket, which doesn't exist on the host for a containerized server. - Port 5432 already in use — you have another Postgres on the host. Use a different host port.
What to do next
- Run Adminer in Docker — a quick GUI to browse this database.
- Docker Volumes vs Bind Mounts — full persistence picture.
- How to Dockerize a Python App — pair this Postgres with a Django/Flask app.
- Docker Compose: Getting Started — the right way to wire Postgres into a stack.
FAQ
Sources
Authoritative references this article was fact-checked against.
- Official PostgreSQL image — Docker Hubhub.docker.com
- PostgreSQL versioning policypostgresql.org

