Das offizielle postgres-Image auf Docker Hub ist eine der saubersten Varianten, Postgres lokal oder im CI laufen zu lassen — definierte Version, schnelles Wegwerfen, keine Reste auf dem Host. In diesem Artikel zeigen wir die wichtigsten Patterns: Single-Container-Setup, persistente Volumes, Initialdaten und ein typisches docker-compose.yaml für die Entwicklung.

Schnellstart

Bash Postgres-Container starten
docker run --name pg18 \
  -e POSTGRES_PASSWORD=secret \
  -p 5432:5432 \
  -d postgres:18

Was passiert hier:

  • --name pg18 gibt dem Container einen festen Namen, sodass spätere Befehle (docker exec, docker logs) ihn referenzieren können.
  • -e POSTGRES_PASSWORD=secret setzt das Passwort des Default-Users postgres. Diese Variable ist Pflicht; ohne sie startet das Image nicht (außer mit POSTGRES_HOST_AUTH_METHOD=trust, was du nur in Wegwerf-Tests nutzen solltest).
  • -p 5432:5432 mappt Port 5432 vom Container auf den Host.
  • postgres:18 ist die feste Version. Verwende möglichst nicht latest — auch lokal nicht. Sonst landest du nach einem Major-Release auf einer neuen Version, die das alte Daten-Verzeichnis nicht öffnet.

Verbindung von außen:

Bash
psql -h localhost -U postgres
# Password: secret

Oder direkt im Container:

Bash
docker exec -it pg18 psql -U postgres

Daten persistent speichern

Das obige Setup verliert alle Daten, sobald der Container entfernt wird. Für ernsthafte Verwendung brauchst du ein Volume:

Bash Mit benanntem Volume
docker run --name pg18 \
  -e POSTGRES_PASSWORD=secret \
  -p 5432:5432 \
  -v pg18_data:/var/lib/postgresql/data \
  -d postgres:18

Das Image schreibt sein Daten-Verzeichnis nach /var/lib/postgresql/data. Mit -v pg18_data:/var/lib/postgresql/data legst du ein benanntes Docker-Volume an, das zwischen Container-Neustarts erhalten bleibt. Auflisten mit:

Bash
docker volume ls
docker volume inspect pg18_data

Bind-Mount statt benanntem Volume? Möglich, aber nicht empfohlen — auf macOS und Windows leidet die Performance, und Permissions zwischen Host und Container sind eine ständige Reibungsquelle. Benannte Volumes sind die saubere Default-Wahl.

Wichtige Environment-Variablen

Das Image kennt eine kleine, klar definierte Schnittstelle:

VariableWirkung
POSTGRES_PASSWORDPflicht (außer Trust). Passwort des Superusers.
POSTGRES_USERName des Superusers. Default: postgres.
POSTGRES_DBDatenbank, die beim ersten Start angelegt wird. Default: gleicher Name wie POSTGRES_USER.
POSTGRES_INITDB_ARGSZusätzliche Argumente für initdb, z. B. --data-checksums.
POSTGRES_HOST_AUTH_METHODAuthentifizierungsmethode in der generierten pg_hba.conf. Default: scram-sha-256.
PGDATAPfad innerhalb des Containers für das Daten-Verzeichnis. Hat einen sinnvollen Default.
TZZeitzone des Containers. Wirkt sich auf now()-Timestamps aus, sofern keine TZ in der Spalte steht.

Wichtig: Diese Variablen wirken nur beim ersten Start, also wenn das Daten-Verzeichnis leer ist. Wer später POSTGRES_PASSWORD ändert, bekommt damit nichts geändert — das Passwort liegt schon im Volume. Für nachträgliche Änderungen: ALTER USER postgres WITH PASSWORD '…'; in psql.

Initialdaten beim ersten Start einspielen

Das Image führt beim ersten Start alle *.sql, *.sql.gz und *.sh-Dateien aus dem Verzeichnis /docker-entrypoint-initdb.d/ aus — alphabetisch sortiert.

Bash Schema beim Bootstrap einspielen
docker run --name pg18 \
  -e POSTGRES_PASSWORD=secret \
  -p 5432:5432 \
  -v pg18_data:/var/lib/postgresql/data \
  -v ./initdb:/docker-entrypoint-initdb.d:ro \
  -d postgres:18

