Strait of Duck Gateway
One command, go-duck create-gateway, scaffolds a standalone service that discovers every GO-DUCK microservice running in your cluster, funnels traffic to them through a single address, and gives operators a Keycloak-gated view of the whole fleet โ pod counts, live metrics, Swagger docs โ without touching your existing apps.
Zero-Config Discovery
Polls the Kubernetes API for Services carrying goduck.io/managed=true โ a label every generated app's Deployment and Service already carries. Nothing to register by hand.
Two-Port Isolation
An admin port carries the UI and topology data; a second, narrower proxy-only port carries nothing but the reverse proxy โ safe to hand a developer without exposing your fleet's map.
No Database, Ever
The discovery cache is a plain in-memory map, rebuilt from the Kubernetes API on every poll. The cluster is the source of truth โ this is just a cache in front of it.
How It Works
Discover
Every discovery.poll-interval (default 15s), the gateway lists Services and Deployments cluster-wide, filtered by label, and counts each one's live ready-replica pods.
// Failed poll? Keep serving the last-known-good snapshot -- // same resilience idiom as DuckGuard ACL's cache. services, err := clientset.CoreV1().Services("").List(ctx, metav1.ListOptions{LabelSelector: "goduck.io/managed=true"})
Proxy
Requests forward byte-for-byte to the discovered service's ClusterIP โ no header injected, no token checked by the gateway itself. Each downstream service's own JWT/DuckGuard ACL middleware still decides access, exactly as if it had been hit directly.
// No auth imposed here on purpose -- see "Never the Chokepoint" below
target := &url.URL{Scheme: "http", Host: info.ClusterIP + ":" + port}
httputil.NewSingleHostReverseProxy(target).ServeHTTP(w, r)
Observe
Two Keycloak-gated pages, each with a live service-picker dropdown: a Swagger switcher that server-side-proxies each service's own docs, and a Metrics page showing pod count per service plus a drill-down into any one service's live telemetry.
// The browser only ever talks to the gateway -- never a // downstream ClusterIP it usually couldn't reach anyway. GET /api/services/:name/swagger.json GET /api/services/:name/metrics
Two Ports, One Purpose Each
Admin Port server.port ยท default 8080
Carries everything: the Keycloak-gated UI, the /api/services* JSON API, and the reverse proxy. Both UI pages force a login (keycloak-js, onLoad: 'login-required') before rendering, and the JSON endpoints underneath carry real server-side JWT validation โ curling them without a token still 401s.
GET /api/services ยท /proxy/*
Proxy-Only Port server.proxy-only-port ยท default 8081
Carries only /health and the reverse proxy. Not the admin port with auth "turned off" โ the UI and API handlers are never registered on this engine at all, so there's nothing there to bypass. Safe to kubectl port-forward to a developer who needs one service without your whole fleet's topology. Set to 0 to disable.
proxy-only-port: 8081
Never the Chokepoint
By Design, Not Oversight.
Making the reverse proxy an auth chokepoint was considered and deliberately rejected โ it would double-auth every request and silently break any route a service intentionally left public: an open Car(read) GDL directive, a health check. The gateway funnels the connection; it never decides who gets through.
Cluster-Scoped RBAC & Tiered Discovery
Why Cluster-Scoped
Every GO-DUCK app deploys into its own dedicated namespace named after the app โ never a shared one. A namespaced Role couldn't see across them, so go-duck create-gateway ships its own read-only ClusterRole (get/list/watch on services, endpoints, pods, deployments โ nothing else) and ClusterRoleBinding.
Scoping to One Tier
Every app also carries a goduck.io/environment label, sourced from go-duck.environment-tag in its own config.yaml (default "prod"). Point a gateway at just one tier via discovery.environment โ blank scans every tier it can see.
environment: "staging"
Give Your Fleet a Front Door
One command scaffolds the whole thing โ its own go.mod, Dockerfile, and Kubernetes manifests included.
go-duck create-gateway -c CONFIG/strait-of-duck-config/config.yaml -o my-gateway
cd my-gateway && go run main.go
# Admin port: http://localhost:8080/ui/swagger
# Proxy-only port: http://localhost:8081/proxy/<service>/...