Docker Compose Postgres
This Compose file runs Postgres 17 with its data in a named volume, a health check and a port that only your machine can reach. Save it as compose.yaml, run docker compose up -d, and connect with postgres://app:app@localhost:5432/app.
Updated
Files
services:
db:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-app}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-app}
POSTGRES_DB: ${POSTGRES_DB:-app}
ports:
- "127.0.0.1:5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
- ./db/init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10
volumes:
pgdata:Save with your agent
Connect the Sniptide MCP server once, then ask your agent to keep this template in your library. It reads the files from the JSON version of this page and saves them as a private snippet you can edit and file into a collection.
claude mcp add --transport http sniptide https://app.sniptide.com/api/mcpRead https://sniptide.com/templates/docker-compose-postgres.json and save its files as a private Sniptide snippet titled "Docker Compose Postgres".Run it
docker compose up -d
docker compose ps # wait until db shows "healthy"
docker compose exec db psql -U app -d appdocker compose down stops the container and keeps the data. Adding -v deletes the volume too, which wipes the database.
What each part does
Line | What it does | When to change it |
|---|---|---|
image: postgres:17 | Pins the major version, so an upgrade never happens by accident. | When you upgrade. A new major version can't read the old data directory without a dump and restore. |
environment | Creates the user, password and database on the first start. ${POSTGRES_USER:-app} reads your .env and falls back to app. | Set real values in .env for anything beyond your laptop. |
ports | 127.0.0.1:5432:5432 publishes the port to your machine only. A bare 5432:5432 opens it on every network interface. | Change the first 5432 if another Postgres already uses that port. |
pgdata volume | Keeps the data when the container is recreated. | Rarely. |
docker-entrypoint-initdb.d | Runs the .sql and .sh files in db/init on the first start, in name order. | Add files for extensions or seed data. |
healthcheck | pg_isready reports when Postgres accepts connections. $$ passes a literal $ to the container's shell. | Rarely. Other services wait on it, as the next variant shows. |
Postgres 18 images keep their data in a versioned folder under /var/lib/postgresql, so when you move to postgres:18, mount the volume at /var/lib/postgresql instead of its data folder.
The environment values and init scripts apply only while the volume is empty. After the first start, changing POSTGRES_PASSWORD does nothing until you delete the volume or change the password in SQL.
Variant: with your app
Add the app as a second service. condition: service_healthy makes Compose start it only after Postgres passes its health check, and the app reaches the database by its service name, db.
services:
app:
build: .
environment:
DATABASE_URL: postgres://app:app@db:5432/app
ports:
- "3000:3000"
depends_on:
db:
condition: service_healthy
db:
image: postgres:17
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 5s
retries: 10
volumes:
pgdata:The database has no ports here: the app reaches it over the Compose network, and nothing outside needs to. The Next.js Dockerfile builds a matching app image.
Variant: with pgAdmin
pgAdmin gives you a browser interface to the database. Add it under services, next to db, open http://localhost:5050 and register a server with the host name db.
services:
pgadmin:
image: dpage/pgadmin4
environment:
PGADMIN_DEFAULT_EMAIL: [email protected]
PGADMIN_DEFAULT_PASSWORD: admin
ports:
- "127.0.0.1:5050:80"
depends_on:
- dbVariant: extensions on the first start
An init script can enable extensions such as pgvector. Switch the image to pgvector/pgvector:pg17, which ships the extension, and add this file:
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS pg_trgm;