Praktisch für lokale Entwicklung: ein 01-schema.sql, ein 02-seed.sql, schon hat jeder Entwickler beim ersten Start denselben Stand. Wieder gilt: nur einmalig, beim leeren Daten-Verzeichnis. Wenn du zwischendurch das Schema änderst, brauchst du eine richtige Migration (Flyway, Liquibase, sqitch, golang-migrate, …) — Init-Scripts sind kein Migrations-Werkzeug.

docker-compose-Setup

Für die meisten Projekte ist docker-compose.yaml lesbarer und stabiler als ein einzelner docker run-Befehl:

YAML docker-compose.yaml
services:
  db:
    image: postgres:18
    container_name: pg18
    restart: unless-stopped
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: app
    ports:
      - "5432:5432"
    volumes:
      - pg18_data:/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

volumes:
  pg18_data:

Starten und stoppen:

Bash
docker compose up -d
docker compose logs -f db
docker compose down       # Container weg, Volume bleibt
docker compose down -v    # Container UND Volume weg

docker compose down -v ist der bequeme „Schema neu, Daten weg“-Knopf für lokale Entwicklung. Im CI ist das oft genau, was man will.

Backup und Restore aus dem Container

Quick-and-dirty-Backup aus dem laufenden Container:

Bash Logisches Backup
docker exec pg18 pg_dump -U app app > backup.sql

Restore in einen frischen Container:

Bash
cat backup.sql | docker exec -i pg18 psql -U app -d app

Für produktionsähnliche Backups gibt es ein eigenes Kapitel (pg_dump, pg_basebackup, WAL-Archivierung, Point-in-Time-Recovery).

FAQ

Warum schlägt der Start mit „database files are incompatible“ fehl?

Du hast ein Volume aus einer älteren Postgres-Version und im Compose image: postgres:18 stehen. Major-Versionen lesen die Daten der Vorgänger nicht direkt — entweder bewusst auf der alten Version bleiben, oder mit pg_upgrade (am einfachsten über Tools wie tianon/postgres-upgrade) das Volume migrieren.

Wie verbinde ich aus einem anderen Container auf den Datenbank-Container?

Innerhalb desselben docker-compose-Netzwerks erreicht man den DB-Container unter dem Service-Namen. Aus einem app-Service heraus also host: db, nicht localhost. localhost zeigt im Container immer auf den Container selbst.

Das Image ist ~400 MB groß. Geht das kleiner?

Es gibt das Tag postgres:18-alpine — auf Alpine basierend, deutlich schlanker. Die Funktionalität ist identisch; der einzige Unterschied ist die libc-Implementierung (musl statt glibc), was bei sehr exotischen Locales selten zu Unterschieden führen kann. Für Produktion sind beide Varianten verbreitet.

Kann ich Extensions wie postgis oder pgvector nachinstallieren?

Das offizielle Image hat eine begrenzte Auswahl. Für Extensions, die nicht enthalten sind, gibt es spezialisierte Images: postgis/postgis, pgvector/pgvector. Alternativ: eigenes Dockerfile FROM postgres:18 mit apt-get install postgresql-18-<ext>.

Die Logs sind sehr leise. Wo kommt mehr raus?

Per Default loggt das Image nur Verbindungen und Fehler. Mehr Detail bekommst du über command-Overrides in compose, z. B. command: ["postgres", "-c", "log_statement=all", "-c", "log_min_duration_statement=0"]. Vorsicht damit in Produktion — log_statement=all schreibt jedes SQL ins Log, inkl. Passwörtern in CREATE USER-Statements.

Warum ist pg_isready der Standard-Healthcheck?

pg_isready ist im Image enthalten, leichtgewichtig und prüft, ob der Server tatsächlich Verbindungen akzeptiert — nicht nur, ob der Prozess läuft. Während eines Recoveries oder beim Booten antwortet er mit Exit-Code != 0, was Compose den Service als „starting“ oder „unhealthy“ markieren lässt. Andere Services können mit depends_on: { db: { condition: service_healthy } } darauf warten.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Grundlagen

Zur Übersicht