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.
A Containerfile is functionally identical to a Dockerfile and uses the same syntax.
The term "Containerfile" is used to be more tool-agnostic, especially in non-Docker environments like Podman or Buildah.
When using Docker, you have two options: either name your file `Dockerfile` (which Docker expects by default), or keep the name `Containerfile` and specify it explicitly using the `-f` flag:
The following `Containerfile` creates a pre-configured {project_name} image that enables the health and metrics endpoints, enables the token exchange feature, and uses a PostgreSQL database.
* 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.
If you try to install new software in a stage `+FROM quay.io/keycloak/keycloak+`, 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 {project_name} itself. This is due to security hardening measures, which reduce the attack surface of the {project_name} container.
* 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:
[source, dockerfile]
----
FROM registry.access.redhat.com/ubi9 AS ubi-micro-build
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.
The {project_name} container image uses OpenJDK {jdk_version_recommended} by default, which is the recommended and tested version.
If a custom extension does not support OpenJDK {jdk_version_recommended}, you can use an older supported OpenJDK version by installing the appropriate OpenJDK RPM and updating the `PATH` and `JAVA_HOME` environment variables.
Use the two-stage pattern from the previous section to install the OpenJDK RPM.
The following example shows how to use OpenJDK 21:
[source, dockerfile]
----
FROM registry.access.redhat.com/ubi9 AS ubi-micro-build
If you use a custom entry point script, start {project_name} with `exec` so it can receive termination signals that are essential for a graceful shutdown.
.Correct approach for an ENTRYPOINT shell script
[source,bash]
----
#!/bin/bash
# (add your custom logic here)
# Run the 'exec' command as the last step of the script.
# As it replaces the current shell process, no additional shell commands will run after the 'exec' command.
exec /opt/keycloak/bin/kc.sh start "$@"
----
[WARNING]
====
Without `exec`, the shell script remains PID 1 in the container and blocks signals like `SIGTERM` from reaching {project_name}.
This prevents a graceful shutdown and can lead to cache inconsistencies or data loss.
Health check endpoints are available at `https://localhost:9000/health`, `https://localhost:9000/health/ready` and `https://localhost:9000/health/live`.
* If a `RUN dnf install` command seems to be taking an excessive amount of time, then likely your Docker systemd service has the file limit setting `LimitNOFILE` configured incorrectly.
Either update the service configuration to use a better value, such as 1024000, or use `--ulimit` in the https://docs.docker.com/reference/cli/docker/buildx/build/#ulimit[docker build command], e.g. `--ulimit nofile=1024000`.
* If you are including provider JARs and your container fails a `start --optimized` with a notification that a provider JAR has changed, this is due to Docker truncating
{project_name} 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:
The {project_name} 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 {project_name} container will import that data on startup! This may only make sense to do in Dev mode.
Feel free to join the open https://github.com/keycloak/keycloak/discussions/8549[GitHub Discussion] around enhancements of the admin bootstrapping process.
The {project_name} 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.