The official postgres image makes running PostgreSQL in Docker easy. Doing it safely — data that survives, backups that work, upgrades that don’t destroy the database — needs a few deliberate choices.

Compose file

services:
  db:
    image: postgres:17
    restart: unless-stopped
    environment:
      POSTGRES_USER: app
      POSTGRES_DB: app
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./initdb:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      timeout: 5s
      retries: 10
    shm_size: 256mb
    # ports:
    #   - "127.0.0.1:5432:5432"   # only if host tools need direct access

volumes:
  pgdata:

secrets:
  db_password:
    file: ./secrets/db_password.txt
mkdir -p secrets initdb
openssl rand -base64 24 > secrets/db_password.txt
chmod 600 secrets/db_password.txt
docker compose up -d

Key points:

  • Pin the major version (postgres:17). postgres:latest will eventually jump to a new major version, and PostgreSQL won’t start on a data directory from an older major version.
  • Named volume for /var/lib/postgresql/data — the data survives container recreation.
  • POSTGRES_PASSWORD_FILE reads the password from a Docker secret instead of a plain environment variable.
  • No published port by default: other containers on the same Compose network connect to db:5432. If you publish, bind to 127.0.0.1 — never expose PostgreSQL to the internet.
  • shm_size avoids “could not resize shared memory segment” errors on larger queries (Docker’s default /dev/shm is 64 MB).

Environment variables only apply on first start

POSTGRES_USER, POSTGRES_DB and the password are used only when the data directory is empty. Changing them later has no effect — change the password with SQL instead:

docker compose exec db psql -U app -c "ALTER ROLE app PASSWORD 'new-password';"

Init scripts

Files in ./initdb (*.sql, *.sql.gz, *.sh) run once at first initialisation, in alphabetical order — handy for extra databases, roles or extensions:

-- initdb/01-extensions.sql
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE DATABASE analytics OWNER app;

Connect

docker compose exec db psql -U app -d app

From an app container: postgres://app:PASSWORD@db:5432/app.

Tuning

Pass settings as command arguments:

    command:
      - postgres
      - -c
      - shared_buffers=512MB
      - -c
      - effective_cache_size=1536MB
      - -c
      - work_mem=16MB
      - -c
      - max_connections=100

Size them to the memory you give the container (see Docker CPU and memory limits) and PostgreSQL performance tuning.

Backups

Logical dumps through the container:

docker compose exec -T db pg_dump -U app -Fc app > app-$(date +%F).dump
docker compose exec -T db pg_dumpall -U app --globals-only > globals-$(date +%F).sql

Restore:

docker compose exec -T db pg_restore -U app -d app --clean --if-exists < app-2026-11-04.dump

Schedule with cron and copy dumps off the server. Don’t back up by copying the volume while PostgreSQL is running — the files may be inconsistent. See pg_dump and pg_restore.

Major version upgrades (e.g. 16 → 17)

Changing the image tag isn’t enough — PostgreSQL refuses to start with an older data directory. The simple, reliable method is dump and restore:

# 1. dump with the old version
docker compose exec -T db pg_dumpall -U app > full-dump.sql
# 2. stop and keep the old volume as a fallback
docker compose down
docker volume create pgdata17
# 3. point the service at postgres:17 and the new volume (edit compose.yaml), start it
docker compose up -d
# 4. restore
docker compose exec -T db psql -U app -d postgres < full-dump.sql

Check the application, then remove the old volume after a while. For big databases, pg_upgrade (via community images that bundle two versions) is faster. See upgrading PostgreSQL major versions.

Minor updates

Minor releases (17.1 → 17.2) are drop-in: docker compose pull && docker compose up -d. They contain bug and security fixes — apply them regularly.

Should you run your database in Docker?

For small and medium applications on a VPS, yes — with a pinned version, a volume, and real backups. For heavy production databases, many teams prefer PostgreSQL installed directly on the host (or a dedicated database server) for simpler tuning and upgrades. See installing PostgreSQL on a VPS.

  • PostgreSQL
  • Docker
  • Docker Compose