Skip to content

Dev vs Production Mode

Keycloak has two startup modes: start-dev for development and start for production. They are not interchangeable — start-dev makes several convenience compromises that are unsafe in a real environment. Understanding the difference is important before you move beyond local learning.

start-dev is optimised for getting started quickly. It uses an embedded H2 in-memory database (data is lost on restart), enables HTTP (no TLS), disables hostname verification, and relaxes several security checks. Use it only on localhost when learning Keycloak.

Terminal window
docker run --name keycloak -p 8080:8080 \
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
-e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:latest start-dev

start is the production command. It requires an external database, a fixed hostname, and HTTPS (or a configured reverse proxy). Keycloak will refuse to start in production mode without a valid hostname and TLS configuration. Key environment variables:

Environment variablePurposeExample
KC_DBDatabase vendorpostgres
KC_DB_URLJDBC connection URLjdbc:postgresql://db:5432/keycloak
KC_DB_USERNAMEDB usernamekeycloak
KC_DB_PASSWORDDB passwordchangeme
KC_HOSTNAMEPublic hostname for Keycloakauth.example.com
KC_PROXY_HEADERSTrust proxy headers (xforwarded or forwarded)xforwarded
KC_HTTP_ENABLEDAllow HTTP (needed behind a TLS-terminating proxy)true
docker run --name keycloak -p 8080:8080 \
  -e KC_DB=postgres \
  -e KC_DB_URL=jdbc:postgresql://db:5432/keycloak \
  -e KC_DB_USERNAME=keycloak \
  -e KC_DB_PASSWORD=changeme \
  -e KC_HOSTNAME=auth.example.com \
  -e KC_PROXY_HEADERS=xforwarded \
  -e KC_HTTP_ENABLED=true \
  quay.io/keycloak/keycloak:latest start

For production, Keycloak recommends running kc.sh build (or kc.bat build) inside the image before starting. This pre-processes configuration and significantly reduces startup time. A typical production Dockerfile uses a two-stage build: one stage to run kc.sh build, another to copy the built distribution and run kc.sh start.

Terminal window
# Stage 1: build
FROM quay.io/keycloak/keycloak:latest AS builder
RUN /opt/keycloak/bin/kc.sh build --db=postgres
# Stage 2: run
FROM quay.io/keycloak/keycloak:latest
COPY --from=builder /opt/keycloak/ /opt/keycloak/
ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "start"]
OptionBenefitCost
start-dev (development mode)Fast setup — no TLS certificate, hostname, or external database needed. Good for learning and local developmentNo TLS, skips hostname checks, uses an in-memory H2 database that loses data on every restart — cannot be used in production
start (production mode)Forces TLS/hostname/database to be configured correctly before it will actually start, preventing an accidentally insecure deploymentRequires an external database, a certificate, and reverse proxy configuration up front — setup takes longer
  • Running start-dev in production because setup is easier — teams in a hurry to deploy often forget to switch to start, leaving the system without TLS and with data in an in-memory database that disappears on every container restart.
  • Forgetting to set KC_HOSTNAME to match the real domain — a mismatched hostname makes the issued tokens carry the wrong issuer URL, so any app that validates tokens strictly rejects all of them.
  • Enabling KC_HTTP_ENABLED=true with no real reverse proxy doing TLS termination — this setting should only be used when a proxy such as Nginx or a load balancer is already handling TLS, never left on for a bare public endpoint.

Real-world example

An organisation consolidating SSO — companies with dozens of internal apps commonly move all of them onto a single Keycloak instance in production mode, with external PostgreSQL and TLS through a load balancer, so employees log in once for every system.

Red Hat build of Keycloak (RH-SSO) — Red Hat’s supported build enforces the same principle: production-mode configuration (database, hostname, TLS) is required before a real deployment, to stop customers from accidentally running an insecure setup.

What database does start-dev use?
Which environment variable sets the public hostname in production mode?
What is the purpose of running kc.sh build before kc.sh start in production?
Which KC_PROXY_HEADERS value should you use when Keycloak is behind an Nginx or load balancer that sets X-Forwarded-* headers?