| Tested on | Red Hat Enterprise Linux 10.2 (Coughlan) |
|---|---|
| Package | podman-5.8.2-5.el10_2.x86_64quay.io/keycloak/keycloak:26.7.3 |
| Applies to | Any Linux host with a Keycloak ZIP install, Podman, or Docker |
| Privilege | Normal user for rootless Podman and ZIP show-config commands |
| Scope | Map Keycloak options to KC_* environment variables, pass them with Docker, Podman, Compose, or a Linux ZIP install, and diagnose ignored values. Does not catalog every option or deploy PostgreSQL. |
| Related guides | Install Keycloak with Podman Keycloak start vs start-dev Install Keycloak with Docker Keycloak container volumes and persistence Install Keycloak on Kubernetes |
In this article we will configure Keycloak using KC_* environment variables. Keycloak environment-variable names normally begin with KC_. The * in KC_* is only shorthand for any Keycloak variable; you do not type the asterisk.
An environment variable is a NAME=value setting available to a running process. When Keycloak starts with KC_HOSTNAME=auth.example.com in its environment, Keycloak reads that value as its hostname. Environment variables are especially convenient for containers because the same image can be configured differently without editing files inside it.
How Keycloak environment variables work
Keycloak server settings can be supplied as command-line options, environment variables, or lines in conf/keycloak.conf. Those three forms are different spellings of the same underlying option.
One setting, three configuration methods
Suppose you want to set Keycloak's hostname to auth.example.com. These three lines do the same job:
Same setting, three spellings:
CLI --hostname=auth.example.com
keycloak.conf hostname=auth.example.com
environment KC_HOSTNAME=auth.example.comOfficial Keycloak documentation uses the same idea: CLI, environment, and the configuration file are different representations of one server option.
How Keycloak option names map to KC_*
For normal Keycloak options, the environment variable follows a simple rule. Start from the option name, which normally uses lowercase words separated by hyphens (db-url-host, sometimes called kebab-case):
- Uppercase it:
DB-URL-HOST - Replace hyphens with underscores:
DB_URL_HOST - Prefix
KC_:KC_DB_URL_HOST
Same setting, three spellings:
CLI --db-url-host=postgres
keycloak.conf db-url-host=postgres
environment KC_DB_URL_HOST=postgresCLI / keycloak.conf |
Environment variable |
|---|---|
--hostname=auth.example.com |
KC_HOSTNAME=auth.example.com |
--db-url-host=mydb |
KC_DB_URL_HOST=mydb |
--metrics-enabled=true |
KC_METRICS_ENABLED=true |
--bootstrap-admin-username=admin |
KC_BOOTSTRAP_ADMIN_USERNAME=admin |
Keys containing characters that do not map cleanly to this pattern can require special handling. That case is covered later under KCKEY_*. Most options use the normal KC_* form.
How to find the correct environment variable for a Keycloak option
The safest method is to look the option up rather than guessing the name.
Keycloak's All configuration page lists CLI and environment forms for each supported option. Search for the hyphenated option name (hostname, db-url-host, http-relative-path) and read the Env column on that row.
For ordinary options you can usually derive the name yourself with the conversion rule above. Confirm the result against All configuration when the value seems ignored.
The naming rule converts an existing Keycloak option into environment-variable form. It does not mean you can invent a KC_* variable by guessing its name. If the page has no matching option, the environment variable is not a Keycloak setting, even when the name looks logical.
Set Keycloak environment variables
Keycloak reads KC_* from the environment of the process that starts the server. Containers do not inherit variables from your host shell automatically. You pass them in with -e, --env-file, or a Compose environment: block.
Set variables with Docker or Podman -e
In Podman and Docker, -e NAME=value passes an environment variable into the container. --rm removes the container when the command finishes.
export KC_HOSTNAME=… on the host does not enter the container. The -e flag is what copies the value across:
Host shell Container
┌──────────────────────────┐
│ export KC_HOSTNAME=... │
│ │
│ not inherited ──✕──► │ (missing unless you pass -e)
└──────────────────────────┘
Your terminal
│
│ -e KC_HOSTNAME=auth.example.com
▼
Container environment
│
│ KC_HOSTNAME=auth.example.com
▼
Keycloak process
│
▼
hostname = auth.example.comshow-config asks Keycloak to print the final values it resolved after reading those sources. It does not start the Keycloak server or prove that an external service such as PostgreSQL is reachable. Here it only checks that Keycloak recognized the environment variable.
Pass one hostname and print what Keycloak resolved. Extra podman run flags such as publish and detach are covered in Run containers with podman run:
podman run --rm -e KC_HOSTNAME=auth.example.com quay.io/keycloak/keycloak:26.7.3 show-configThe (ENV) suffix on the hostname line confirms the container environment supplied the value:
kc.hostname = auth.example.com (ENV)docker run uses the same -e flags and the same KC_* names.
Set multiple variables with --env-file
An env file avoids repeating many -e flags and prevents their values from appearing directly in the command stored in your shell history. It is not a secure secret store.
This file is read by Docker or Podman. It is not a shell script, which is why you write KC_HOSTNAME=value, not export KC_HOSTNAME=value.
Create a file of KEY=value lines:
cat > keycloak.env << 'EOF'
KC_HOSTNAME=auth.example.com
KC_HEALTH_ENABLED=true
KC_METRICS_ENABLED=true
EOFcat prints nothing when the file is written. Load that file into the container:
podman run --rm --env-file keycloak.env quay.io/keycloak/keycloak:26.7.3 show-configAll three names resolve from the environment:
kc.health-enabled = true (ENV)
kc.metrics-enabled = true (ENV)
kc.hostname = auth.example.com (ENV)docker run --env-file keycloak.env is the same file format. Environment variables still appear in podman inspect / docker inspect. For production secrets, prefer the runtime secret mechanism your orchestrator provides.
Set variables with Docker Compose
Compose maps the same names under environment:. Quoting "true" keeps the Compose value clearly represented as the string passed to Keycloak rather than a YAML boolean:
services:
keycloak:
image: quay.io/keycloak/keycloak:26.7.3
environment:
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: change_me
KC_HEALTH_ENABLED: "true"
command: start-devThat block only shows how Compose spells KC_* variables. A full stack with PostgreSQL, networks, volumes, and health checks belongs in Keycloak Docker Compose with PostgreSQL.
Set variables on a Linux ZIP installation
Keycloak reads KC_* variables from the environment of the process that starts kc.sh. You do not need Docker or Podman to use them.
export makes the variable available to commands started from the current shell, including kc.sh. From the Keycloak install directory used in Install Keycloak on Linux, export the name in the same shell that will run the server, then confirm it resolves:
export KC_HOSTNAME=auth.example.com
bin/kc.sh show-config kc.hostname = auth.example.com (ENV)The hostname line shows (ENV), so the exported variable reached kc.sh. Use bin/kc.sh start from that same shell when you want the server running. An export in one terminal is not inherited by systemd, sudo, or a second shell.
Verify environment variables with show-config
Keycloak may receive configuration from more than one place. show-config prints the resolved configuration: the final value for each option and where that value came from.
Read each line in three pieces:
kc.hostname = auth.example.com (ENV)
↑ ↑ ↑
internal name resolved source
valuekc.hostnameis Keycloak's internal representation of the optionauth.example.comis the value Keycloak will use(ENV)means an environment variable supplied it
You will also see these source labels:
| Suffix | Meaning |
|---|---|
(ENV) |
Environment variable |
(CLI) |
Command-line flag |
(Persisted) |
Value saved during kc.sh build |
(keycloak.conf) or similar file source |
A configuration file |
Sensitive values such as KC_DB_PASSWORD print as *******. The line still confirms the variable is set and came from (ENV).
Use show-config with the same -e / --env-file arguments you plan to use for start. It still does not start the server, open a port, or test that PostgreSQL is reachable.
Keycloak configuration precedence
You can configure the same setting in more than one place, especially when a container image already contains keycloak.conf and your deployment adds environment variables. When that happens, Keycloak must decide which value wins.
CLI vs environment vs keycloak.conf
Both environment variables and keycloak.conf set the same underlying kc.* properties. They differ in precedence, not in meaning. Highest source wins:
CLI
↓ wins over
Environment
↓ wins over
keycloak.conf
↓ wins over
Java KeyStore secrets (advanced configuration source)File paths, ${…} interpolation in keycloak.conf, and KeyStore wiring are covered in the Keycloak keycloak.conf guide.
Example: what happens when the same option is set twice?
Suppose metrics-enabled is configured in three places:
keycloak.confsaysfalseKC_METRICS_ENABLED=true- The CLI says
--metrics-enabled=false
Keycloak uses the CLI value (false). If you remove the flag, the environment value (true) wins over the file.
A live check of CLI versus environment looks like this. Metrics are false in the environment and true on the CLI:
podman run --rm -e KC_METRICS_ENABLED=false \
quay.io/keycloak/keycloak:26.7.3 show-config --metrics-enabled=trueThe (CLI) suffix shows that the command-line value won:
kc.metrics-enabled = true (CLI)Remove the flag or align the values when you want the environment variable to apply.
Common Keycloak environment variables
You do not need to memorize every Keycloak environment variable. The following examples show four common jobs: creating the first administrator, connecting a database, setting Keycloak's public URL, and enabling health or metrics endpoints.
| Task | Variable |
|---|---|
| Bootstrap admin username | KC_BOOTSTRAP_ADMIN_USERNAME |
| Bootstrap admin password | KC_BOOTSTRAP_ADMIN_PASSWORD |
| Database vendor | KC_DB |
| Complete JDBC URL | KC_DB_URL |
| Database username | KC_DB_USERNAME |
| Database password | KC_DB_PASSWORD |
| Public hostname or URL | KC_HOSTNAME |
| HTTP listener | KC_HTTP_ENABLED |
| Health endpoints | KC_HEALTH_ENABLED |
| Metrics endpoints | KC_METRICS_ENABLED |
These are syntax examples. Dedicated articles cover the full database, TLS, and bootstrap procedures.
Bootstrap administrator
On first startup Keycloak can create the initial admin user from the environment:
| Variable | Example |
|---|---|
KC_BOOTSTRAP_ADMIN_USERNAME |
admin |
KC_BOOTSTRAP_ADMIN_PASSWORD |
ChangeMe!2026 |
Older Keycloak releases commonly used KEYCLOAK_ADMIN and KEYCLOAK_ADMIN_PASSWORD. In Keycloak 26, use KC_BOOTSTRAP_ADMIN_USERNAME and KC_BOOTSTRAP_ADMIN_PASSWORD.
These variables bootstrap the initial administrator. They do not reset an existing administrator on every restart. First-start methods and recovery after the first start are covered in Keycloak bootstrap admin.
PostgreSQL database
This does not connect to PostgreSQL. The command below uses show-config only to demonstrate how the database variables are recognized. A working server still needs an actual PostgreSQL instance reachable at the configured host.
| Variable | What it tells Keycloak |
|---|---|
KC_DB |
Database type or vendor |
KC_DB_URL_HOST |
PostgreSQL server hostname |
KC_DB_URL_DATABASE |
Database name |
KC_DB_USERNAME |
Database login username |
KC_DB_PASSWORD |
Database login password |
podman run --rm \
-e KC_DB=postgres \
-e KC_DB_URL_HOST=postgres \
-e KC_DB_URL_DATABASE=keycloak \
-e KC_DB_USERNAME=keycloak \
-e KC_DB_PASSWORD='KcDb!2026' \
quay.io/keycloak/keycloak:26.7.3 show-configshow-config reports the vendor, host, database name, and username. The password prints as *******:
kc.db-username = keycloak (ENV)
kc.db-url-database = keycloak (ENV)
kc.db = postgres (ENV)
kc.db-url-host = postgres (ENV)
kc.db-password = ******* (ENV)Inside the Keycloak container, localhost refers to the Keycloak container itself, not another PostgreSQL container. Use the PostgreSQL container name or Kubernetes Service DNS in KC_DB_URL_HOST when both services run in containers:
Keycloak container ──► postgres:5432
↑
PostgreSQL container nameDatabase creation, networking, PostgreSQL users, and a complete working deployment are covered in Configure Keycloak with PostgreSQL.
Full KC_DB_URL vs separate URL components
There are two ways to give Keycloak the database address.
Complete URL:
KC_DB_URL=jdbc:postgresql://postgres:5432/keycloakOr the same pieces separately:
KC_DB_URL_HOST=postgres
KC_DB_URL_PORT=5432
KC_DB_URL_DATABASE=keycloakChoose one approach. If the complete KC_DB_URL is set, Keycloak ignores the separate URL pieces. show-config can still list both if you set both; that listing does not mean they combine.
Hostname
Users may connect directly to Keycloak, or through a reverse proxy. For environment-variable syntax, start with the public URL:
podman run --rm -e KC_HOSTNAME=https://auth.example.com quay.io/keycloak/keycloak:26.7.3 show-configKeycloak stores that value as kc.hostname from the environment:
kc.hostname = https://auth.example.com (ENV)KC_HOSTNAME can be a hostname or a full URL. Proxy headers, HTTP behind TLS termination, and certificate files depend on how Keycloak is exposed. Those layouts are covered in Keycloak HTTPS/TLS.
Health and metrics
Health endpoints let monitoring systems check whether Keycloak is running and ready. Metrics expose operational measurements that monitoring tools such as Prometheus can collect.
Confirm both resolve from the environment:
podman run --rm \
-e KC_HEALTH_ENABLED=true \
-e KC_METRICS_ENABLED=true \
quay.io/keycloak/keycloak:26.7.3 show-config kc.health-enabled = true (ENV)
kc.metrics-enabled = true (ENV)Both flags resolved from the environment. By default, /health and /metrics are exposed through Keycloak's management interface on port 9000. Enabling the variables does not publish that port on the host. In a container, you must also publish or map the management port if you want to open http://localhost:9000/health from the host.
Keycloak environment variables not working
When a KC_* value does not match what you expect, run show-config with the same -e / --env-file arguments you use for start. The source column (ENV, CLI, keycloak.conf) tells you which layer won.
The KC_ prefix is a mapping system, not a free-form namespace. A logical-looking name is not enough. The underlying option must actually exist.
Variable never entered the container
On a container host, export KC_HOSTNAME=… does not enter the container. Add -e, --env-file, or a Compose environment: entry.
For a running container, podman exec <container> env | grep KC_ shows whether the process has the name you set. If the container exits during startup, use podman inspect <container> to read the configured environment instead.
On a Linux ZIP install, an export in one shell or account is not inherited by another. If you start Keycloak with sudo, systemd, a second terminal, or a different user, set the variables in that process environment instead.
Wrong KC_* name
Confirm the name is a real Keycloak option. A typo such as KC_DB_HOST instead of KC_DB_URL_HOST never maps to db-url-host. Compare the hyphenated option against All configuration and read the Env column.
Older tutorials still show KEYCLOAK_ADMIN / KEYCLOAK_ADMIN_PASSWORD or KC_PROXY=edge. Current Keycloak 26 uses KC_BOOTSTRAP_ADMIN_* for the first admin and KC_PROXY_HEADERS (with hostname options) instead of the removed proxy setting.
KC_* does not make an unsupported option valid
The conversion rule works only for options Keycloak actually defines.
Existing option:
http-relative-pathEnvironment variable:
KC_HTTP_RELATIVE_PATHKC_HOSTNAME_PATH looks related to URLs, but it is not a documented Keycloak option. show-config output alone does not prove that a guessed KC_* name corresponds to a supported Keycloak server option. Verify the original option in All configuration.
CLI option overrides the environment variable
Command-line flags beat environment variables for the same option. If show-config prints (CLI) for a setting you also passed with -e, remove the flag or align the values.
Bootstrap admin variables do not reset an existing administrator
KC_BOOTSTRAP_ADMIN_USERNAME and KC_BOOTSTRAP_ADMIN_PASSWORD create the first administrator only during initialization of Keycloak's built-in administrative realm (master). If master already exists, those values do not recreate or reset an administrator. Use the recovery procedure in Reset Keycloak admin password after the first start.
Build-time option appears ignored
KC_DB, KC_HEALTH_ENABLED, and KC_METRICS_ENABLED are classified as build-time options. With a normal start, Keycloak can apply them through an implicit build, so setting them on podman run often appears to work.
With a pre-built optimized image and start --optimized, those values must already match what was used at build time. Changing them only on podman run does not rebuild the server.
Password containing $ changes
Standard KC_* values run through expression evaluation. A password that contains $ or ${…} can change silently. On Keycloak 26.6 or later, switch that secret to KCRAW_DB_PASSWORD and do not set KC_DB_PASSWORD at the same time.
Build-time vs runtime environment variables
Some Keycloak settings change the server installation itself. Keycloak needs those values while preparing or building the server. Other settings, such as the database password or hostname, can be supplied whenever the already-built server starts.
BUILD TIME
Create/prepare Keycloak installation
│
├── KC_DB
├── KC_HEALTH_ENABLED
└── KC_METRICS_ENABLED
│
▼
Built server
│
│ runtime settings
├── KC_DB_URL
├── KC_DB_USERNAME
├── KC_DB_PASSWORD
└── KC_HOSTNAME
▼
Running Keycloak| Example | Type | Typical stage |
|---|---|---|
KC_DB |
Build-time | image / build |
KC_HEALTH_ENABLED |
Build-time | image / build |
KC_METRICS_ENABLED |
Build-time | image / build |
KC_DB_URL_HOST |
Runtime | container start |
KC_DB_USERNAME |
Runtime | container start |
KC_DB_PASSWORD |
Runtime | container start |
KC_HOSTNAME |
Runtime | container start |
What build-time actually means
Build-time options change how the Keycloak server installation itself is prepared, for example which database driver is included. Runtime options supply values that can vary between deployments, such as the address and credentials of the PostgreSQL server.
From a Keycloak install directory, an explicit build looks like this:
KC_DB=postgres bin/kc.sh buildA successful build reports that the configuration was updated and persisted:
Server configuration updated and persisted. Run the following command to review the configuration:
kc.sh show-configshow-config then reports the vendor as persisted, for example kc.db = postgres (Persisted).
Runtime variables such as KC_DB_PASSWORD and KC_DB_URL_HOST belong on start or start-dev, not in the build layer. Build metadata is stored in plain text inside the image; never bake secrets into build.
Why KC_DB=postgres often appears in docker run
KC_DB is classified by Keycloak as a build-time option. With a normal non-optimized start, Keycloak can perform the necessary build automatically, which is why you often see KC_DB=postgres supplied when starting a container.
That is also why the PostgreSQL show-config example earlier could pass -e KC_DB=postgres without an explicit kc.sh build.
What changes with start --optimized
With a pre-built optimized image and start --optimized, the database vendor must already match what was used at build time. start --optimized skips the implicit build. If you change a build-time value later and start with --optimized, Keycloak warns that the new value will not apply until you rebuild.
Multi-stage Containerfiles and start --optimized are covered in Build an optimized Keycloak container.
Advanced: Preserve $ characters with KCRAW_*
Most values should use normal KC_* variables. One special case occurs when a password or other secret contains dollar signs ($).
A password such as pa$$word${abc} is the problem. Keycloak normally performs expression processing on environment-variable values, so the value it receives may not remain exactly as written:
KC_DB_PASSWORD=pa$$word${abc}
│
▼ expression processing may alter $ and ${…}
KCRAW_DB_PASSWORD=pa$$word${abc}
│
▼ keep the characters literallyKCRAW_* is available in Keycloak 26.6 and later. Use that prefix for any environment variable whose value must be preserved exactly as written.
When KC_DB_PASSWORD is enough
For passwords without $ characters, KC_DB_PASSWORD is enough. Hostnames, ports, and feature flags also stay on the KC_ prefix.
When to use KCRAW_DB_PASSWORD
Use KCRAW_DB_PASSWORD when the secret contains $ or ${ sequences your orchestrator injects verbatim.
| Prefix | Expression handling | Typical use |
|---|---|---|
KC_ |
${var} expanded; $$ collapsed |
Hostnames, ports, feature flags |
KCRAW_ |
$ characters escaped automatically |
Database passwords, API keys with $ |
You cannot set both KC_DB_PASSWORD and KCRAW_DB_PASSWORD for the same key. Keycloak fails at startup with a configuration error when both are present.
Advanced: Map unusual option names with KCKEY_*
Most users never need KCKEY_*. Skip this section unless you are configuring a key whose name contains characters that cannot be mapped cleanly to the normal KC_* form, most commonly logging categories.
Normally Keycloak can reconstruct the original option name from the environment variable. Converting _ back to - or . can become ambiguous for those keys. Logging categories often use dots in Java package names, so an underscore in the category can map to a dot instead of a dash. KC_LOG_LEVEL_PACKAGE_CLASS_NAME may therefore target log-level-package.class.name rather than log-level-package.class_name.
KCKEY_* lets you tell Keycloak which configuration key an environment variable represents. Pair a value variable with a KCKEY_* companion that names the real option:
| Variable | Role |
|---|---|
KC_MYKEY=debug |
Holds the value |
KCKEY_MYKEY=log-level-package.class_name |
Names the exact Keycloak key |
Confirm the mapping with show-config:
podman run --rm \
-e KC_MYKEY=debug \
-e KCKEY_MYKEY=log-level-package.class_name \
quay.io/keycloak/keycloak:26.7.3 show-config kc.log-level-package.class_name = debug (ENV)You only need this pattern for awkward keys. Normal options such as KC_DB_URL_HOST do not require KCKEY_*.
Summary
Keycloak environment variables are NAME=value settings whose names normally begin with KC_. The same server option can be a CLI flag, a keycloak.conf line, or an environment variable. For ordinary options, uppercase the hyphenated name, replace hyphens with underscores, and add KC_. Confirm unknown names in All configuration instead of inventing a plausible KC_* string.
Pass variables with podman run -e or docker run -e for containers, --env-file for a list of names, a Compose environment: block, or export before kc.sh on a Linux ZIP install. Host export does not enter a container. show-config prints the resolved value and its source (ENV, CLI, Persisted) without starting the server or testing PostgreSQL.
Environment variables outrank keycloak.conf but lose to CLI flags. For PostgreSQL, pass KC_DB=postgres plus either a full KC_DB_URL or host-style KC_DB_URL_* components, not both. On a normal start, Keycloak can apply build-time options such as KC_DB through an implicit build. With start --optimized, those values should already be baked in. Use KCRAW_* when secrets contain $ characters, and KCKEY_* only when underscore mapping would hit the wrong key.
References
- Configuring Keycloak — precedence,
KC_*/KCRAW_*/KCKEY_*formats - All configuration — searchable option to environment mapping
- Running Keycloak in a container — recommended container env patterns
- Configuring the hostname (v2) —
hostnameand related options
Frequently Asked Questions
Does Keycloak still read KEYCLOAK_ADMIN?
Older releases commonly used KEYCLOAK_ADMIN and KEYCLOAK_ADMIN_PASSWORD. In Keycloak 26, use KC_BOOTSTRAP_ADMIN_USERNAME and KC_BOOTSTRAP_ADMIN_PASSWORD for the first administrator. Those values bootstrap the initial administrator; they do not reset an existing administrator on later startups.
Can I mix KC_* variables and keycloak.conf?
Yes, for different options. For the same option, precedence picks one winner: environment beats the file unless a CLI flag overrides both.
Is KC_DB a runtime or build-time variable?
Database vendor (KC_DB=postgres) is a build-time option. A normal start without --optimized can apply it through an implicit build, which is why container examples often set KC_DB=postgres at start. On an optimized image started with --optimized, set KC_DB during kc.sh build. JDBC host, database name, username, and password are runtime options on start.
Can I set KC_DB_URL and KC_DB_URL_HOST together?
Do not combine them expecting the host, port, or database variables to edit the JDBC URL. When KC_DB_URL is set, Keycloak ignores the URL-component options.
Why does show-config mask my password?
Sensitive values such as KC_DB_PASSWORD print as asterisks by design. The line still confirms the variable is set and came from ENV.
When do I need KCRAW_* instead of KC_*?
Use KCRAW_ on Keycloak 26.6 and later when the value must keep dollar signs exactly as written, typically a password injected by a secrets manager. Do not set both prefixes for the same key.

