How to Run PostgreSQL in Docker
(with Docker Compose)
Docker gives you a clean, reproducible Postgres environment in under a minute — no system install required. This guide covers the one-liner quickstart, a production-ready Docker Compose setup with persistent volumes, and when self-hosting Postgres in Docker stops being worth the effort.
Quick Start — docker run
The fastest way to get Postgres running locally:
docker run -d \ --name my-postgres \ -e POSTGRES_USER=myuser \ -e POSTGRES_PASSWORD=mypassword \ -e POSTGRES_DB=mydb \ -p 5432:5432 \ postgres:17
Connect with psql:
psql postgresql://myuser:mypassword@localhost:5432/mydb
Data warning: without a volume mount, all data lives inside the container. Run docker rm my-postgres and it's gone permanently. Use Docker Compose with a named volume for anything you care about.
The Real Setup — Docker Compose + Named Volume
Docker Compose is how most teams actually run Postgres locally. It handles startup order, environment variables, volume mounts, and health checks in one file you can commit to git.
services:
postgres:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
POSTGRES_DB: mydb
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myuser -d mydb"]
interval: 10s
timeout: 5s
retries: 5
volumes:
pgdata:Pin the version. Use postgres:17 not postgres:latest. The latest tag will eventually bump a major version and break your migrations without warning.
Seeding Schema on First Start
The official image runs any .sql or .sh files in /docker-entrypoint-initdb.d/ on first boot. Mount an init script to seed schema or create extra databases:
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sqlOnly runs when pgdata is empty (fresh volume). Safe to leave in — ignored on all subsequent starts.
Why Developers Run Postgres in Docker
Pin postgres:17.2 and every developer, CI job, and staging server runs the exact same version — no "works on my machine" drift.
Run Postgres 14, 15, 16, and 17 side by side. Each project gets its own isolated instance with no port or binary conflicts.
Spin up a fresh DB per test run, run migrations, run tests, tear down. GitHub Actions and GitLab CI support Docker services natively.
Commit docker-compose.yml to the repo. New developers run one command and have a working database in 30 seconds.
Where It Gets Hard
- Backups are your job. No automatic pg_dump, no point-in-time recovery, no off-site copy. You build this or you skip it.
- Production ops don't disappear. Disk fills up, major version upgrades need pg_upgrade, replicas need manual setup. Docker moves the ops burden — it doesn't remove it.
- Volume performance on Mac and Windows. Docker Desktop mounts are noticeably slower than native disk for write-heavy workloads.
- Container networking trips teams up. Other containers reach Postgres via the service name (
postgres), notlocalhost. Health checks prevent premature connections. - One command wipes everything.
docker compose down -vdeletes the volume silently. No confirmation, no recycle bin.
Skip the Ops for Your Production Database
Docker Postgres is great for local dev and CI. When you're shipping to real users — get a managed Postgres database in 30 seconds, backups included, zero ops.
Get a Free Postgres Database →Free tier · No credit card · Instant connection string
Frequently Asked Questions
Yes — as long as you have a named volume mounted to /var/lib/postgresql/data. docker compose stop then docker compose up preserves everything. docker compose down -v removes the volume and wipes all data.
Port 5432 — same as a native install. Map it with -p 5432:5432 or ports: ["5432:5432"] in Docker Compose to reach it from your host machine.
It works, but you own everything: backups, HA, major version upgrades, disk monitoring. For most indie projects and startups, a managed Postgres service saves significant time and reduces risk.
Pin a specific major version like postgres:17 rather than postgres:latest. The latest tag will eventually jump to a new major version and silently break your migrations.