The platform splits cleanly into a data plane (Keycloak itself) and a control plane
(everything around it), with tenants packed into cells and an Aurora PostgreSQL
source of truth.
Keycloak runs as long-running containers on AWS Fargate in JVM mode (Quarkus),
clustered across Availability Zones via JDBC_PING, fronted by CloudFront (TLS,
WAF/Shield, edge cache for /resources/*) and a regional ALB. Tasks autoscale on CPU.
A cell is a self-contained Keycloak unit — a Fargate cluster + its database + its
cache in a region — bounded to a set number of realms. You scale by adding cells, and a
placement engine routes each tenant to one (by plan, region/residency, and required
capabilities such as multi-cloud).
Aurora PostgreSQL Multi-AZ (primary + synchronous replica) is the source of truth for
realms, clients, users, credentials, and — with persistent-user-sessions (KC 26 default)
— sessions. Infinispan is an in-region near-cache in front of it, not the system of
record, so a cache wipe or node loss doesn’t lose sessions. Only the database (plus tiny
cache-invalidation signals) ever crosses regions. See High Availability.
Horizontal scale for tenant count: add cells as realm counts approach the measured per-cell ceiling.
One huge realm → dedicated Fargate
A single large realm (e.g. 1M users) gets its own Fargate service sized by login rate
(~1 vCPU per 15 logins/sec — password hashing is the CPU cost), not by user count.
1M stored users is trivial; active load drives sizing.