Chapter 5. Running Red Hat build of Keycloak in a container This chapter describes how to optimize and run the Red Hat build of Keycloak container image to provide the best experience running a container. Warning This chapter applies only for building an image that you run in a OpenShift environment. Only an OpenShift environment is supported for this image. It is not supported if you run it in other Kubernetes distributions. 5.1. Creating a customized and optimized container image The default Red Hat build of Keycloak container image ships ready to be configured and optimized. For the best start up of your Red Hat build of Keycloak container, build an image by running the build step during the container build. This step will save time in every subsequent start phase of the container image. 5.1.1. Writing your optimized Red Hat build of Keycloak Containerfile The following Containerfile creates a pre-configured Red Hat build of Keycloak image that enables the health and metrics endpoints, enables the token exchange feature, and uses a PostgreSQL database. Containerfile: FROM registry.redhat.io/rhbk/keycloak-rhel9:26 as builder # Enable health and metrics support ENV KC_HEALTH_ENABLED=true ENV KC_METRICS_ENABLED=true # Configure a database vendor ENV KC_DB=postgres WORKDIR /opt/keycloak # for demonstration purposes only, please make sure to use proper certificates in production instead RUN keytool -genkeypair -storepass password -storetype PKCS12 -keyalg RSA -keysize 2048 -dname "CN=server" -alias server -ext "SAN:c=DNS:localhost,IP:127.0.0.1" -keystore conf/server.keystore RUN /opt/keycloak/bin/kc.sh build FROM registry.redhat.io/rhbk/keycloak-rhel9:26 COPY --from=builder /opt/keycloak/ /opt/keycloak/ # change these values to point to a running postgres instance ENV KC_DB=postgres ENV KC_DB_URL=<DBURL> ENV KC_DB_USERNAME=<DBUSERNAME> ENV KC_DB_PASSWORD=<DBPASSWORD> ENV KC_HOSTNAME=localhost ENTRYPOINT ["/opt/keycloak/bin/kc.sh"] The build process includes multiple stages: Run the build command to set server build options to create an optimized image. The files generated by the build stage are copied into a new image. In the final image, additional configuration options for the hostname and database are set so that you don't need to set them again when running the container. In the entrypoint, the kc.sh enables access to all the distribution sub-commands. To install custom providers, you just need to define a step to include the JAR file(s) into the /opt/keycloak/providers directory. This step must be placed before the line that RUNs the build command, as below: # A example build step that downloads a JAR file from a URL and adds it to the providers directory FROM registry.redhat.io/rhbk/keycloak-rhel9:26 as builder ... # Add the provider JAR file to the providers directory ADD --chown=keycloak:keycloak --chmod=644 <MY_PROVIDER_JAR_URL> /opt/keycloak/providers/myprovider.jar ... # Context: RUN the build command RUN /opt/keycloak/bin/kc.sh build 5.1.2. Installing additional RPM packages If you try to install new software in a stage FROM registry.redhat.io/rhbk/keycloak-rhel9 , you will notice that microdnf , dnf , and even rpm are not installed. Also, very few packages are available, only enough for a bash shell, and to run Red Hat build of Keycloak itself. This is due to security hardening measures, which reduce the attack surface of the Red Hat build of Keycloak container. First, consider if your use case can be implemented in a different way, and so avoid installing new RPMs into the final container: A RUN curl instruction in your Containerfile can be replaced with ADD , since that instruction natively supports remote URLs. Some common CLI tools can be replaced by creative use of the Linux filesystem. For example, ip addr show tap0 becomes cat /sys/class/net/tap0/address Tasks that need RPMs can be moved to a former stage of an image build, and the results copied across instead. Here is an example. Running update-ca-trust in a former build stage, then copying the result forward: FROM registry.access.redhat.com/ubi9 AS ubi-micro-build COPY mycertificate.crt /etc/pki/ca-trust/source/anchors/mycertificate.crt RUN update-ca-trust FROM registry.redhat.io/rhbk/keycloak-rhel9 COPY --from=ubi-micro-build /etc/pki /etc/pki It is possible to install new RPMs if absolutely required, following this two-stage pattern established by ubi-micro: FROM registry.access.redhat.com/ubi9 AS ubi-micro-build RUN mkdir -p /mnt/rootfs RUN dnf install --installroot /mnt/rootfs <package names go here> --releasever 9 --setopt install_weak_deps=false --nodocs -y && \ dnf --installroot /mnt/rootfs clean all && \ rpm --root /mnt/rootfs -e --nodeps setup FROM registry.redhat.io/rhbk/keycloak-rhel9 COPY --from=ubi-micro-build /mnt/rootfs / This approach uses a chroot, /mnt/rootfs , so that only the packages you specify and their dependencies are installed, and so can be easily copied into the second stage without guesswork. Warning Some packages have a large tree of dependencies. By installing new RPMs you may unintentionally increase the container's attack surface. Check the list of installed packages carefully. 5.1.3. Building the container image To build the actual container image, run the following command from the directory containing your Containerfile: podman build . -t mykeycloak 5.1.4. Starting the optimized Red Hat build of Keycloak container image To start the image, run: podman run --name mykeycloak -p 8443:8443 -p 9000:9000 \ -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me \ mykeycloak \ start --optimized --hostname=localhost Red Hat build of Keycloak starts in production mode, using only secured HTTPS communication, and is available on https://localhost:8443 . Health check endpoints are available at https://localhost:9000/health , https://localhost:9000/health/ready and https://localhost:9000/health/live . Opening up https://localhost:9000/metrics leads to a page containing operational metrics that could be used by your monitoring solution. 5.2. Exposing the container to a different port By default, the server is listening for http and https requests using the ports 8080 and 8443 , respectively. If you want to expose the container using a different port, you need to set the hostname accordingly: Exposing the container using a port other than the default ports By setting the hostname option to a full url you can now access the server at https://localhost:3000 . 5.3. Trying Red Hat build of Keycloak in development mode The easiest way to try Red Hat build of Keycloak from a container for development or testing purposes is to use the Development mode. You use the start-dev command: podman run --name mykeycloak -p 8080:8080 \ -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me \ registry.redhat.io/rhbk/keycloak-rhel9:26 \ start-dev Invoking this command starts the Red Hat build of Keycloak server in development mode. This mode should be strictly avoided in production environments because it has insecure defaults. For more information about running Red Hat build of Keycloak in production, see Configuring Red Hat build of Keycloak for production . 5.4. Running a standard Red Hat build of Keycloak container In keeping with concepts such as immutable infrastructure, containers need to be re-provisioned routinely. In these environments, you need containers that start fast, therefore you need to create an optimized image as described in the preceding section. However, if your environment has different requirements, you can run a standard Red Hat build of Keycloak image by just running the start command. For example: podman run --name mykeycloak -p 8080:8080 \ -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me \ registry.redhat.io/rhbk/keycloak-rhel9:26 \ start \ --db=postgres --features=token-exchange \ --db-url=<JDBC-URL> --db-username=<DB-USER> --db-password=<DB-PASSWORD> \ --https-key-store-file=<file> --https-key-store-password=<password> Running this command starts a Red Hat build of Keycloak server that detects and applies the build options first. In the example, the line --db=postgres --features=token-exchange sets the database vendor to PostgreSQL and enables the token exchange feature. Red Hat build of Keycloak then starts up and applies the configuration for the specific environment. This approach significantly increases startup time and creates an image that is mutable, which is not the best practice. 5.5. Provide initial admin credentials when running in a container Red Hat build of Keycloak only allows to create the initial admin user from a local network connection. This is not the case when running in a container, so you have to provide the following environment variables when you run the image: # setting the admin username -e KC_BOOTSTRAP_ADMIN_USERNAME=<admin-user-name> # setting the initial password -e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me 5.6. Importing A Realm On Startup The Red Hat build of Keycloak containers have a directory /opt/keycloak/data/import . If you put one or more import files in that directory via a volume mount or other means and add the startup argument --import-realm , the Red Hat build of Keycloak container will import that data on startup! This may only make sense to do in Dev mode. podman run --name keycloak_unoptimized -p 8080:8080 \ -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me \ -v /path/to/realm/data:/opt/keycloak/data/import \ registry.redhat.io/rhbk/keycloak-rhel9:26 \ start-dev --import-realm Feel free to join the open GitHub Discussion around enhancements of the admin bootstrapping process. 5.7. Specifying different memory settings The Red Hat build of Keycloak container, instead of specifying hardcoded values for the initial and maximum heap size, uses relative values to the total memory of a container. This behavior is achieved by JVM options -XX:MaxRAMPercentage=70 , and -XX:InitialRAMPercentage=50 . The -XX:MaxRAMPercentage option represents the maximum heap size as 70% of the total container memory. The -XX:InitialRAMPercentage option represents the initial heap size as 50% of the total container memory. These values were chosen based on a deeper analysis of Red Hat build of Keycloak memory management. As the heap size is dynamically calculated based on the total container memory, you should always set the memory limit for the container. Previously, the maximum heap size was set to 512 MB, and in order to approach similar values, you should set the memory limit to at least 750 MB. For smaller production-ready deployments, the recommended memory limit is 2 GB. The JVM options related to the heap might be overridden by setting the environment variable JAVA_OPTS_KC_HEAP . You can find the default values of the JAVA_OPTS_KC_HEAP in the source code of the kc.sh , or kc.bat script. For example, you can specify the environment variable and memory limit as follows: podman run --name mykeycloak -p 8080:8080 -m 1g \ -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me \ -e JAVA_OPTS_KC_HEAP="-XX:MaxHeapFreeRatio=30 -XX:MaxRAMPercentage=65" \ registry.redhat.io/rhbk/keycloak-rhel9:26 \ start-dev Warning If the memory limit is not set, the memory consumption rapidly increases as the heap size can grow up to 70% of the total container memory. Once the JVM allocates the memory, it is returned to the OS reluctantly with the current Red Hat build of Keycloak GC settings. 5.8. Relevant options Value db 🛠 The database vendor. CLI: --db Env: KC_DB dev-file (default), dev-mem , mariadb , mssql , mysql , oracle , postgres db-password The password of the database user. CLI: --db-password Env: KC_DB_PASSWORD db-url The full database JDBC URL. If not provided, a default URL is set based on the selected database vendor. For instance, if using postgres , the default JDBC URL would be jdbc:postgresql://localhost/keycloak . CLI: --db-url Env: KC_DB_URL db-username The username of the database user. CLI: --db-username Env: KC_DB_USERNAME features 🛠 Enables a set of one or more features. CLI: --features Env: KC_FEATURES account-api[:v1] , account[:v3] , admin-api[:v1] , admin-fine-grained-authz[:v1] , admin[:v2] , authorization[:v1] , cache-embedded-remote-store[:v1] , ciba[:v1] , client-policies[:v1] , client-secret-rotation[:v1] , client-types[:v1] , clusterless[:v1] , declarative-ui[:v1] , device-flow[:v1] , docker[:v1] , dpop[:v1] , dynamic-scopes[:v1] , fips[:v1] , hostname[:v2] , impersonation[:v1] , kerberos[:v1] , login[:v2,v1] , multi-site[:v1] , oid4vc-vci[:v1] , opentelemetry[:v1] , organization[:v1] , par[:v1] , passkeys[:v1] , persistent-user-sessions[:v1] , preview , recovery-codes[:v1] , scripts[:v1] , step-up-authentication[:v1] , token-exchange[:v1] , transient-users[:v1] , update-email[:v1] , web-authn[:v1] hostname Address at which is the server exposed. Can be a full URL, or just a hostname. When only hostname is provided, scheme, port and context path are resolved from the request. CLI: --hostname Env: KC_HOSTNAME Available only when hostname:v2 feature is enabled https-key-store-file The key store which holds the certificate information instead of specifying separate files. CLI: --https-key-store-file Env: KC_HTTPS_KEY_STORE_FILE https-key-store-password The password of the key store file. CLI: --https-key-store-password Env: KC_HTTPS_KEY_STORE_PASSWORD password (default) health-enabled 🛠 If the server should expose health check endpoints. If enabled, health checks are available at the /health , /health/ready and /health/live endpoints. CLI: --health-enabled Env: KC_HEALTH_ENABLED true , false (default) metrics-enabled 🛠 If the server should expose metrics. If enabled, metrics are available at the /metrics endpoint. CLI: --metrics-enabled Env: KC_METRICS_ENABLED true , false (default)

