Guide to deploy Keycloak on Scalingo

October 04, 2022 - 10 min read
Guide to deploy Keycloak on Scalingo

What is Keycloak?

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.

How can Keycloak be used?

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.

Deploying Keycloak on Scalingo

Planning the deployment

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:

  • First, Keycloak is a Java web application, and it requires Java 8 JRE or Java 11 JRE.
  • It needs at least 512 MB of RAM.
  • It requires an external database such as PostgreSQL, MySQL, MariaDB, Oracle...
  • It can use environment variables to configure itself.

These few points lead us to the following assumptions:

  • We want to deploy Keycloak 19.0.1, released on July 29th, 2022.
  • There is some documentation explaining how to run a Java application on Scalingo, which makes a good starting point.
  • We will most probably need a Java-related buildpack. More precisely, reading the documentation previously mentioned and seeing no mention of Maven in the Keycloak documentation, we will need to use the buildpack-jvm-common buildpack at some point.
  • We will need a web container of size M.
  • We will need a PostgreSQL database addon. A sandbox plan should be enough for our first tests.

All right, it's now time to dive into the code!

Getting started

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!

Creating the 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.

Linking Keycloak to the created resources

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}"

Telling Scalingo how to run Keycloak

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:

  1. Create the required environment variables. This is done in the create-env.sh script.
  2. Actually start Keycloak. To do this, Keycloak provides a 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"

Creating the admin user

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

Deploying

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

Selecting the OpenJDK version

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:

  • First, the Java exception message seems to warn us about an outdated version of the Java Runtime.
  • Second, the log states that OpenJDK 1.8 has been deployed.

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> -->

Dealing with Content Security Policy

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!

Going Further

Changing the admin Account Password

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

Externalizing the Logs

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.

And more

  • Configuring your realm(s): this is out of the scope of this blog, but Keycloak has nice documentation to help you.
  • Switching to production: this will probably involve a few steps such as switching to a Business plan for your database, updating your Procfile and maybe updating Keycloak's configuration.
Share the article
François Kubler
François Kubler
François is a Solution Engineer at Scalingo. He likes to automate and to keep things simple. His various experiences in computing lead him to strongly believe in open-source and, more importantly, in the strenght of a European cloud.

Try Scalingo for free

30-day free trial / No credit card required / Hosted in Europe