shc / arch / exposure-ladder ← lab docs

Who can reach my app?

An shc install sets two independent dials — access (open by default, --gated) and exposure (lan by default, --public). They do not stack into a ladder: every one of the four cells below is legal. The request path never changes; only the place a request dies moves. Drag an origin along its wire toward the app — it stops where this posture stops it.

posture access=open exposure=lan tls: shc leaf
Request path: an off-network client resolving through public DNS, a LAN client, and a caller already on the node — all converging on Traefik, the shc-oidc gate and the app container Three draggable request origins converge on one path. An off-network client must first resolve the hostname in public DNS; when exposure is lan there is no public record and the request dies at name resolution, not at a packet filter, with no HTTP status at all. A LAN client reaches Traefik directly. A caller who already has a route to the node — the plainest case being one already on the node — sends the right Host header, skips DNS entirely and still reaches Traefik regardless of exposure. Past Traefik, gated deployments meet the shc-oidc forward-auth checkpoint backed by Keycloak, which answers 401 without a session, before the app container. internet lan node · shc your edge · no packet filter client off-network public dns lan fallback client on-lan any caller already on the node sends Host: traefik file router Host(`…`) keycloak ungated · /auth shc-oidc forward-auth not chained nextcloud container :80 no public record no http status — never left the resolver session required 401
off-net
on lan
on node
posture · pick a cell 0 / 3 origins sent
what this cell installs shc cli

simulated Mirrors internal/modules/app/inject_traefik.go, internal/modules/app/dns_target.go, internal/modules/app/commands/install.go and forward-auth/cmd/forward-auth/main.go. No live cluster; packet timing is illustrative.
The honest part: exposure is not a firewall — SHC installs no iptables or nftables rule for it. What --public changes is the managed-DNS target and the cert posture. exposure=internet points the record at the Traefik node's public_address and fails closed (X200980DNSDEF, record deferred) rather than ever publishing an RFC1918 address; other exposures keep the LAN fallback — the Traefik node's internal address, else all swarm nodes / the advertise address. That is why the off-network request dies at the name layer above, and why the faint any caller path still arrives: anyone who already has a route to the node and sends the right Host header reaches the router regardless of exposure.
Two axes, not a ladder: --gated and --open are mutually exclusive (MarkFlagsMutuallyExclusive("gated","open")) and open is the default; --public is independent of both, which is why all four cells above are legal. --public raises exposure only — it never skips the SSO gate. The ungated warning (ShouldWarnUngatedX200950APPUGW) fires on access=open plus self_auth: false and does not read exposure at all, so a LAN-only open app warns too.
Ingress exists only when --host is passed — with no --host there is no Traefik router at all. Docs: docs.