Skip to content
ineedcloudineedcloud — home
All guides
HomelabIntermediate

Run Keycloak and PostgreSQL together with Docker Compose

The built-in H2 database isn't for production. Here's the Docker Compose file that wires Keycloak to PostgreSQL — persistent, restartable, and ready for real use.

14 min readLast updated 4 May 2026

TL;DR

The development mode quickstart uses an in-memory database — everything is gone when you restart. This guide gives you a docker-compose.yml that runs Keycloak with a proper PostgreSQL database, so data survives restarts.

What you'll need

  • Docker and Docker Compose installed (docker compose version to confirm)
  • The Keycloak quickstart done so you know your way around the admin console
  • 5 minutes

Step 1 — Create the Compose file

Create a directory for the project:

mkdir keycloak && cd keycloak

Create docker-compose.yml:

services:
  postgres:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: changeme_db
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U keycloak"]
      interval: 10s
      timeout: 5s
      retries: 5

  keycloak:
    image: quay.io/keycloak/keycloak:latest
    restart: unless-stopped
    command: start-dev
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "8080:8080"
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: changeme_admin
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: changeme_db

volumes:
  postgres_data:

Change the passwords before running anything. Use something long and random for changeme_db and changeme_admin.

Step 2 — Start the stack

docker compose up -d

Watch the logs until Keycloak is ready:

docker compose logs -f keycloak

Wait for the "started" message, then open http://localhost:8080.

Step 3 — Verify data persists

  1. Log in to the admin console and create a test realm and user
  2. Stop the stack: docker compose down
  3. Start it again: docker compose up -d
  4. Log in — your realm and user should still be there

That's the difference from the quickstart: PostgreSQL stores everything in a named Docker volume (postgres_data) that survives container restarts.

Step 4 — Useful day-to-day commands

# Start in the background
docker compose up -d

# View logs
docker compose logs -f

# Stop without deleting data
docker compose down

# Stop and delete ALL data (volumes too) — careful
docker compose down -v

Common pitfalls

Keycloak starts before Postgres is ready. The healthcheck and depends_on: condition: service_healthy in the Compose file prevent this. Without it, Keycloak tries to connect to Postgres before it accepts connections and crashes.

Using old environment variable names. Keycloak 17+ uses KC_DB, KC_DB_URL, KC_DB_USERNAME, and KC_DB_PASSWORD. The old DB_VENDOR, DB_ADDR, DB_USER, and DB_PASSWORD variables no longer work. If you're following an old tutorial that uses those names, it'll fail silently or error on startup.

start-dev in production. For a proper production setup, change command: start-dev to command: start and add KC_HOSTNAME and KC_PROXY=edge — see the Nginx guide.

Where to go next