Keycloak¶
dev.rightsize.modules.KeycloakContainer — a single-node Keycloak container in
start-dev mode (in-memory H2, no external database — fine for tests, never for
production).
Defaults¶
| Default image | quay.io/keycloak/keycloak:26.4 |
| Exposed ports | 8080 (HTTP / auth server), 9000 (management — health lives here, see below) |
| Command | start-dev |
| Env | KC_BOOTSTRAP_ADMIN_USERNAME=admin, KC_BOOTSTRAP_ADMIN_PASSWORD=admin, KC_HEALTH_ENABLED=true |
| Memory limit | withMemoryLimit(1024) — see below |
| Wait strategy | Wait.forHttp("/health").forPort(9000).withStartupTimeout(Duration.ofSeconds(180)) |
Helpers¶
| Member | Returns |
|---|---|
authServerUrl: String |
The auth server's base URI (HTTP port) — realm/OIDC endpoints live under this |
managementUrl: String |
The management interface's base URI (health/metrics — port 9000, a different port than authServerUrl) |
adminUsername / adminPassword: String |
The configured bootstrap admin credentials (default admin/admin) |
withAdminUsername(username: String): KeycloakContainer |
Overrides KC_BOOTSTRAP_ADMIN_USERNAME |
withAdminPassword(password: String): KeycloakContainer |
Overrides KC_BOOTSTRAP_ADMIN_PASSWORD |
Call the withX overrides before start().
Example¶
package dev.rightsize.modules
import dev.rightsize.modules.KeycloakContainer
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
class KeycloakContainerTest {
private val http = HttpClient.newHttpClient()
@Test
fun `OIDC discovery document asserts the issuer`() {
val keycloak = KeycloakContainer()
keycloak.start()
try {
val resp = http.send(
HttpRequest.newBuilder(
URI("${keycloak.authServerUrl}/realms/master/.well-known/openid-configuration"),
).GET().build(),
HttpResponse.BodyHandlers.ofString(),
)
assertEquals(200, resp.statusCode(), "discovery document fetch failed: ${resp.body()}")
assertTrue(
resp.body().contains("\"issuer\":\"${keycloak.authServerUrl}/realms/master\""),
"unexpected issuer in discovery document: ${resp.body()}",
)
} finally {
keycloak.stop()
}
}
}
Backend notes¶
Admin bootstrap env names changed in 26.x — verified against the pinned tag.
Keycloak 26.x replaced the older KEYCLOAK_ADMIN/KEYCLOAK_ADMIN_PASSWORD pair with
KC_BOOTSTRAP_ADMIN_USERNAME/KC_BOOTSTRAP_ADMIN_PASSWORD. Verified directly against
quay.io/keycloak/keycloak:26.4: booting with only the new names produces
KC-SERVICES0077: Created temporary admin user with username admin in the log, and
the legacy names are not read by this image at all. If you're overriding the image to
an older tag, check which pair it actually reads before assuming these overrides
apply.
Health lives on the management port (9000), not the HTTP port (8080) — and the path
is /health, not /health/ready. Captured verbatim from a real boot:
With KC_HEALTH_ENABLED=true set, GET /health on port 9000 returns 200 OK
(literal body OK). The same path on 8080 404s, and the commonly assumed
/health/ready sub-path also 404s on this tag — this image's SmallRye Health root
aggregate is served bare at /health, not under a /ready sub-path. This module's
wait strategy is pinned to /health on 9000 accordingly.
Memory needed the same ladder as Spring Cloud Config's Paketo JVM. Booted under
microsandbox's default ~450 MB microVM RAM, the Quarkus JVM gets OOM-killed partway
through startup (captured: 'java' ... -XX:MaxRAMPercentage=70 ... Killed) — the same
Paketo/Quarkus-on-microVM story as
Spring Cloud Config. Retried with -m 1024M:
boots clean, /health reports 200 well within the startup timeout. This module
ships with withMemoryLimit(1024) for exactly this reason.