Commands:
FROM registry.redhat.io/rhbk/keycloak-rhel9:26 as builder Enable health and metrics support ENV KC_HEALTH_ENABLED=true ENV KC_METRICS_ENABLED=true Configure a database vendor ENV KC_DB=postgres WORKDIR /opt/keycloak for demonstration purposes only, please make sure to use proper certificates in production instead RUN keytool -genkeypair -storepass password -storetype PKCS12 -keyalg RSA -keysize 2048 -dname "CN=server" -alias server -ext "SAN:c=DNS:localhost,IP:127.0.0.1" -keystore conf/server.keystore RUN /opt/keycloak/bin/kc.sh build FROM registry.redhat.io/rhbk/keycloak-rhel9:26 COPY --from=builder /opt/keycloak/ /opt/keycloak/ change these values to point to a running postgres instance ENV KC_DB=postgres ENV KC_DB_URL=<DBURL> ENV KC_DB_USERNAME=<DBUSERNAME> ENV KC_DB_PASSWORD=<DBPASSWORD> ENV KC_HOSTNAME=localhost ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
A example build step that downloads a JAR file from a URL and adds it to the providers directory FROM registry.redhat.io/rhbk/keycloak-rhel9:26 as builder Add the provider JAR file to the providers directory ADD --chown=keycloak:keycloak --chmod=644 <MY_PROVIDER_JAR_URL> /opt/keycloak/providers/myprovider.jar Context: RUN the build command RUN /opt/keycloak/bin/kc.sh build
FROM registry.access.redhat.com/ubi9 AS ubi-micro-build COPY mycertificate.crt /etc/pki/ca-trust/source/anchors/mycertificate.crt RUN update-ca-trust FROM registry.redhat.io/rhbk/keycloak-rhel9 COPY --from=ubi-micro-build /etc/pki /etc/pki
FROM registry.access.redhat.com/ubi9 AS ubi-micro-build RUN mkdir -p /mnt/rootfs RUN dnf install --installroot /mnt/rootfs <package names go here> --releasever 9 --setopt install_weak_deps=false --nodocs -y && dnf --installroot /mnt/rootfs clean all && rpm --root /mnt/rootfs -e --nodeps setup FROM registry.redhat.io/rhbk/keycloak-rhel9 COPY --from=ubi-micro-build /mnt/rootfs /
build . -t mykeycloak
run --name mykeycloak -p 8443:8443 -p 9000:9000 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me mykeycloak start --optimized --hostname=localhost
run --name mykeycloak -p 3000:8443 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me mykeycloak start --optimized --hostname=https://localhost:3000
run --name mykeycloak -p 8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me registry.redhat.io/rhbk/keycloak-rhel9:26 start-dev
run --name mykeycloak -p 8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me registry.redhat.io/rhbk/keycloak-rhel9:26 start --db=postgres --features=token-exchange --db-url=<JDBC-URL> --db-username=<DB-USER> --db-password=<DB-PASSWORD> --https-key-store-file=<file> --https-key-store-password=<password>
setting the admin username -e KC_BOOTSTRAP_ADMIN_USERNAME=<admin-user-name> setting the initial password -e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me
run --name keycloak_unoptimized -p 8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me -v /path/to/realm/data:/opt/keycloak/data/import registry.redhat.io/rhbk/keycloak-rhel9:26 start-dev --import-realm
run --name mykeycloak -p 8080:8080 -m 1g -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me -e JAVA_OPTS_KC_HEAP="-XX:MaxHeapFreeRatio=30 -XX:MaxRAMPercentage=65" registry.redhat.io/rhbk/keycloak-rhel9:26 start-dev