Skip to content

Commit 37f8868

Browse files
committed
feat(lab-06): Keycloak production -- 2-node HA cluster, PG, Redis, Traefik LB
1 parent 082cfa6 commit 37f8868

3 files changed

Lines changed: 283 additions & 98 deletions

File tree

.github/workflows/ci.yml

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,9 @@ jobs:
3333
echo "Validating: docker/docker-compose.integration.yml"
3434
docker compose -f docker/docker-compose.integration.yml config -q
3535
echo "OK: docker/docker-compose.integration.yml"
36-
for f in docker/docker-compose.production.yml; do
37-
echo "Checking scaffold: $f"
38-
docker compose -f "$f" config --no-interpolate -q 2>&1 && echo "OK: $f" \
39-
|| echo "WARN: $f has placeholder variables (scaffold — not yet built out)"
40-
done
36+
echo "Validating: docker/docker-compose.production.yml"
37+
docker compose -f docker/docker-compose.production.yml config -q
38+
echo "OK: docker/docker-compose.production.yml"
4139
4240
- name: ShellCheck — lab test scripts
4341
run: |
@@ -250,4 +248,34 @@ jobs:
250248

251249
- name: Cleanup
252250
if: always()
253-
run: docker compose -f docker/docker-compose.integration.yml down -v
251+
run: docker compose -f docker/docker-compose.integration.yml down -v
252+
253+
lab-06-smoke:
254+
name: Lab 06 — Keycloak Production HA (2-node cluster, Traefik LB, PG, Redis)
255+
runs-on: ubuntu-latest
256+
needs: validate
257+
continue-on-error: true
258+
steps:
259+
- uses: actions/checkout@v4
260+
261+
- name: Install tools
262+
run: sudo apt-get install -y curl
263+
264+
- name: Start production stack
265+
run: docker compose -f docker/docker-compose.production.yml up -d
266+
267+
- name: Wait for Keycloak cluster (both nodes via Traefik)
268+
run: timeout 300 bash -c 'until curl -sf http://localhost:8080/health/ready | grep -q UP; do sleep 5; done'
269+
270+
- name: Run Lab 02-06 test script
271+
env:
272+
KC_PASS: "Lab06Password!"
273+
run: bash tests/labs/test-lab-02-06.sh
274+
275+
- name: Collect logs on failure
276+
if: failure()
277+
run: docker compose -f docker/docker-compose.production.yml logs
278+
279+
- name: Cleanup
280+
if: always()
281+
run: docker compose -f docker/docker-compose.production.yml down -v
Lines changed: 149 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,166 @@
1-
# Lab 06 Production: keycloak HA-ready with monitoring and external volumes
1+
# Lab 06 -- Production: Keycloak HA cluster with load balancing, PostgreSQL, Redis, Traefik
22
---
33
services:
4-
keycloak:
5-
image: quay.io/keycloak/keycloak:24
6-
container_name: it-stack-keycloak
4+
kc-db:
5+
image: postgres:16-alpine
6+
container_name: it-stack-kc-db-prod
77
restart: always
8-
ports:
9-
- "8080:$firstPort"
108
environment:
11-
- IT_STACK_ENV=production
12-
- KEYCLOAK_URL=
13-
- DB_HOST=
14-
- REDIS_HOST=
15-
- GRAYLOG_HOST=
9+
POSTGRES_DB: keycloak
10+
POSTGRES_USER: keycloak
11+
POSTGRES_PASSWORD: "Lab06Password!"
1612
volumes:
17-
- keycloak_data:/var/lib/keycloak
18-
- /etc/ssl/certs:/etc/ssl/certs:ro
13+
- kc-db-prod-data:/var/lib/postgresql/data
14+
healthcheck:
15+
test: ["CMD-SHELL", "pg_isready -U keycloak -d keycloak"]
16+
interval: 10s
17+
timeout: 5s
18+
retries: 10
19+
networks:
20+
- kc-db-prod-net
1921
deploy:
20-
replicas: 1
2122
resources:
2223
limits:
23-
cpus: "4.0"
24-
memory: G
25-
reservations:
2624
cpus: "1.0"
2725
memory: 1G
28-
restart_policy:
29-
condition: any
30-
delay: 5s
31-
logging:
32-
driver: gelf
33-
options:
34-
gelf-address: "udp://${GRAYLOG_HOST}:12201"
35-
tag: "it-stack-keycloak"
26+
27+
redis:
28+
image: redis:7-alpine
29+
container_name: it-stack-kc-redis-prod
30+
restart: always
31+
command: redis-server --requirepass Lab06Password! --appendonly yes --maxmemory 256mb
32+
volumes:
33+
- kc-redis-prod-data:/data
34+
healthcheck:
35+
test: ["CMD", "redis-cli", "-a", "Lab06Password!", "ping"]
36+
interval: 5s
37+
timeout: 3s
38+
retries: 10
39+
networks:
40+
- kc-prod-net
41+
deploy:
42+
resources:
43+
limits:
44+
cpus: "0.5"
45+
memory: 384M
46+
47+
keycloak-1:
48+
image: quay.io/keycloak/keycloak:26.0
49+
container_name: it-stack-keycloak-1
50+
restart: always
51+
environment:
52+
KC_DB: postgres
53+
KC_DB_URL: jdbc:postgresql://kc-db:5432/keycloak
54+
KC_DB_USERNAME: keycloak
55+
KC_DB_PASSWORD: "Lab06Password!"
56+
KC_BOOTSTRAP_ADMIN_USERNAME: admin
57+
KC_BOOTSTRAP_ADMIN_PASSWORD: "Lab06Password!"
58+
KC_HTTP_ENABLED: "true"
59+
KC_HOSTNAME_STRICT: "false"
60+
KC_PROXY_HEADERS: xforwarded
61+
KC_CACHE: local
62+
KC_HTTP_PORT: 8080
63+
KC_NODE_NAME: keycloak-1
64+
command: start --optimized
65+
depends_on:
66+
kc-db:
67+
condition: service_healthy
3668
healthcheck:
37-
test: ["CMD-SHELL", "curl -sf http://localhost/health || exit 1"]
38-
interval: 30s
39-
timeout: 10s
40-
retries: 3
69+
test: ["CMD-SHELL", "curl -sf http://localhost:8080/health/ready | grep -q UP || exit 1"]
70+
interval: 10s
71+
timeout: 5s
72+
retries: 30
4173
start_period: 120s
4274
networks:
43-
- it-stack-net
75+
- kc-prod-net
76+
- kc-db-prod-net
77+
labels:
78+
- "traefik.enable=true"
79+
- "traefik.http.routers.keycloak.rule=PathPrefix(`/`)"
80+
- "traefik.http.routers.keycloak.entrypoints=web"
81+
- "traefik.http.services.keycloak.loadbalancer.server.port=8080"
82+
- "traefik.http.services.keycloak.loadbalancer.healthcheck.path=/health/ready"
83+
- "traefik.http.services.keycloak.loadbalancer.healthcheck.interval=15s"
84+
deploy:
85+
resources:
86+
limits:
87+
cpus: "2.0"
88+
memory: 2G
89+
90+
keycloak-2:
91+
image: quay.io/keycloak/keycloak:26.0
92+
container_name: it-stack-keycloak-2
93+
restart: always
94+
environment:
95+
KC_DB: postgres
96+
KC_DB_URL: jdbc:postgresql://kc-db:5432/keycloak
97+
KC_DB_USERNAME: keycloak
98+
KC_DB_PASSWORD: "Lab06Password!"
99+
KC_BOOTSTRAP_ADMIN_USERNAME: admin
100+
KC_BOOTSTRAP_ADMIN_PASSWORD: "Lab06Password!"
101+
KC_HTTP_ENABLED: "true"
102+
KC_HOSTNAME_STRICT: "false"
103+
KC_PROXY_HEADERS: xforwarded
104+
KC_CACHE: local
105+
KC_HTTP_PORT: 8080
106+
KC_NODE_NAME: keycloak-2
107+
command: start --optimized
108+
depends_on:
109+
kc-db:
110+
condition: service_healthy
111+
healthcheck:
112+
test: ["CMD-SHELL", "curl -sf http://localhost:8080/health/ready | grep -q UP || exit 1"]
113+
interval: 10s
114+
timeout: 5s
115+
retries: 30
116+
start_period: 120s
117+
networks:
118+
- kc-prod-net
119+
- kc-db-prod-net
120+
labels:
121+
- "traefik.enable=true"
122+
- "traefik.http.routers.keycloak.rule=PathPrefix(`/`)"
123+
- "traefik.http.routers.keycloak.entrypoints=web"
124+
- "traefik.http.services.keycloak.loadbalancer.server.port=8080"
125+
deploy:
126+
resources:
127+
limits:
128+
cpus: "2.0"
129+
memory: 2G
130+
131+
traefik:
132+
image: traefik:v3.3
133+
container_name: it-stack-kc-traefik-prod
134+
restart: always
135+
ports:
136+
- "8080:80"
137+
- "8081:8081"
138+
command:
139+
- --api.dashboard=true
140+
- --api.insecure=true
141+
- --api.dashboard.entrypoint=dashboard
142+
- --providers.docker=true
143+
- --providers.docker.exposedbydefault=false
144+
- --entrypoints.web.address=:80
145+
- --entrypoints.dashboard.address=:8081
146+
- --log.level=INFO
147+
volumes:
148+
- /var/run/docker.sock:/var/run/docker.sock:ro
149+
networks:
150+
- kc-prod-net
151+
depends_on:
152+
keycloak-1:
153+
condition: service_healthy
154+
keycloak-2:
155+
condition: service_healthy
44156

45157
networks:
46-
it-stack-net:
47-
external: true
48-
name: it-stack-production
158+
kc-prod-net:
159+
driver: bridge
160+
kc-db-prod-net:
161+
driver: bridge
162+
internal: true
49163

50164
volumes:
51-
keycloak_data:
52-
external: true
53-
name: it-stack-keycloak-data
165+
kc-db-prod-data:
166+
kc-redis-prod-data:

0 commit comments

Comments
 (0)