Run Keycloak in Docker over HTTPS using Certbot

In this article, I will walk you through the process of setting up Keycloak in Docker and configuring it to handle SSL/HTTPS. Normally, it would require you to buy a certificate for your Keycloak instance domain name; however, in this example, we are going to use a free SSL certificate from Let’s Encrypt. Our journey will consist of three parts: running Certbot in Docker and adding a validation record to our DNS, setting read access to the generated certificate, and finally, running Keycloak in Docker with the issued certificates.

Prerequisites

What are Let's Encrypt and Certbot

Let's Encrypt is a global non-profit Certificate Authority (CA) operated by the Internet Security Research Group (ISRG). As part of its public security enhancement program, it provides X.509 certificates for Transport Layer Security (TLS) encryption for free. To issue our certificate and go through the validation phase, we will use Certbot, a free, open-source command-line tool that can be used to manage Let's Encrypt certificates.

Getting Certificate

The first step of our ride is to run Certbot in Docker. Let's execute the next command to create certificates for our test project "https://yummy-life.art" for the Keycloak instance. We will use "auth.yummy-life.art" as our endpoint. To share files between applications, we will mount the same folder and files as volumes to both containers. In this guide, I will use "~/letsencrypt" as the path to our shared folder. There's no need to create it in advance; Docker will handle it for us:

sudo docker run -it --rm \
-v ~/letsencrypt:/etc/letsencrypt \
certbot/certbot \
certonly -d auth.yummy-life.art --manual --preferred-challenges dns

This command instructs Docker to start a new container, allocate a pseudo-TTY connected to it, remove the container when it exits, and mount the "~/letsencrypt" folder into the container at the "/etc/letsencrypt" path used by Certbot to store files. The final line is an application command with arguments that directs Certbot to run in manual mode with a DNS challenge to confirm domain name ownership. During the process, Certbot will ask you for your email to use for urgent renewal and security notices:

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
(Enter 'c' to cancel): admin@yummy-life.art

Then, you will need to confirm that you have read and agree to the Terms of Service:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf. You must
agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y

In the next step, you can choose to either agree or disagree to participate in the newsletter and etc. For this particular occasion, we will choose to disagree:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: N

Our account is registered. Now, it's time to create the requested DNS server record:

Account registered.
Requesting a certificate for auth.yummy-life.art
 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name:
 
_acme-challenge.auth.yummy-life.art.
 
with the following value:
 
4dh7sSh67DFlkgRmB_i_n74CkShgdDz7-kjhe98ko3s
 
Before continuing, verify the TXT record has been deployed. Depending on the DNS
provider, this may take some time, from a few seconds to multiple minutes. You can
check if it has finished deploying with aid of online tools, such as the Google
Admin Toolbox: https://toolbox.googleapps.com/apps/dig/#TXT/_acme-challenge.auth.yummy-life.art.
Look for one or more bolded line(s) below the line ';ANSWER'. It should show the
value(s) you've just added.
 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

Let's proceed and create it:

Name Type TTL Value
_acme-challenge.auth TXT 300 4dh7sSh67DFlkgRmB_i_n74CkShgdDz7-kjhe98ko3s
auth A 300 150.136.20.73

To pass validation, we only need the first record. However, since my domain name is new, and I don't have a related A record for my subdomain "auth.yummy-life.art," I added it as well. Our certificate was successfully generated:

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/auth.yummy-life.art/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/auth.yummy-life.art/privkey.pem
This certificate expires on 2024-03-25.
These files will be updated when the certificate renews.
 
NEXT STEPS:
- This certificate will not be renewed automatically. Autorenewal of --manual certificates
requires the use of an authentication hook script (--manual-auth-hook) but one was not provided.
To renew this certificate, repeat this same certbot command before the certificate's expiry date.  
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
 * Donating to EFF:                  https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Here are the files and folders with certificates created by Certbot:

[chief@test ~]$ sudo tree
.
└── letsencrypt
├── accounts
│   └── acme-v02.api.letsencrypt.org
│   └── directory
│   └── 508c7d121cc3d0f20cb6f2231f9efb36
│   ├── meta.json
│   ├── private_key.json
│   └── regr.json
├── archive
│   └── auth.yummy-life.art
│   ├── cert1.pem
│   ├── chain1.pem
│   ├── fullchain1.pem
│   └── privkey1.pem
├── live
│   ├── auth.yummy-life.art
│   │   ├── cert.pem -> ../../archive/auth.yummy-life.art/cert1.pem
│   │   ├── chain.pem -> ../../archive/auth.yummy-life.art/chain1.pem
│   │   ├── fullchain.pem -> ../../archive/auth.yummy-life.art/fullchain1.pem
│   │   ├── privkey.pem -> ../../archive/auth.yummy-life.art/privkey1.pem
│   │   └── README
│   └── README
├── renewal
│   └── auth.yummy-life.art.conf
└── renewal-hooks
├── deploy
├── post
└── pre

14 directories, 14 files

The keycloak install

Let's advance to the next stage of our journey and execute the Docker command to run Keycloak in a container. Please do not forget to replace the exemplary admin username and password with yours for security's sake:

sudo docker run -d -p 443:8443
-v ~/letsencrypt/live/auth.yummy-life.art/fullchain.pem:/etc/x509/https/tls.crt
-v ~/letsencrypt/live/auth.yummy-life.art/privkey.pem:/etc/x509/https/tls.key
-e KEYCLOAK_ADMIN=chief
-e KEYCLOAK_ADMIN_PASSWORD=h12#67Kwh#jgs4
-e KC_HOSTNAME=auth.yummy-life.art
-e KC_HTTPS_CERTIFICATE_FILE=/etc/x509/https/tls.crt
-e KC_HTTPS_CERTIFICATE_KEY_FILE=/etc/x509/https/tls.key
quay.io/keycloak/keycloak:latest start-dev

Docker will download the image if you don't have it on your server and will install Keycloak with specified parameters. It will take some minutes to start, and after that, you will see that the Keycloak instance is ... failed:

[chief@test ~]$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
470ff6182094 quay.io/keycloak/keycloak:latest "/opt/keycloak/bin/k…" 4 minutes ago Exited (1) About a minute ago serene_noyce

In the logs, you can find the following error lines:

[chief@test ~]$ sudo docker logs 470ff6182094 ... 2023-12-27 21:05:25,769 ERROR [org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler] (main) ERROR: Failed to start server in (production) mode 2023-12-27 21:05:25,770 ERROR [org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler] (main) ERROR: /etc/x509/https/tls.key 2023-12-27 21:05:25,771 ERROR [org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler] (main) For more details run the same command passing the '--verbose' option. Also you can use '--help' to see the details about the usage of the particular command.

Keycloak shows an error because it has no access to the file. Let's add read permission to the file and restart the container:

[chief@test ~]$ sudo chmod +r ~/letsencrypt/live/auth.yummy-life.art/privkey.pem [chief@test ~]$ sudo docker restart 470ff6182094

And voila! Now we can see our Keycloak is up and running over the HTTPS protocol:

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
470ff6182094 quay.io/keycloak/keycloak:latest "/opt/keycloak/bin/k…" 23 minutes ago Up 8 minutes 8080/tcp, 0.0.0.0:443->8443/tcp, :::443->8443/tcp serene_noyce

Now, let's test it in the browser: Keycloak over HTTPS

Conclusion

In this guide, we generated a Let's Encrypt X.509 certificate and ran a brand new instance of Keycloak in a Docker container over the HTTPS protocol. It's important to remember that our certificate is valid only for 90 days, and we will have to repeat the Certbot command to regenerate it. Happy coding, people!