How to package extensions in a Docker image

I’m trying to figure out which way would be better to package an image of Keycloak with my own extensions. I’m targeting a deployment in a Kubernetes cluster so everything needs to be self contained.

I have experimented a little with the server deployments by adding my jar files in standalone/deployments through a multi-stage dockerfile, like this example:

FROM gradle:6.7.0-jdk11 AS builder
WORKDIR /work
ADD . .
RUN gradle jar

FROM quay.io/keycloak/keycloak:11.0.2
COPY --from=builder /work/build/libs/my-module.jar /opt/jboss/keycloak/standalone/deployments/

This works fine for themes, jpa or required actions so far, but it doesn’t seem to work for custom Spi (as stated in this thread: Not able to register a custom SPI)

So another approach could be to package my jars in modules instead and use the module approach, but then I would have to edit something like the standalone.xml file to include my modules. That would probably be achieved by doing some sed magic through Docker, but that’s not very robust: prone to break when I’ll be upgrading Keycloak versions or maybe inserting my XML entry at the wrong place and messing something up.

Is there another approach I’m not thinking of that would be better for my use case?

1 Like

You can use startup scripts:

Just create a file deploy.cli and use the docker file to copy it and your spi:

FROM jboss/keycloak:11.0.2

COPY ./your-spi.jar /opt/jboss/keycloak/
COPY ./deploy.cli /opt/jboss/startup-scripts/ 

delpoy.cli

embed-server --std-out=echo  --server-config=standalone-ha.xml
if (outcome != success) of /core-service=module-loading/:list-resource-loader-paths(module=com.your.module.xyz)
    echo adding module com.your.module.xyz
    module add --name=com.your.module.xyz --resources=/opt/jboss/keycloak/you-spi.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-services,org.keycloak.keycloak-model-jpa,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-server-spi-private,javax.ws.rs.api,javax.persistence.api,org.hibernate,org.javassist,org.liquibase
    /subsystem=keycloak-server:list-add(name=providers, value=module:com.your.module.xyz)
else
    echo module com.your.module.xyz already installed
end-if
stop-embedded-server

(of course adjust your module and dependencies)

3 Likes

Thanks @Tikko, I’ll try it out!

Out of curiosity, are there any performance or reliability reasons why to choose the module approach rather than the standalone/deployments one when packaging a docker image, apart that some functionalities are not supported by the latter?

Also, the script would be executed when building the Dockerfile, right? Not during the entrypoint.

The script will be executed during startup of keycloak and not during the docker build. The Dockerfile just copies the script to the startup-scripts.

I don’t know why some SPIs require to be a module. I experienced that we would need to use a module when the SPI needs to change the database layout using liquibase. I am not sure if that is true, but it would make sense for me, because liquibase is probably run at the startup of the keycloak context and not during hot-deployments of jars.

Regarding performance or reliability, I think it will not have any differences. Performance is more relevant when the context is up and running and then it is only about the specific SPI and its implementation whether it is performant.

1 Like

You want to look into jboss-deployment-structure.xml
This is a file very similar to module.xml and it is packaged inside the jar one meta-inf.
So it describes inside-out the modules dependencies and the deployment works fine.
This means you can mount the file from outside the docker container and if it’s being replaced it will be uninstalled and installed including details on the log.

Another option is to use the extraInitContainer parameter, along with mounted volumes parameters, for the Helm chart for a Kubernetes deployment. You can provide a custom image here that contains the theme, which would run as a sidecar with the Keycloak Pod of the Keycloak StatefulSet.
The startup scripts of the extra container could copy the theme from the mounted shared volume to the themes location in the Keycloak container.

In the this values.yaml file, you’d be entering values here:

  • extraInitContainers
  • extraVolumes
  • extraVolumeMounts
1 Like

Dear maxlegault,

You can do this way as well, copy your file into extensions and run the batch before starting the EAP.
Just create a file deploy.cli and use the docker file to copy it and your spi:

FROM jboss/keycloak:11.0.2

    COPY ./your-spi.jar /opt/jboss/keycloak/
    COPY ./deploy.cli /opt/jboss/extensions/deploy.cli

delpoy.cli

    batch
    module add --name=com.your.module.xyz --resources=/opt/jboss/keycloak/you-spi.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-services,org.keycloak.keycloak-model-jpa,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-server-spi-private,javax.ws.rs.api,javax.persistence.api,org.hibernate,org.javassist,org.liquibase
 /subsystem=keycloak-server:list-add(name=providers, value=module:com.your.module.xyz)
run-batch

Batch mode is a suggested approach, to add any external component into the standalone file. Even we do not need to mention the configuration file it will automatically detect. Moreover, extensions will automatically read by the Keycloak startup.