On this page 10 sections
To run PostgreSQL in Docker, start the official postgres image with a password and a named volume: docker run -d --name pg -e POSTGRES_PASSWORD=secret -p 5432:5432 -v pgdata:/var/lib/postgresql postgres:18. That gives you a Postgres 18 server on localhost:5432 whose data survives the container being removed. The one detail that changed recently is the volume path. From Postgres 18, the image keeps its data under /var/lib/postgresql. On 17 and older, mount /var/lib/postgresql/data.
This guide covers docker run, Docker Compose, init scripts, health checks, backups and major-version upgrades, then what a container leaves out once the data matters.
TL;DR
- Use the official
postgresimage and pin the major version (postgres:18, notlatest). - On Postgres 18 and newer, mount the volume at
/var/lib/postgresql. On 17 and older, mount it at/var/lib/postgresql/data. The wrong path writes data to an anonymous volume you lose on re-create. POSTGRES_PASSWORDis required.POSTGRES_USERandPOSTGRES_DBare optional, and all three apply only on the first start with an empty volume.- In Compose, give Postgres a
pg_isreadyhealth check and make the app wait forservice_healthy. - A container is great for development. In production you also need scheduled backups, point-in-time recovery, failover and upgrades, which is the work a managed Postgres does.
How do I run Postgres in Docker?
docker run -d \ --name pg \ -e POSTGRES_PASSWORD=secret \ -e POSTGRES_USER=app \ -e POSTGRES_DB=app \ -p 5432:5432 \ -v pgdata:/var/lib/postgresql \ postgres:18Then connect with any client:
psql "postgres://app:secret@localhost:5432/app"Or open psql inside the container, which needs nothing installed on your machine:
docker exec -it pg psql -U app -d appWhat each part does:
| Flag | Purpose |
|---|---|
-e POSTGRES_PASSWORD | Superuser password. Required, and it cannot be empty |
-e POSTGRES_USER | Creates this superuser and a database of the same name. Defaults to postgres |
-e POSTGRES_DB | Name of the default database. Defaults to the user name |
-p 5432:5432 | Publishes the port to your machine. Leave it out if only other containers connect |
-v pgdata:/var/lib/postgresql | A named volume, so data outlives the container |
The environment variables take effect only when the data directory is empty. Changing POSTGRES_PASSWORD later does nothing to an existing database. Change it with ALTER USER instead.
Where does Postgres store data in Docker?
This is the most common way to lose data with the official image, and the answer depends on the major version.
| Image | Mount the volume at | PGDATA inside it |
|---|---|---|
postgres:18 and newer | /var/lib/postgresql | /var/lib/postgresql/18/docker |
postgres:17 and older | /var/lib/postgresql/data | /var/lib/postgresql/data |
The image changed the layout in 18 so that a major-version upgrade can use pg_upgrade --link across versions sitting side by side in one volume. If you copy a Compose file written for Postgres 16 and only bump the tag to 18, the old mount path no longer matches. Postgres then writes to an anonymous volume, and the data disappears the next time the container is re-created.
Check where your data actually lives:
docker exec pg psql -U app -c 'SHOW data_directory;'docker inspect pg --format '{{ json .Mounts }}'How do I run Postgres with Docker Compose?
A Compose file keeps the app, the database and the volume together:
services: db: image: postgres:18 restart: unless-stopped environment: POSTGRES_USER: app POSTGRES_PASSWORD: secret POSTGRES_DB: app volumes: - pgdata:/var/lib/postgresql - ./initdb:/docker-entrypoint-initdb.d:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U app -d app"] interval: 5s timeout: 3s retries: 10 ports: - "5432:5432"
web: build: . environment: DATABASE_URL: postgres://app:secret@db:5432/app depends_on: db: condition: service_healthy
volumes: pgdata:Publish 5432 only if you want to reach the database from your machine. Other services in the file reach it either way.
The host is the service name
Inside the Compose network, the app connects to db:5432. localhost inside the web container is the web container itself.
Wait for the health check
depends_on alone only waits for the container to start. Postgres takes a few seconds to accept connections, so the health check with condition: service_healthy stops the app from crashing on its first connection attempt.
How do I run SQL when the container first starts?
Put .sql, .sql.gz or .sh files in a folder mounted at /docker-entrypoint-initdb.d, as in the Compose file above. The image runs them in name order after it creates the user and database:
-- initdb/01-extensions.sqlCREATE EXTENSION IF NOT EXISTS pg_trgm;CREATE EXTENSION IF NOT EXISTS pgcrypto;They run only on an empty data directory. If you add a script later, remove the volume (docker compose down -v, which deletes the data) or run the SQL by hand. If a script fails, the container exits, and the next start skips the scripts because the directory is no longer empty. Fix the script and recreate the volume.
The official image ships the contrib extensions such as pg_trgm, pgcrypto, citext and uuid-ossp. Extensions like pgvector and PostGIS need an image that includes them, such as pgvector/pgvector:pg18 or postgis/postgis.
How do I back up Postgres running in Docker?
Use pg_dump from inside the container and write the file to your machine:
docker exec pg pg_dump -U app -d app --format=custom > app.dumpRestore into a fresh database:
docker exec -i pg pg_restore -U app -d app --clean --if-exists < app.dumpCopying the volume’s files while Postgres is running does not give you a consistent backup. Stop the container first, or use pg_dump or pg_basebackup.
A nightly pg_dump from cron is a start. It still loses up to a day of writes, and it is only a backup once you have restored it somewhere and checked the data. Restoring to the minute needs WAL archiving with a tool such as pgBackRest or WAL-G.
How do I upgrade Postgres in Docker to a new major version?
Changing the tag from postgres:17 to postgres:18 on an existing volume does not upgrade the data. Postgres refuses to start on files from an older major version. The simplest path for small databases:
- Dump the old database:
docker exec pg pg_dump -U app -d app --format=custom > app.dump. - Start a new container on
postgres:18with a new, empty volume at the new path. - Restore the dump into it with
pg_restore, and point the app at the new container.
For large databases, pg_upgrade is faster, and the Postgres 18 volume layout exists to make it easier between future versions. Minor versions (18.5 to 18.6) need only a new image and a restart.
Should I run Postgres in Docker in production?
For development, CI and small internal tools, yes. A container gives everyone the same version with one command, and throwing the database away is a feature.
For data you cannot lose, the container is the easy part. The rest is the work around it:
| You need | In a container, you set up |
|---|---|
| Backups you can restore | pg_dump on a schedule, off-machine storage, restore tests |
| Restore to a point in time | WAL archiving with pgBackRest or WAL-G |
| A standby if the machine dies | Replication, failover and a way to move clients over |
| Upgrades | Minor-version patching, and a plan for each major version |
| Sizing | Memory settings (shared_buffers, work_mem) matched to the host |
| Security | Network rules, TLS, password rotation |
If you are happy to own that list, Postgres in Docker on a VM you control works well. If not, a managed Postgres runs the same engine with that list handled.
How Ownkube runs Postgres
Ownkube runs managed PostgreSQL 17 and 18, so a database you develop against in postgres:18 locally runs on the same major version in production. Databases start at $4 a month for DB 0.5 (0.25 vCPU, 0.5 GiB), with storage at $0.25 per GB. On Personal and Team, that storage price includes 7-day backups with point-in-time restore. On Team, a database can add up to two standbys with automatic failover.
Extensions such as pgvector, PostGIS and pg_cron turn on from the database’s Settings tab, so there is no custom image to build. The database is private by default and reachable from your apps in the same environment.
To move a local database across, open a tunnel and restore the dump you already know how to make:
okctl connect my-database --tunnel-only --port 15432pg_restore --no-owner -d "postgres://<user>:<password>@localhost:15432/<database>" app.dumpKeep docker compose up for local development. Your app container deploys from the same Dockerfile next to the managed database. Apps run in the Germany and Mumbai regions.
FAQ
What is the default Postgres username and password in Docker?
The default user is postgres, and there is no default password. POSTGRES_PASSWORD is required, so you set it the first time the container starts.
Why is my Postgres Docker data gone after a restart?
Usually the volume is mounted at the wrong path, so data went to an anonymous volume. On Postgres 18 and newer, mount at /var/lib/postgresql. On 17 and older, mount at /var/lib/postgresql/data. Data is also lost with docker compose down -v, which deletes named volumes.
Why does my app say “connection refused” to Postgres in Docker Compose?
Either the app connects to localhost instead of the service name (db), or it starts before Postgres accepts connections. Use the service name as the host and add a pg_isready health check with condition: service_healthy.
Why didn’t my POSTGRES_PASSWORD change take effect?
The environment variables apply only when the data directory is empty. On an existing volume, change the password with ALTER USER app WITH PASSWORD '...'.
Which Postgres Docker image should I use?
The official postgres image, pinned to a major version such as postgres:18. Use the default Debian-based tag unless image size matters, in which case postgres:18-alpine is smaller. For pgvector, use pgvector/pgvector:pg18.
Where Ownkube fits
Run Postgres in Docker for development, and hand production to a managed database when backups, restores and failover start to matter. On Ownkube that is Postgres 17 or 18 from $4 a month, with backups in the storage price on Personal and Team, next to your app on one bill. The add a Postgres database guide walks through it, and managed Postgres pricing compared prices the alternatives. Deploy my first app.