Keycloak is a powerful open source Identity and Access Management (IAM) solution used to manage access to modern infrastructure, applications, and services. It gives you advanced authentication and authorization mechanism for your services, enabling developers to better secure applications without coding custom authentication modules.
Features of Keycloak (Benefits)?
Here are some key benefits and uses of Keycloak:
- Centralized Access Management: You get a unified interface from where you can administer user identities, permissions, access roles, across various services and applications.
- Management of user accounts: Keycloak enables users to manage their own accounts information, including password changes and account linking.
- Single Sign-On (SSO) and Single Log-Out: Single authentication for multiple applications, and logout once from all apps.
- Keycloak as Identity Broker / Social Login: It can be integrated with third party identity providers like Google, Github, Facebook, and other OpenID Connect or SAML-based identity providers.
- Use for User Federation: Keycloak can Integrate with existing user databases in corporate environments such as Active Directory or LDAP.
- Multi-Factor Authentication (MFA): You can introduce an extra layer of security by enforcing multiple forms of verification to gain access to systems and applications.
- API Access Management: Easily secure application APIs using tokens (JWT tokens or SAML assertions).
- Keycloak support multiple protocols like OAuth2, SAML, and OpenID Connect (OIDC).
In this article we will take you through the process of installing and running Keycloak in Docker or Podman. Two options will be considered:
- Running Keycloak in a container without SSL (HTTP)
- Running Keycloak in container with SSL (HTTPs)
All configurations used in this guide are available in our Docker Compose templates Github repository. You can click on the link to learn more.
Let’s begin with the first option.
Running Keycloak in a container without SSL (HTTP) – Not Recommended
We will use domain name as follows, with an IP address:
- IP 192.168.1.20 mapped to domain iam.cloudspinx.com
Let’s begin by installing Docker and Podman in the system:
- Install Docker Container Engine:
curl -fsSL get.docker.com -o get-docker.sh
sudo sh get-docker.sh
- Installing Podman container runtime:
# RHEL based systems
sudo dnf -y install podman
sudo dnf install -y python3-pip
sudo pip3 install podman-compose
# Debian based systems
sudo apt update && sudo apt install podman
sudo apt install python3-pip
sudo pip3 install podman-compose
Create a directory what will host the project code.
mkdir ~/keycloak && cd ~/keycloak
Create an environment file with configuration properties.
$ vim .env
KC_DB_PASSWORD=StrongDBPassword
KC_HOSTNAME=iam.cloudspinx.com
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=Str0ngAdminPassw0rD
- StrongDBPassword should be replaced with your desired Keycloak admin user password
- admin is the default privileged user for Keycloak
- Str0ngAdminPassw0rD is the password for the admin user
- iam.cloudspinx.com with your server hostname is configured in DNS or
/etc/hosts
file
Create docker-compose.yaml
file with contents required to run keycloak in container.
vim docker-compose.yaml
Then update your docker compose yaml file like below.
services:
keycloak:
container_name: keycloak
image: quay.io/keycloak/keycloak:latest
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres_db:5432/keycloak
KC_DB_USER: keycloak
KC_DB_SCHEMA: public
KC_DB_PASSWORD: ${KC_DB_PASSWORD}
KC_HOSTNAME: ${KC_HOSTNAME}
KC_HOSTNAME_PORT: 8080
KC_HOSTNAME_STRICT_BACKCHANNEL: false
KC_HTTP_ENABLED: true
KC_HOSTNAME_STRICT_HTTPS: false
KC_HEALTH_ENABLED: true
KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN}
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
ports:
- 8080:8080
restart: always
command: start
depends_on:
- postgres_db
networks:
- keycloak
postgres_db:
container_name: postgres-db
image: postgres
restart: always
volumes:
- ./data/db:/var/lib/postgresql/data
- /etc/localtime:/etc/localtime:ro
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: ${KC_DB_PASSWORD}
ports:
- 5432:5432
networks:
- keycloak
networks:
keycloak:
external: true
Manually create keycloak network
docker network create keycloak
To start the containers, run the following commands:
docker compose up -d
Expected output once the command is executed:
[+] Running 20/20
✔ keycloak Pulled 14.1s
✔ a04e04efb6ef Pull complete 3.4s
✔ 41df698d6f61 Pull complete 8.8s
✔ 15c52560d256 Pull complete 11.9s
✔ 844215b13393 Pull complete 11.9s
✔ postgres_db Pulled 14.4s
✔ efc2b5ad9eec Pull complete 3.9s
✔ c1fb352e1bee Pull complete 3.9s
✔ 64bfb99d167c Pull complete 4.4s
✔ 111153255129 Pull complete 4.6s
✔ f319b93a653f Pull complete 5.3s
✔ c52a8331fefe Pull complete 5.4s
✔ eede18b3a95f Pull complete 5.4s
✔ 8ecfa78d0b10 Pull complete 5.4s
✔ 1d8a72096603 Pull complete 12.6s
✔ 74799cfc006f Pull complete 12.6s
✔ ca3e3ab274d4 Pull complete 12.7s
✔ 17b5d00a7c44 Pull complete 12.7s
✔ 86ed78b05f10 Pull complete 12.7s
✔ 786a87677547 Pull complete 12.7s
[+] Running 2/2
✔ Container postgres-db Started 0.5s
✔ Container keycloak Started
Container status can be checked by running:
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
keycloak quay.io/keycloak/keycloak:latest "/opt/keycloak/bin/k…" keycloak 26 seconds ago Up 26 seconds 8443/tcp, 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp, 9000/tcp
postgres-db postgres "docker-entrypoint.s…" postgres_db 26 seconds ago Up 26 seconds 0.0.0.0:5432->5432/tcp, :::5432->5432/tcp
Watch for any startup errors using docker logs
command:
docker logs postgres-db
docker logs keycloak
Access Keycloak admin dashboard at http://your_domain:8080
Login with values set for KEYCLOAK_ADMIN and KEYCLOAK_ADMIN_PASSWORD.
Running Keycloak in container with SSL (HTTPS) – Let’s Encypt
Two options are viable for this:
Option 1) Using Self-signed certificates
Create a directory called ssl.
mkdir ssl && cd ssl
You can use OpenSSL to generate self-signed certificates.
Generate private key.
openssl genrsa -out keycloak.key
Generate CSR and private key files by running the following commands.
openssl req -new -key keycloak.key -out keycloak.csr
Sign the certificate using a private key and CSR:
openssl x509 -req -in keycloak.csr -signkey keycloak.key -days 3650 -out keycloak.crt
Set ssl directory permissions to allow container to read SSL files.
cd ..
chmod -R 0644 ssl
Open the compose file for update
vim docker-compose.yaml
Modify the contents to include mounting of SSL certificate and key and setting environment variables in the container.
services:
keycloak:
container_name: keycloak
image: quay.io/keycloak/keycloak:latest
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres_db:5432/keycloak
KC_DB_USER: keycloak
KC_DB_SCHEMA: public
KC_DB_PASSWORD: ${KC_DB_PASSWORD}
KC_HOSTNAME: ${KC_HOSTNAME}
KC_HEALTH_ENABLED: true
KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN}
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
KC_HTTPS_CERTIFICATE_FILE: /opt/keycloak/conf/server.crt
KC_HTTPS_CERTIFICATE_KEY_FILE: /opt/keycloak/conf/server.key
ports:
- 8443:8443
restart: always
volumes:
- ./ssl/keycloak.crt:/opt/keycloak/conf/server.crt
- ./ssl/keycloak.key:/opt/keycloak/conf/server.key
command: start
depends_on:
- postgres_db
networks:
- keycloak
postgres_db:
container_name: postgres-db
image: postgres
restart: always
volumes:
- ./data/db:/var/lib/postgresql/data
- /etc/localtime:/etc/localtime:ro
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: ${KC_DB_PASSWORD}
ports:
- 5432:5432
networks:
- keycloak
networks:
keycloak:
external: true
Take down the services.
docker compose down
Bring up the containers with the new configurations.
docker compose up -d
Monitor logs to see if it can start successfully.
docker logs keycloak
Option 2) Using Let’s Encrypt SSL
We can can create a directory that will contain SSL files.
mkdir ~/keycloak/certbot && cd ~/keycloak/certbot
Create docker compose file for generating Let’s Encrypt SSL certificates.
vim docker-compose.yaml
Here are the contents to use for the file.
services:
webserver:
image: nginx:latest
ports:
- 80:80
- 443:443
restart: always
volumes:
- ${WEBROOT_PATH}:/var/www/certbot/
- ${LETSENCRYPT_PATH}:/etc/letsencrypt
- ${NGINX_CONF_PATH}:/etc/nginx/nginx.conf
certbot:
image: certbot/certbot:latest
entrypoint: ""
command: sh -c "trap exit TERM; while :; do certbot certonly --webroot --webroot-path=/var/www/certbot --email ${EMAIL} --agree-tos --no-eff-email -d ${DOMAIN}; sleep 12h & wait $${!}; done;"
volumes:
- ./www/:/var/www/certbot/
- ./letsencrypt:/etc/letsencrypt
Create .env
file with email address, domain, and other required params.
$ vim .env
EMAIL=[email protected]
DOMAIN=iam.cloudspinx.com
WEBROOT_PATH=./www/
LETSENCRYPT_PATH=./letsencrypt
NGINX_CONF_PATH=./nginx.conf
Create necessary directories.
source .env
mkdir -p $LETSENCRYPT_PATH $WEBROOT_PATH
chmod 0777 $LETSENCRYPT_PATH $WEBROOT_PATH
Create nginx configuration file:
vim ./nginx.conf
Add the contents below into the file. Don’t forget to use your valid domain in place of iam.cloudspinx.com.
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name iam.cloudspinx.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
}
Run the docker compose up
to request for SSL.
$ docker compose up
[+] Running 2/0
✔ Container letsencrypt-certbot-1 Created 0.1s
✔ Container letsencrypt-webserver-1 Created 0.1s
Attaching to certbot-1, webserver-1
webserver-1 | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
webserver-1 | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
webserver-1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
webserver-1 | 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
webserver-1 | 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
webserver-1 | /docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
webserver-1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
webserver-1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
webserver-1 | /docker-entrypoint.sh: Configuration complete; ready for start up
certbot-1 | Saving debug log to /var/log/letsencrypt/letsencrypt.log
certbot-1 | Requesting a certificate for iam.cloudspinx.com
webserver-1 | 23.178.112.106 - - [13/Aug/2024:09:51:10 +0000] "GET /.well-known/acme-challenge/QswDZWRIvZn4ZkvPJ_hugHhAQ6j6M6sWL3uOfwEYcx4 HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
webserver-1 | 13.51.177.57 - - [13/Aug/2024:09:51:10 +0000] "GET /.well-known/acme-challenge/QswDZWRIvZn4ZkvPJ_hugHhAQ6j6M6sWL3uOfwEYcx4 HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
webserver-1 | 18.117.224.42 - - [13/Aug/2024:09:51:10 +0000] "GET /.well-known/acme-challenge/QswDZWRIvZn4ZkvPJ_hugHhAQ6j6M6sWL3uOfwEYcx4 HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
webserver-1 | 52.10.95.25 - - [13/Aug/2024:09:51:10 +0000] "GET /.well-known/acme-challenge/QswDZWRIvZn4ZkvPJ_hugHhAQ6j6M6sWL3uOfwEYcx4 HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
webserver-1 | 18.143.65.27 - - [13/Aug/2024:09:51:10 +0000] "GET /.well-known/acme-challenge/QswDZWRIvZn4ZkvPJ_hugHhAQ6j6M6sWL3uOfwEYcx4 HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
certbot-1 |
certbot-1 | Successfully received certificate.
certbot-1 | Certificate is saved at: /etc/letsencrypt/live/iam.cloudspinx.com/fullchain.pem
certbot-1 | Key is saved at: /etc/letsencrypt/live/iam.cloudspinx.com/privkey.pem
certbot-1 | This certificate expires on 2024-11-11.
certbot-1 | These files will be updated when the certificate renews.
certbot-1 | NEXT STEPS:
certbot-1 | - The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.
certbot-1 |
certbot-1 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
certbot-1 | If you like Certbot, please consider supporting our work by:
certbot-1 | * Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
certbot-1 | * Donating to EFF: https://eff.org/donate-le
certbot-1 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "\x16\x03\x01\x01\x05\x01\x00\x01\x01\x03\x03\x9B\xA1\xD8\xBB\xC8\x1DJ\x89\x18\xAF1\xEA\x17\xBCT>:^\xD7\x02\xAB\xC2\xAF\x18\x5C\xFAh8\x84\xB2\xC2p \xA9\xD7\xE8\xAA\xD5" 400 157 "-" "-"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET / HTTP/1.1" 301 169 "-" "-"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET / HTTP/1.1" 301 169 "-" "Mozilla/5.0 (Linux; Android 6.0; HTC One M9 Build/MRA523394) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.1959.98 Mobile Safari/537.3"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET /server HTTP/1.1" 301 169 "-" "Go-http-client/1.1"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET /.vscode/sftp.json HTTP/1.1" 301 169 "-" "Go-http-client/1.1"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET /about HTTP/1.1" 301 169 "-" "Go-http-client/1.1"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET /debug/default/view?panel=config HTTP/1.1" 301 169 "-" "Go-http-client/1.1"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET /v2/_catalog HTTP/1.1" 301 169 "-" "Go-http-client/1.1"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET /ecp/Current/exporttool/microsoft.exchange.ediscovery.exporttool.application HTTP/1.1" 301 169 "-" "Go-http-client/1.1"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET /server-status HTTP/1.1" 301 169 "-" "Go-http-client/1.1"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET /login.action HTTP/1.1" 301 169 "-" "Go-http-client/1.1"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET /_all_dbs HTTP/1.1" 301 169 "-" "Mozilla/5.0 (l9scan/2.0.631323e2033323e2330323e2631313; +https://leakix.net)"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET /.DS_Store HTTP/1.1" 301 169 "-" "Go-http-client/1.1"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET /.env HTTP/1.1" 301 169 "-" "Go-http-client/1.1"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET /.git/config HTTP/1.1" 301 169 "-" "Go-http-client/1.1"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET /s/631323e2033323e2330323e2631313/_/;/META-INF/maven/com.atlassian.jira/jira-webapp-dist/pom.properties HTTP/1.1" 301 169 "-" "Go-http-client/1.1"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET /config.json HTTP/1.1" 301 169 "-" "Go-http-client/1.1"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET /telescope/requests HTTP/1.1" 301 169 "-" "Go-http-client/1.1"
webserver-1 | 164.92.244.132 - - [13/Aug/2024:09:51:23 +0000] "GET /?rest_route=/wp/v2/users/ HTTP/1.1" 301 169 "-" "Go-http-client/1.1"
You can also run it in the background.
docker compose up -d
Verify that Nginx is serving the challenge files correctly and that Certbot is able to retrieve the certificate.
docker compose logs -f webserver
docker compose logs -f certbot
Once the key and certificate is generated you can cancel or stop the container services for certbot.
source .env
ls -1 ssl-certs/letsencrypt/live/$DOMAIN/*
Create a different directory and copy cert and key files over.
cd ../
mkdir ./keycloak-certs
cp letsencrypt/live/$DOMAIN/fullchain.pem ./keycloak-certs
cp letsencrypt/live/$DOMAIN/privkey.pem ./keycloak-certs
chmod -R 0644 ./keycloak-certs
Update your Compose file:
services:
keycloak:
container_name: keycloak
image: quay.io/keycloak/keycloak:latest
privileged: true
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres_db:5432/keycloak
KC_DB_USER: keycloak
KC_DB_SCHEMA: public
KC_DB_PASSWORD: ${KC_DB_PASSWORD}
KC_HOSTNAME: ${KC_HOSTNAME}
KC_HEALTH_ENABLED: true
KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN}
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
KC_HTTPS_CERTIFICATE_FILE: ${KC_HTTPS_CERTIFICATE_FILE}
KC_HTTPS_CERTIFICATE_KEY_FILE: ${KC_HTTPS_CERTIFICATE_KEY_FILE}
ports:
- 8443:8443
restart: always
volumes:
- ${LOCAL_CERT}:${KC_HTTPS_CERTIFICATE_FILE}
- ${LOCAL_KEY}:${KC_HTTPS_CERTIFICATE_KEY_FILE}
command: start
depends_on:
- postgres_db
networks:
- keycloak
postgres_db:
container_name: postgres-db
image: postgres
restart: always
volumes:
- ./data/db:/var/lib/postgresql/data
- /etc/localtime:/etc/localtime:ro
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: ${KC_DB_PASSWORD}
ports:
- 5432:5432
networks:
- keycloak
networks:
keycloak:
external: true
Update environment configurations:
KC_DB_PASSWORD=StrongDBPassword
KC_HOSTNAME=iam.cloudspinx.com
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=Str0ngAdminPassw0rD
KC_HTTPS_CERTIFICATE_FILE=/opt/keycloak/conf/fullchain.pem
KC_HTTPS_CERTIFICATE_KEY_FILE=/opt/keycloak/conf/privkey.pem
LOCAL_CERT=./keycloak-certs/fullchain.pem
LOCAL_KEY=./keycloak-certs/privkey.pem
Stop and start the services.
docker compose down && docker compose up -d
Access your Keycloak server web console on https://yourdomain:8443.
Let’s Encrypt renewal script
Create renewal script.
#!/bin/bash
# Load environment variables
source .env
# Stop containers
docker compose stop
# Run certbot renewal
docker compose run --rm certbot sh -c "certbot renew --webroot --webroot-path=/var/www/certbot"
# Check if certbot renewal was successful
if [ $? -eq 0 ]; then
cd ../
mkdir -p ./keycloak-certs
# Copy the renewed certificates to the keycloak-certs directory
mkdir -p ./keycloak-certs
cp -f certbot/letsencrypt/live/$DOMAIN/fullchain.pem ./keycloak-certs
cp -f certbot/letsencrypt/live/$DOMAIN/privkey.pem ./keycloak-certs
chmod -R 0644 ./keycloak-certs
echo "Certificates renewed and copied successfully."
else
echo "Certificate renewal failed."
fi
Make it executable
chmox +x ssl_renew.sh
Execute the script to renew the certificates:
bash ssl_renew.sh
You can have cron job to check every 4am.
$ crontab -e
0 04 * * * /path/to/your/renew_certificates.sh >> /var/log/certbot-renew.log 2>&1
Enjoy using Keycloak as your identity manager for better access control to systems and applications. You can access all configs used in our github repository. If you need any help CloudSpinx Engineers are available 24/7.