Deploying a Persistent and Secure Keycloak Cluster with Docker Compose + Nginx + SSL
# 🚀 Deploying a Persistent and Secure Keycloak Cluster with Docker Compose + Nginx + SSL
Keycloak is a powerful open-source identity and access management solution. In this guide, we’ll set up a **persistent**, **SSL-secured**, and **Nginx-proxied** Keycloak deployment using **Docker Compose** — perfect for production or local testing.
---
## 🧩 Project Overview
We’ll be running:
- PostgreSQL 15.5 (persistent data)
- Keycloak 26.0.0
- Nginx reverse proxy with Let’s Encrypt SSL
Domain used: `keycloak.exceltoquiz.com`
Ports used: `7081` (Keycloak), `5432` (Postgres)
---
## 🗄️ Folder Structure
```
keycloak-cluster/
├── docker-compose.yml
├── postgres_data/ # Persistent Postgres data
└── nginx.conf # Nginx reverse proxy config
```
---
## 🐳 Docker Compose Configuration
Below is the working `docker-compose.yml` file:
```yaml
version: "3.9"
services:
postgres:
container_name: keycloak_db
image: postgres:15.5
restart: always
healthcheck:
test: ["CMD", "pg_isready", "-q", "-d", "keycloak", "-U", "postgres"]
timeout: 30s
interval: 10s
retries: 5
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: keycloak
volumes:
- ./postgres_data:/var/lib/postgresql/data
networks:
- keycloak_net
ports:
- "5432:5432"
kc1:
container_name: keycloak_1
image: quay.io/keycloak/keycloak:26.0.0
command: ["start-dev"]
restart: always
depends_on:
- postgres
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres/keycloak
KC_DB_USERNAME: postgres
KC_DB_PASSWORD: postgres
KC_HTTP_ENABLED: "true"
KC_PROXY_HEADERS: xforwarded
KC_HOSTNAME: keycloak.exceltoquiz.com
KC_HOSTNAME_URL: https://keycloak.exceltoquiz.com
KC_HOSTNAME_ADMIN_URL: https://keycloak.exceltoquiz.com
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
KC_BOOTSTRAP_ADMIN_USERNAME: admin
networks:
- keycloak_net
ports:
- "7081:8080"
networks:
keycloak_net:
driver: bridge
```
### 🧠 Notes:
- Persistent Postgres data stored in `./postgres_data`
- Fixed permissions (if needed) using:
```bash
sudo chown -R 999:999 postgres_data
```
- Keycloak runs in `start-dev` mode (suitable for initial setup)
---
## 🌐 Nginx HTTPS Reverse Proxy
The following **Nginx configuration** handles HTTPS termination via Certbot-managed certificates:
```nginx
upstream keycloak_cluster {
server 127.0.0.1:7081;
}
server {
server_name keycloak.exceltoquiz.com;
location / {
proxy_pass http://keycloak_cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Port 443;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/keycloak.exceltoquiz.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/keycloak.exceltoquiz.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
server {
if ($host = keycloak.exceltoquiz.com) {
return 301 https://$host$request_uri;
}
listen 80;
server_name keycloak.exceltoquiz.com;
return 404;
}
```
---
## 🔐 Enable SSL with Certbot
Install Certbot and obtain the certificate:
```bash
sudo mkdir -p /var/www/certbot
sudo certbot certonly --webroot -w /var/www/certbot -d keycloak.exceltoquiz.com
```
Reload Nginx after Certbot setup:
```bash
sudo systemctl reload nginx
```
---
## 🧠 Useful Commands
| Command | Description |
|----------|-------------|
| `docker compose up -d` | Start all containers |
| `docker compose logs -f` | View logs |
| `docker exec -it keycloak_db psql -U postgres -l` | List databases |
| `docker exec -it keycloak_db psql -U postgres -c "CREATE DATABASE keycloak;"` | Create Keycloak DB manually |
| `sudo chown -R 999:999 postgres_data` | Fix folder permission for Postgres persistence |
---
## 🎯 Result
You now have:
- Persistent Postgres data stored locally
- Secure Keycloak running at **https://keycloak.exceltoquiz.com**
- SSL managed automatically by Certbot
- Production-ready reverse proxy with Nginx
---
💡 **Tip:** You can easily scale Keycloak horizontally by adding another service block (e.g., `kc2`) and including it in the `upstream` block.
---
**Author:** Md Golam Kibria (GK)
**Tags:** `Keycloak`, `Docker`, `Nginx`, `SSL`, `PostgreSQL`, `DevOps`