Keycloak is an open-source Identity and Access Management (IAM) solution for modern web applications and services. IAM solutions ensure that individuals and entities with a digital identity have the right level of access to company resources. And this solution offers a great set of features, such as Single Sign On/Off (SSO), User Federation, Identity Brokering, Social Login, Two Factor Authentication,...
Keycloak is based on standard protocols and provides support for OpenID Connect, OAuth 2.0 and SAML (Security Assertion Markup Language).
It is also customizable and extensible, making it a very interesting product for everything that relates to Identity and Access Management.
This article describes the few steps necessary to deploy Keycloak on Scalingo. It does not explain how to set up your Keycloak realm(s). Configuring Keycloak is out of the scope of this article. Please note that while we will probably do our best to help you with Keycloak, we don’t provide managed Keycloak instances.
The nature of Keycloak as an IAM solution allows it to be used in multiple use cases depending on your needs and your type of organization.
However, there is a known limitation: for the time being, it is unfortunately not possible to run Keycloak in High Availability (HA) mode on Scalingo. The main consequence is that all users will be disconnected each time the container is restarted.
Before diving into the command lines and configuration files, I usually take some time to actually think and plan my deployment a bit. What are the software requirements? Are they available? How will I proceed?
In the case of Keycloak, here are a few requirements to consider:
These few points lead us to the following assumptions:
buildpack-jvm-common
buildpack at some point.All right, it's now time to dive into the code!
First, and from the working directory of your choice, download Keycloak and extract the archive:
% curl -L -O https://github.com/keycloak/keycloak/releases/download/19.0.1/keycloak-19.0.1.tar.gz
% tar xvzf keycloak-19.0.1.tar.gz
Let's see what we have:
% tree -L 1 keycloak-19.0.1
keycloak-19.0.1
├── bin
├── conf
├── lib
├── LICENSE.txt
├── providers
├── README.md
├── themes
└── version.txt
5 directories, 3 files
Nothing too fancy here, but we can still notice that there is no pom.xml
file in the directory, nor .m2
folder. This gives us another clue that we most probably fall under the "Installation of JDK only (no Maven)" case.
OK, keeping that in mind, let's go on and create our application!
We first need a git
repository to track our changes and to push them to Scalingo. Let’s take care of this:
# In the keycloak-19.0.1 directory:
% git init
# Add everything:
% git add .
# Create a first commit:
% git commit -m "First commit"
Then we can create our app. We will use the Scalingo command line to do so:
# Create the application:
% scalingo create my-app
# Scale the web container to size M:
% scalingo --app my-app scale web:1:M
# Create the PostgreSQL database addon:
% scalingo --app my-app addons-add postgresql postgresql-sandbox
Note that we could have used our dashboard to do the same.
Scalingo automatically detects that we are in a git
repository and, consequently, adds a git remote named scalingo
.
In the previous step, we asked Scalingo to create a database addon for Keycloak. This database addon should now be provisioned and ready to use. So let's see how to link this database to our future Keycloak instance.
Amongst the dozens of environment variables available to configure Keycloak, we will use the following:
KC_DB
KC_DB_URL
KC_DB_USERNAME
KC_DB_PASSWORD
We still need to figure out the values to put. For KC_DB
, it's pretty easy: we chose a PostgreSQL database, so the value to provide is postgres
.
The other values can be deduced from the SCALINGO_POSTGRESQL_URL
environment variable that has been provisioned along with the database addon. We will have to decrypt it, which is not that complicated.
The URL is, in fact, made of 7 main parts:
<protocol>://<username>:<password>@<host>:<port>/<database>?<parameters>
We will use these parts to set the values of the environment variables with the following mapping:
KC_DB_USERNAME
: <username>
KC_DB_PASSWORD
: <password>
KC_DB_URL
: jdbc:postgresql://<host>:<port>/<database>
Notice that we prepended jdbc:postgresql://
to the KC_DB_URL
value. This formatting is mandatory and due to the fact that Keycloak uses a technology called JDBC
to interact with the database.
There is yet another element to care about. While it’s extremely unlikely to happen, the SCALINGO_POSTGRESQL_URL
could still change in the future. To avoid errors and service failures due to such a situation, it would be ideal to create these environment variables automatically.
Create a file named create-env.sh
at the root of the keycloak-19.0.1
directory with the following content, which basically creates the required environment variables from the given connection string:
#!/usr/bin/env bash
host=$( echo $SCALINGO_POSTGRESQL_URL | cut -d "@" -f2 | cut -d ":" -f1 )
username=$( echo $SCALINGO_POSTGRESQL_URL | cut -d "/" -f3 | cut -d ":" -f1 )
port=$( echo $SCALINGO_POSTGRESQL_URL | cut -d ":" -f4 | cut -d "/" -f1 )
password=$( echo $SCALINGO_POSTGRESQL_URL | cut -d "@" -f1 | cut -d ":" -f3 )
database=$( echo $SCALINGO_POSTGRESQL_URL | cut -d "?" -f1 | cut -d "/" -f4 )
export KC_DB="postgres"
export KC_DB_USERNAME="${username}"
export KC_DB_PASSWORD="${password}"
export KC_DB_URL="jdbc:postgresql://${host}:${port}/${database}"
Finally, we have to tell Scalingo how to run Keycloak. Without this step, and even if our deployment was successful, Scalingo wouldn't know how to start the Keycloak instance!
Hopefully, Scalingo supports Procfile, which allows us to specify how containers should start.
In our case, starting Keycloak means two things:
create-env.sh
script.bin/kc.sh
script (actually, since the application is deployed in /app
, the command is /app/bin/kc.sh start-dev
).So let’s ask our web container to launch these scripts when it’s ready! Create a file named Procfile
at the root of the keycloak-19.0.1
directory with the following content:
web: bash /app/create-env.sh && /app/bin/kc.sh start-dev
Validate the changes:
% git add Procfile
% git commit -m "Create Procfile"
There is one more thing we need to handle before trying our first deployment. Keycloak requires an admin user to be created. Again, we can use some environment variables to define this user. Keycloak will automatically pick these values and create the user (if it does not exist yet):
# These values are obviously not suitable for a production setup,
# please change them:
% scalingo --app my-app env-set KEYCLOAK_ADMIN=admin
% scalingo --app my-app env-set KEYCLOAK_ADMIN_PASSWORD=keycloak
Now that everything seems ready, it's time to try the first deployment! With Scalingo, this is very little effort:
% git push scalingo master
After a while, you will see that the deployment fails:
Your deployment has been queued and is going to start…
<-- Start deployment of my-app -->
Fetching source code
Fetching deployment cache
! We didn't find the type of technology you are using...
We're sorry this build is failing!
The error message is pretty straightforward and quite logical: in this particular case, and despite doing its best, Scalingo can't find out what technology is used. This is something we have already considered a bit earlier. Do you remember that we'd probably have to tell Scalingo what build pack to use? It seems like this error is confirming this, so let's fix it!
Once more, let's do this by setting the BUILDPACK_URL
environment variable:
% scalingo --app my-app env-set BUILDPACK_URL="https://github.com/Scalingo/buildpack-jvm-common"
We can now trigger a new deployment to see if things are getting better:
% git push scalingo master
Unfortunately, this deployment fails again...but without the same error message, which we can consider as a good point: we are going forward. This time, the Java trace gives us a good hint of what is going wrong:
-- Start deployment of my-app -->
Fetching source code
Fetching deployment cache
-----> Cloning custom buildpack: <https://github.com/Scalingo/buildpack-jvm-common#master>
-----> Installing OpenJDK 1.8... done
Build complete, shipping your container...
Waiting for your application to boot...
! Error deploying the application
! → Your application has stopped unexpectedly with the following logs:
======== BEGIN APP LOGS ========
2022-07-29 15:04:50.425626192 +0000 UTC [web-1] Setting JAVA_TOOL_OPTIONS defaults based on dyno size. Custom settings will override them.
2022-07-29 15:04:50.703613309 +0000 UTC [web-1] JAVA_OPTS already set in environment; overriding default settings with values: -Xmx300m -Xss512k -XX:CICompilerCount=2 -Dfile.encoding=UTF-8
2022-07-29 15:04:50.707620930 +0000 UTC [web-1] Picked up JAVA_TOOL_OPTIONS: -Xmx300m -Xss512k -XX:CICompilerCount=2 -Dfile.encoding=UTF-8
2022-07-29 15:04:50.757674453 +0000 UTC [web-1] Error: A JNI error has occurred, please check your installation and try again
2022-07-29 15:04:50.757986378 +0000 UTC [web-1] Exception in thread "main" java.lang.UnsupportedClassVersionError: io/quarkus/bootstrap/runner/QuarkusEntryPoint has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0
2022-07-29 15:04:50.757990030 +0000 UTC [web-1] at java.lang.ClassLoader.defineClass1(Native Method)
2022-07-29 15:04:50.758047467 +0000 UTC [web-1] at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
2022-07-29 15:04:50.758170298 +0000 UTC [web-1] at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
2022-07-29 15:04:50.758173712 +0000 UTC [web-1] at java.net.URLClassLoader.defineClass(URLClassLoader.java:473)
2022-07-29 15:04:50.758174559 +0000 UTC [web-1] at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
2022-07-29 15:04:50.758296556 +0000 UTC [web-1] at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
2022-07-29 15:04:50.758300075 +0000 UTC [web-1] at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
2022-07-29 15:04:50.758300786 +0000 UTC [web-1] at java.security.AccessController.doPrivileged(Native Method)
2022-07-29 15:04:50.758301241 +0000 UTC [web-1] at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
2022-07-29 15:04:50.758431236 +0000 UTC [web-1] at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
2022-07-29 15:04:50.758433815 +0000 UTC [web-1] at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352)
2022-07-29 15:04:50.758457787 +0000 UTC [web-1] at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
2022-07-29 15:04:50.758460279 +0000 UTC [web-1] at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:601)
======== END APP LOGS ========
There are two main elements that will help us here:
Yet, we have seen before that Keycloak requires Java 8 or Java 11 JRE!
Let's deploy using a newer version of OpenJDK. To do so, create a file named system.properties
at the root of your keycloak-19.0.1
directory and put the following content in it:
java.runtime.version=17
Validate the changes and launch a new deployment:
% git add system.properties
% git commit -m "Add system.properties"
% git push scalingo master
This time, the deployment is successful, congratulations!
Your deployment has been queued and is going to start…
<-- Start deployment of my-app -->
Fetching source code
Fetching deployment cache
-----> Cloning custom buildpack: <https://github.com/Scalingo/buildpack-jvm-common#master>
-----> Installing OpenJDK 17... done
Build complete, shipping your container...
Waiting for your application to boot...
<-- <https://my-app.osc-fr1.scalingo.io> -->
Unfortunately, although we are now able to see Keycloak welcome screen, we also face a new issue: the Administration Console is stuck loading.
This is due to the fact that our Keycloak instance is running behind a reverse proxy, and, consequently, some requests are blocked by a security feature of your web browser is called Content Security Policy.
Hopefully, Keycloak provides some configuration directives to circumvent this. So let's fix this, again, by setting a few environment variables:
% scalingo --app my-app env-set KC_PROXY=edge
% scalingo --app my-app env-set KC_HTTP_PORT=\\$PORT
% scalingo --app my-app env-set KC_HTTPS_PORT=443
And trigger a new deployment:
% git commit --allow-empty -m "Add reverse proxy env vars"
% git push scalingo master
This time we are all set! Well done!
The very first thing to care about after this first deployment is to change the admin account password for something strong and secure. Once done, the corresponding environment variables are useless and can be removed:
% scalingo --app my-app env-unset KEYCLOAK_ADMIN
% scalingo --app my-app env-unset KEYCLOAK_ADMIN_PASSWORD
% scalingo --app my-app restart
Since Keycloak is an IAM software, externalizing the logs it produces seems to be a reasonable idea, should you want to detect any weird behaviors, analyze requests for debugging purposes or keep them (the logs) during a certain amount of time for legal reasons.
An excellent way to achieve these goals is to deploy an ELK stack to collect these logs. Here again, we've got you covered!
Once your ELK stack is ready, don't forget to add a log-drain to your Keycloak instance.
Procfile
and maybe updating Keycloak's configuration.At Scalingo (with our partners) we use trackers on our website.
Some of those are mandatory for the use of our website and can't be refused.
Some others are used to measure our audience as well as to improve our relationship with you or to send you quality content and advertising.