Build stage pinned to BUILDPLATFORM and GOARCH driven by TARGETARCH, so an amd64 image builds natively from an arm64 workstation instead of running the Go toolchain under QEMU. TARGETARCH defaults to amd64 — every cluster node is amd64, and a plain docker build on arm64 would otherwise produce an image the nodes cannot run.
homelab-frontend
A Go API gateway for the homelab cluster. One capability per subdomain, one auth implementation, one routing table.
Replaces Kong OSS entirely — see ADR-0001 for why, and docs/MIGRATION-kong.md for the cutover.
Position in the stack
browser / SDK ──▶ Cloudflare ──▶ ingress-nginx (TLS, edge)
│
▼
┌─────────────────────────────┐
│ homelab-frontend │
│ routing · authn · budgets │
└──────────────┬──────────────┘
│
/v1 /sqs /workflow /cluster
│ │ │ │
▼ ▼ ▼ ▼
llm-serving kmsvc/Kafka temporal atlas
(predictors) (sqs ns) (temporal ns) (riotpiao-backend)
└──── in-cluster Services ────┘
ingress-nginx keeps TLS and the edge. The gateway owns everything after it.
Backend services are reached through the gateway rather than published individually — a single place for authentication, budgets, timeouts and observability, and a single hostname surface to reason about.
Capability map
One host, one path prefix per capability.
Prefix on api.riotpiao.com |
Backs onto | Status |
|---|---|---|
/v1/* |
llm-serving predictors (vLLM, Ollama, TEI) |
migrating off Kong |
/sqs/* |
kmsvc management-service + Kafka/Strimzi (sqs ns) |
future |
/workflow/* |
Temporal (temporal ns) |
future |
/cluster/* |
atlas — cluster topology / Argo delivery (separate repo) | future |
/db/* |
CloudNativePG, MinIO, monitoring/metrics reads | future |
/v1/* is reserved for the OpenAI-compatible surface. An SDK expects
/v1/chat/completions at the base URL, so that prefix cannot be repurposed.
Paths rather than subdomains: one DNS record, one tunnel hostname, one Ingress. Promoting a prefix to its own subdomain later is additive and can run alongside the path — the reverse is not, because clients hardcode hostnames.
atlas lives in its own repo (riotpiao-backend) and keeps its own informers and
RBAC. The gateway routes to it; it does not absorb it. Cluster-read permissions
stay out of the public edge process.
Design rules
- Standard protocol shapes.
POST /v1/chat/completionsselects its model from the request body, like every OpenAI-compatible server. No path-per-model, no bespoke client configuration. Kong OSS could not do this; that limitation does not survive into the replacement. - Bearer tokens, validated against Authentik. JWKS is fetched at runtime and cached, so key rotation needs no runbook and no pinned PEM.
- Policy lives where the state is. GPU slot semaphores, per-caller budgets, queue depth and disconnect propagation are application concerns. They belong here, not in a proxy plugin.
- Streaming is first-class. SSE and WebSocket pass through unbuffered, and a client disconnect cancels the upstream request rather than orphaning it.
- The gateway holds no cluster credentials. It proxies to services that do.
Layout
cmd/gateway/ entrypoint
internal/
auth/ Authentik OIDC, JWKS cache, service-account tokens
llm/ model registry, body-based dispatch, upstream map
queue/ sqs.riotpiao.com surface
workflow/ workflow.riotpiao.com surface
proxy/ reverse proxy, streaming, timeouts, disconnect propagation
config/ upstream + route configuration
observability/ Prometheus metrics, structured logging
deploy/
base/ Kubernetes manifests
argocd/ Argo Application
docs/adr/ architecture decision records
tasks/ task board — see tasks/INDEX.md
testdata/ fixtures for offline tests
Local development
The gateway must be runnable with no cluster, no kubeconfig and no credentials, so that changes can be verified in a closed loop before touching live traffic. Upstreams are configuration, so pointing them at local stubs is the whole mechanism. See tasks/INDEX.md.
Status
Scaffolded 2026-08-19. Nothing is wired yet. Kong is still serving live traffic on
api.riotpiao.com.