Running Keycloak 17+ as Docker Container
Keycloak 17+ is not based on Wildfly anymore but uses Quarkus. This makes it a first-class citizen for running it as a Docker container. Quarkus reduces the startup time of Keycloak massively and reduces its memory footprint. Previously a Keycloak Docker container based on Wildfly consumed around ~800 MB RAM and took roughly 30 seconds to be up and running. Keycloak 17+ (Codename Keycloak.X) changes this by consuming ~300MB RAM and starts almost instantly.
Therefore I was super keen on giving the new Keycloak setup a try and run it on my local machine as a Docker container. This article is about my failures and the eventual success.
My main goal is to run Keycloak, as mentioned as a Docker container connected to a MariaDB database. As mentioned in the Keycloak documentation it is highly recommended to build your own optimised Keycloak Docker image.
For the best start up of your 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.
Easy, let's give it a try
So I started with a MariaDB and a optimised Keycloak Docker image, that listens on http port 8080
. In my first attempt I build the optimised Keycloak image using the following Dockerfile
.
FROM quay.io/keycloak/keycloak:18.0.0 as builder
ENV KC_DB=mariadb
RUN /opt/keycloak/bin/kc.sh build
FROM quay.io/keycloak/keycloak:18.0.0
COPY --from=builder /opt/keycloak/ /opt/keycloak/
WORKDIR /opt/keycloak
ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "start"]
In the first phase I use the base image quay.io/keycloak/keycloak:18.0.0
to build my optimised Keycloak setup, declaring that I want to use MariaDB. In the second stage I pack the optimised output of the builder
stage and put it into my Docker image.
docker build --no-cache . -t ghcr.io/saw303/zscsupporter-be/keycloak-18.0.0:0.0.1
Then I setup a Docker composition declaring a reverse proxy (Caddy), my Keycloak image and the MariaDB.
version: "3.9"
services:
proxy:
image: caddy:2.5.1-alpine
ports:
- "${PROXY_IP}:80:80"
- "${PROXY_IP}:443:443"
volumes:
- ${BASE_PATH:-.}/docker-volume/caddy/Caddyfile:/etc/caddy/Caddyfile:Z
- ${BASE_PATH:-.}/docker-volume/caddy/caddy_data:/data:Z
- ${BASE_PATH:-.}/docker-volume/caddy/caddy_config:/config:Z
keycloak:
image: ghcr.io/saw303/zscsupporter-be/keycloak-18.0.0:0.0.1
ports:
- "127.0.0.1:9001:8080"
- "127.0.0.1:9443:8443"
environment:
KC_HOSTNAME: localhost
KC_HOSTNAME_PORT: 80
KC_HOSTNAME_STRICT_BACKCHANNEL: true
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
KC_DB_URL: jdbc:mariadb://keycloakdb:3306/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: secret
KC_LOG_LEVEL: info
KC_PROXY: edge
keycloakdb:
image: mariadb:10.7.3-focal
environment:
MYSQL_ROOT_PASSWORD: root_secret
MYSQL_DATABASE: keycloak
MYSQL_USER: keycloak
MYSQL_PASSWORD: secret
TZ: "Europe/Zurich"
tmpfs:
- /var/lib/mysql:rw
ports:
- "127.0.0.1:3307:3306"
My initial idea was to access Keycloak and its Admin Console using http (insecure), since I was running it on my local machine. The Caddyfile
for Caddy Server 2.0 looks like this.
{
admin off
}
localhost:80
reverse_proxy /* keycloak:8080
log
It simply passed all the request to the Keycloak container running on port 8080
. But as you might have guessed. That did not work out very well. Clicking on the Admin Console link ended up in a blank browser page.
FFS, ...after some other failed attempts
I took my a while to understand that the Admin Console is requiring secure access by design. I did not find any way around it but finally found a why to make my reverse proxy creating self-signed certificates. So here is a working setup.
First of all, you need to configure Caddy to listen on e.g. port 443 and create a self-signed certificate by declaring tls internal
.
{
admin off
}
localhost:443 {
reverse_proxy keycloak:8080
tls internal
}
log
Then you need to build Keycloak a bit differently.
FROM quay.io/keycloak/keycloak:18.0.0 as builder
ENV KC_FEATURES=token-exchange
ENV KC_DB=mariadb
RUN /opt/keycloak/bin/kc.sh build
FROM quay.io/keycloak/keycloak:18.0.0
COPY --from=builder /opt/keycloak/ /opt/keycloak/
WORKDIR /opt/keycloak
ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "start"]
And finally you got to adjust the Docker composition.
version: "3.9"
services:
proxy:
image: caddy:2.5.1-alpine
ports:
- "${PROXY_IP}:80:80"
- "${PROXY_IP}:443:443"
volumes:
- ${BASE_PATH:-.}/docker-volume/caddy/Caddyfile:/etc/caddy/Caddyfile:Z
- ${BASE_PATH:-.}/docker-volume/caddy/caddy_data:/data:Z
- ${BASE_PATH:-.}/docker-volume/caddy/caddy_config:/config:Z
keycloak:
image: ghcr.io/saw303/zscsupporter-be/keycloak-18.0.0:0.0.1
ports:
- "127.0.0.1:9443:8443"
restart: unless-stopped
environment:
KC_DB_URL: jdbc:mariadb://keycloakdb:3306/keycloak
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
KC_HOSTNAME: localhost
KC_HOSTNAME_STRICT: false
KC_HTTP_ENABLED: true
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: secret
KC_PROXY: edge
keycloakdb:
image: mariadb:10.7.3-focal
environment:
MYSQL_ROOT_PASSWORD: root_secret
MYSQL_DATABASE: keycloak
MYSQL_USER: keycloak
MYSQL_PASSWORD: secret
TZ: "Europe/Zurich"
tmpfs:
- /var/lib/mysql:rw
ports:
- "127.0.0.1:3307:3306"
And this is how it worked for me. Have fun with your local Keycloak container on https://localhost. Hope this helps.