Running locally over HTTPS
Why
Browsers (notably Safari) cache an HSTS rule that forces https://localhost, and they won't load
a plain-HTTP site or trust an untrusted certificate. Running the stack over plain HTTP also means
the app's Secure auth cookie is dropped, so login bounces. Rather than hand-edit the source to
weaken cookie/OIDC security (which causes a divergent working tree), we run the local stack the
same way production does: behind a TLS-terminating Caddy reverse
proxy, with a locally-trusted certificate.
Behind HTTPS-terminating Caddy the app sees Request.IsHttps == true (via the already-committed
UseForwardedHeaders middleware), so the normal production cookie and OIDC defaults just work.
One command
./scripts/dev-https.sh
This will (idempotently):
- install
mkcertif needed (brew install mkcert nss), - run
mkcert -install— a one-time step that adds a local Certificate Authority to your login keychain (it may prompt for your password). This is what makes the cert trusted; it's the only manual step, and only the first time, - generate
certs/local.pem/certs/local-key.pem(git-ignored), - build and start the stack behind Caddy.
Then open https://localhost:11401 — no certificate warning, no HSTS dead-end.
| URL | What |
|---|---|
https://localhost:11401 |
App admin UI — admin@riptide.local / Admin@2026! |
https://localhost:11403 |
Keycloak — admin / admin |
http://localhost:11402 |
REST API (X-Api-Key) — unchanged, for SDK apps / the KMS stack |
Stop with ./scripts/dev-https.sh down.
Local login is password-based (
admin@riptide.local, above). Keycloak runs in the stack and the OIDC flow is wired correctly for HTTPS (issuer/redirect overhttps://localhost:11403), but no "Sign in with SSO" button appears on the login page by default: the login page lists only databaseIdentityProviderrows, and the local Keycloak is registered via config, not seeded as a DB row. Local SSO isn't needed for development — SSO is demoed against Azure Entra ID. If you do want the local button, add a Keycloak provider under Identity Providers in the admin UI (Authority http://keycloak:8080/realms/riptide, clientapplication-manager, secretriptide-keycloak-secret).
How it works
Browser ──HTTPS──► Caddy (trusted mkcert cert)
├─ https://localhost:11401 ─► application-manager:11401 (Web)
│ └─ /api/* ─► application-manager:11402 (API)
└─ https://localhost:11403 ─► keycloak:8080 (Keycloak)
App ──internal HTTP──► keycloak:8080 (OIDC discovery / token / jwks back-channel)
- Files:
docker-compose.https.yml(overrides the base compose),scripts/caddy/local.caddy,scripts/dev-https.sh. - OIDC issuer is HTTPS, back-channel is internal HTTP. The browser-facing Keycloak issuer is
https://localhost:11403(KC_HOSTNAME_URL), so SSO redirects and tokenissare HTTPS. The app still reaches Keycloak's discovery/token/jwks endpoints over the trusted Docker network athttp://keycloak:8080— no split-horizon DNS and no need to trust the cert inside the container. That internal-HTTP authority is allowed viaIdentityProviders__0__RequireHttpsMetadata=false(a per-provider config option; the default staystrue, so production is unaffected). - No source edits: the cookie
SecurePolicyand OIDCRequireHttpsMetadatadefaults are the committed production values.
Caveats
- Requires Docker Compose v2.24+ (the override uses the
!resetmerge tag to drop the base-published ports that Caddy takes over). - Existing Keycloak volume:
--import-realmonly seeds a freshkeycloak-datavolume. If you ran the stack before this change, the realm's redirect URIs won't include the HTTPS origin. Refresh withdocker compose -f docker-compose.yml -f docker-compose.https.yml down -v(wipes Keycloak data and re-imports) or addhttps://localhost:11401/*to theapplication-managerclient in the Keycloak admin console. - Playwright/headless Chromium doesn't read the mkcert CA from your keychain; pass
ignoreHTTPSErrors: truefor automated tests. Your normal Safari/Chrome do trust it.
Pulling the published image instead of building
After this lands on development, CI publishes ghcr.io/riptide-solutions/application-manager:development.
To run that image over HTTPS instead of building locally, docker pull it and point the
application-manager service's image: at it (dropping build:), then use the same
docker-compose.https.yml override + scripts/dev-https.sh.
Alternative: single URL
This setup uses two trusted origins (app :11401, Keycloak :11403). A single-origin variant —
Keycloak under https://localhost:11401/idp via KC_HTTP_RELATIVE_PATH — is possible but needs
Caddy path-routing and is more fragile; the two-origin model is preferred for local dev.