Docker Compose: Keycloak + PostgreSQL
The fastest way to get Keycloak running locally is Docker Compose with a PostgreSQL backend. This setup mirrors a real production configuration and takes under 5 minutes.
Prerequisites
- Docker Desktop installed and running
- Port 8080 available
docker-compose.yml
Create docker-compose.yml in your project root:
version: '3.9'
services:
postgres:
image: postgres:16-alpine
container_name: keycloak_db
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: keycloak_secret
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U keycloak"]
interval: 10s
timeout: 5s
retries: 5
networks:
- keycloak_net
keycloak:
image: quay.io/keycloak/keycloak:25.0
container_name: keycloak
command: start-dev
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: keycloak_secret
KC_HOSTNAME: localhost
KC_HOSTNAME_PORT: 8080
KC_HOSTNAME_STRICT: false
KC_HOSTNAME_STRICT_HTTPS: false
KC_HTTP_ENABLED: true
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin_password
ports:
- "8080:8080"
depends_on:
postgres:
condition: service_healthy
networks:
- keycloak_net
volumes:
postgres_data:
networks:
keycloak_net:
driver: bridge
Starting Keycloak
# Start in background
docker compose up -d
# Watch logs until ready
docker compose logs -f keycloak
# Look for: "Keycloak 25.0 on JVM ... started in Xs"
Access the admin console at: http://localhost:8080
Login with: admin / admin_password
Stopping and Data Persistence
# Stop (data preserved in postgres_data volume)
docker compose stop
# Stop and remove containers but keep data
docker compose down
# Stop and DESTROY all data
docker compose down -v
Importing a Realm on Startup
Place a realm export file at ./realm-export.json and add to keycloak service:
keycloak:
command: start-dev --import-realm
volumes:
- ./realm-export.json:/opt/keycloak/data/import/realm-export.json
Healthcheck
Verify Keycloak is healthy:
curl -s http://localhost:8080/health/ready
# {"status":"UP","checks":[...]}
# Get the OIDC discovery document for your realm
curl -s http://localhost:8080/realms/master/.well-known/openid-configuration | jq .
Common Issues
Port 8080 in use:
ports:
- "8090:8080" # Change host port
Slow startup on first run: Keycloak initializes the database schema on first boot. This takes 30-60 seconds — wait for the "started" log message.
Database connection refused:
The depends_on with service_healthy ensures Postgres is ready before Keycloak starts. If it still fails, check Postgres logs: docker compose logs postgres.