Deployment topology¶
HOG is designed to run as N identical, stateless replicas behind a single TLS-terminating load balancer or ingress — the same coordination-free cluster model KrakenD popularized. Any replica can answer any request; there is no leader election and no required shared control plane.
Behind a trusted proxy¶
HOG speaks plain HTTP; it never terminates TLS itself. It derives the
request's external scheme, host, and client IP from X-Forwarded-Proto,
X-Forwarded-Host, and X-Forwarded-For — values that only a fronting proxy
can set. These drive the OIDC redirect URI, the session cookie's Secure
attribute, and the X-Forwarded-* chain HOG forwards on to backends. The
Gateway resource's trustedProxies field is enforced: a gateway-wide
forwarded layer (the outermost wrapper around the whole handler, applied
before request routing and before OpenTelemetry) strips X-Forwarded-For,
X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port, X-Real-Ip, and
Forwarded from any request whose immediate peer isn't in trustedProxies.
trustedProxies accepts CIDRs and bare IPs; "*" trusts every peer; the
default — an empty list — trusts no peer, so those headers are stripped
from every request unless you configure it (the secure default). In practice
this means: with no trustedProxies set, HOG only ever sees its own
direct-connection peer address, session cookies fall back to always-Secure
(since the X-Forwarded-Proto override that would otherwise permit a
non-https cookie is stripped), and the logged/projected client IP is always
the immediate peer, never the true client — set trustedProxies to your load
balancer's/ingress's CIDR to see the real client IP and scheme. Still run HOG
directly behind that proxy, with no untrusted hop in a position to spoof its
own forwarded headers — trustedProxies establishes which peer is
trusted, not that every hop before it is.
Horizontal scaling and session state¶
Because there is no sticky-session requirement, replicas scale out and back freely behind the load balancer. What differs by configuration is where the session lives:
- Default — stateless cookie. With no
stateProviderconfigured, the entire session (passport, groups, access token, expiry, fingerprint) is sealed into an encrypted,HttpOnlycookie — chunked across numbered cookies if it grows large. Any replica that holds the sharedsession.keycan decrypt any request's cookie; there is no server-side session store and no silent refresh (the caller re-authenticates when the session expires). - Opt-in — server-side state provider. When the
Gateway'sstateProviderblock is set, the cookie holds only an opaque session ID; the sealed record — including the refresh token, which is never sent to the client — lives in aStateStoremodule keyed by that ID. This unlocks silent refresh of the access token near expiry. HOG core ships no storage backend for this: theStateStoreinterface is a minimal encrypted KV-with-TTL contract, and an operator registers their own implementation (for example, a Valkey-backed one) as a plugin. A store error fails closed — the session becomes invalid rather than trusting an unreadable record.
Either way, the cluster stays coordination-free: adding a state provider adds a shared backend for sessions, not a shared control plane for the gateway itself.
Splitting login from verification¶
The replicas above are identical to each other, but a deployment can also run two different HOG configurations side by side: one instance in front of the browser, terminating the OIDC session — it runs the authorization-code flow, holds the cookie and refreshes the access token — and a second in front of the APIs, which only verifies the Bearer access token the first obtained (and accepts an identity assertion from it on a proxied hop).
The two instances need different credentials, and the configuration says so.
The verifying instance declares no session, so it mounts no login, logout
or callback route and never performs a code exchange; its IdP declares
verificationOnly: true and carries only the issuer and the client ID —
neither a client secret nor a redirect URL, since it would use neither. That
keeps the OAuth client secret in the one workload that actually spends it,
rather than in the one exposed to all the platform's API traffic. See
an instance that only verifies
tokens.
Each instance still scales to N stateless replicas on its own terms; only the session-terminating one has session state to think about.
Container images¶
HOG ships as a small set of Docker build stages:
hog-runtime— the base runtime image: agolang:1.26-alpinebuild stage compilescmd/hog, and the runtime stage is a minimal Alpine image running as a non-root user (hog, uid/gid10001). It exposes port8080— matching theGateway's defaultlistenaddress — and starts ashog --config /etc/hog. Being fully stateless, it is designed to run withdocker run --read-only --tmpfs /tmp.hog-static—hog-runtimepreconfigured with a defaultGatewayconfig that serves/srv/webas a single-page app; operators build from it and copy their compiled frontend into/srv/web.hog-builder— a build-stage-only image carrying the HOG source plus thehog-buildcomposer, used to produce a custom binary from a plugin manifest (see extensibility).
See installation & images for the full image reference and Dockerfile patterns.
Listen port and observability export¶
HOG exposes a single plain-HTTP listener at gateway.listen (default
:8080) — there is no separate metrics or admin port. Traces and metrics are
pushed via OTLP (http/protobuf or gRPC) to a collector endpoint set in the
Telemetry resource's otlp.endpoint. With no endpoint configured, W3C trace
propagation and trace/span ID allocation still happen in-process — so every
log line, including a panic caught by recover, still carries correlation
IDs — but nothing is exported off-box.