Optimized Keycloak build
Tags: keycloak, kubernetes, dockerfile, iam
Published: 2024-03-31 22:26:50

Keycloak provides a documentation on how is to [configure Keycloak](https://www.keycloak.org/server/configuration) the proper way in Kubernetes. I wouldn't repeat the documentation so please make sure you go through it. 


The most important part when said:
> We recommend optimizing Keycloak to provide faster startup and better memory consumption before deploying Keycloak in a production environment. This section describes how to apply Keycloak optimizations for the best performance and runtime behavior - [Optimize the startup](https://www.keycloak.org/server/configuration#_optimize_the_keycloak_startup)

For that to happen, let us write our Dockerfile

## The Dockerfile 
Let us have a look to the entire Dockerfile before we go into each Dockefile layer. 

```DOCKERFILE
ARG KC_VERSION

FROM quay.io/keycloak/keycloak:${KC_VERSION} AS builder

ARG KC_HEALTH_ENABLED=true
ARG KC_METRICS_ENABLED=true
ARG KC_DB=postgres
ARG KC_FEATURES
ARG KC_METRICS_SPI_VERSION
ARG KC_HOSTNAME_PATH=keycloak
ARG KC_HTTP_RELATIVE_PATH=/keycloak

ENV KC_VERSION=${KC_VERSION}
ENV KC_HEALTH_ENABLED=${KC_HEALTH_ENABLED}
ENV KC_DB=${KC_DB}
ENV KC_HOSTNAME_PATH=${KC_HOSTNAME_PATH}
ENV KC_HTTP_RELATIVE_PATH=${KC_HTTP_RELATIVE_PATH}
ENV KC_FEATURES=${KC_FEATURES}

ADD --chown=keycloak:keycloak https://github.com/aerogear/keycloak-metrics-spi/releases/download/${KC_METRICS_SPI_VERSION}/keycloak-metrics-spi-${KC_METRICS_SPI_VERSION}.jar /opt/keycloak/providers/keycloak-metrics-spi-${KC_METRICS_SPI_VERSION}.jar

RUN /opt/keycloak/bin/kc.sh build --cache-stack=kubernetes

# Installing additional RPM packages https://www.keycloak.org/server/containers
FROM registry.access.redhat.com/ubi9:9.3 AS ubi-micro-build
RUN mkdir -p /mnt/rootfs
RUN dnf install -y curl-7.76.1 --installroot /mnt/rootfs  --releasever 9 --setopt install_weak_deps=false --nodocs && \
    dnf --installroot /mnt/rootfs clean all && \
    rpm --root /mnt/rootfs -e --nodeps setup

FROM quay.io/keycloak/keycloak:${KC_VERSION}

COPY --from=builder /opt/keycloak/ /opt/keycloak/
COPY --from=ubi-micro-build /mnt/rootfs /
COPY ./entrypoint.sh /deployments/entrypoint.sh

ENTRYPOINT ["/deployments/entrypoint.sh"]

```
### Build Keycloak
For maintainability, parameterize the Keycloak version so it is easier from the build tool (CI/CD) to upgrade Keycloak without touching the Dockerfile. Below is the first stage in our multi-stage Dockerfile defined as `builder`
```DOCKERFILE
ARG KC_VERSION

FROM quay.io/keycloak/keycloak:${KC_VERSION} AS builder
```

#### Arguments 

Here is the Dockerfile arguments. 
```Dockerfile
ARG KC_HEALTH_ENABLED=true
ARG KC_METRICS_ENABLED=true
ARG KC_DB=postgres
ARG KC_FEATURES
ARG KC_METRICS_SPI_VERSION
ARG KC_HOSTNAME_PATH=keycloak
ARG KC_HTTP_RELATIVE_PATH=/keycloak
```
- `KC_HEALTH_ENABLED` and `KC_METRICS_ENABLED`: Both are important for monitoring Keycloak. I enabled them by default.
- `KC_DB`: For the target database provider. I consider `postgres` database. 
- `KC_FEATURES`: To control the enabling features in Keycloak from CI/CD. 
- `KC_METRICS_SPI_VERSION`: If `KC_METRICS_ENABLED` is true, the target version of the [Keycloak Metrics SPI](https://github.com/aerogear/keycloak-metrics-spi). Keycloak Metrics SPI is not an official metrics from Keycloak but a well known SPI. It worth to check and decide about it. 
- `KC_HOSTNAME_PATH` and `KC_HTTP_RELATIVE_PATH` are required to be added by the same name if you choose different context path for Keycloak than the default `/`. In my case, I choose `/keycloak` as context path.
 
#### Prepare the environment variables 
```Dockerfile
ENV KC_VERSION=${KC_VERSION}
ENV KC_HEALTH_ENABLED=${KC_HEALTH_ENABLED}
ENV KC_DB=${KC_DB}
ENV KC_HOSTNAME_PATH=${KC_HOSTNAME_PATH}
ENV KC_HTTP_RELATIVE_PATH=${KC_HTTP_RELATIVE_PATH}
ENV KC_FEATURES=${KC_FEATURES}
```
Keycloak uses the environment variables during the build to read from and act based on it. In the above snippet, the required environment variables for our build added. 

#### Prepare the SPIs
```Dockerfile
ADD --chown=keycloak:keycloak https://github.com/aerogear/keycloak-metrics-spi/releases/download/${KC_METRICS_SPI_VERSION}/keycloak-metrics-spi-${KC_METRICS_SPI_VERSION}.jar /opt/keycloak/providers/keycloak-metrics-spi-${KC_METRICS_SPI_VERSION}.jar
```
A example build step that downloads a JAR file from a URL and adds it to the providers directory

#### Build Keycloak 
```Dockerfile
RUN /opt/keycloak/bin/kc.sh build --cache-stack=kubernetes
```
This is the most important step where we build Keycloak based on the environment variables we provided in the build step and cache stack as `kubernetes`. 

#### Install additional RPM packages 
```Dockerfile
# Installing additional RPM packages https://www.keycloak.org/server/containers
FROM registry.access.redhat.com/ubi9:9.3 AS ubi-micro-build
RUN mkdir -p /mnt/rootfs
RUN dnf install -y curl-7.76.1 --installroot /mnt/rootfs  --releasever 9 --setopt install_weak_deps=false --nodocs && \
    dnf --installroot /mnt/rootfs clean all && \
    rpm --root /mnt/rootfs -e --nodeps setup
```
This step is not necessary, although if you need to have additional RPM packages in the container, you can install it as shown in the above example for installing `curl`. 

#### Final image.
```Dockerfile
FROM quay.io/keycloak/keycloak:${KC_VERSION}

COPY --from=builder /opt/keycloak/ /opt/keycloak/
COPY --from=ubi-micro-build /mnt/rootfs /
COPY ./entrypoint.sh /deployments/entrypoint.sh
```
The final stage is our image where Keycloak build files are pre-installed and ready to start up. 
- From the `builder` stage, we copy the Keycloak build
- From the `ubi-micro-build` stage, we copy the installed packages
- `entrypoint.sh` is our custom entrypoint file to start Keycloak. In its basic form can be as simple as below:
```shell
exec /opt/keycloak/bin/kc.sh start --optimized
```
I prefer to have the entrypoint in separate file so later I have the flexibility to customize it without modifying the Dockerfile. _Indeed I use additional scripts in the entrypoint. For instance, integrate Datadog with Keycloak. I can write a separate article to describe it ;)_

--- 
Keycloak is an identity provider which provides the front-end for the users same as admins. Someone would think to narrow the exposed part of Keycloak to only what necessary public and keep everything else to be accessed only through VPN / Internal network. 
In the next blog post, I describe how you can [separate Keycloak public and private APIs/URLs](https://dev.to/mohammedalics/securing-keycloak-configuring-admin-access-within-your-private-network-4f1g) for an enhanced security. 

I hope you find it useful.