Every public OpsAPI host, from the demo identity service to this website, goes through the same edge: a DNS record that only points the way, an edge proxy that terminates TLS and chooses a backend, and the Kubernetes ingress that hands the request to a pod. Knowing which layer owns what saves a lot of time when something returns the wrong status.

The path, hop by hop

Request path from the browser through DNS, an edge PoP and the cluster ingress to the podBrowserhttps://…Cloudflare DNSCNAME, DNS-onlylookupEdge PoP ATLS + rulesEdge PoP Bmirror of AHTTPS to the IP DNS gaveresolves to A or BIngressby Host headerplain HTTPPodOpsAPI
DNS only resolves the name; it never carries traffic. TLS ends at the edge PoP, which proxies plain HTTP to the cluster ingress.
LayerOwnsWhere to look when it breaks
Cloudflare DNSName → edge. A CNAME per host, DNS-only (grey cloud)dig the host; it must return the edge, not a Cloudflare proxy IP
Edge PoP (wslproxy)TLS certificate, server + rule records, headers, rate limitsResponse headers; the PoP’s access and error log for the host
Ingress (traefik)Host → Servicekubectl get ingress; a 404 “page not found” is the ingress
PodThe application/ready and the pod log

Why DNS-only

The records are not proxied by Cloudflare. The edge issues its own Let’s Encrypt certificate per host, and the HTTP-01 challenge has to reach the edge on port 80. A proxied record would also put a second TLS hop and a second set of limits in front of the edge. The cost is that the edge’s addresses are public, so the edge is also where per-host WAF policies and rate limits are switched on.

identity.demo   300  IN  CNAME  <edge hostname>.   ; DNS-only
dashboard.demo  300  IN  CNAME  <edge hostname>.
opsapi.org      300  IN  CNAME  <edge hostname>.   ; apex: CNAME flattening

Inside the edge: servers and rules

The edge is wslproxy, an OpenResty gateway. A server record describes a hostname: its listeners, TLS settings, extra upstream headers and which rules apply. A rule says what to do with a matching request. Rules are read from disk on every request, so changing one needs no reload.

How an edge PoP picks a rule and acts on itServer JSONhost:<fqdn>TLS, headersCandidate rulesrules linkedto the serverMatch + selectpath, IP, countrypriority winsResponsecode 305= proxy passBackendingress orPagesties: priority, then path specificity, then condition count, then rule idread from disk on every request: a rule change needs no reload
One request, inside one PoP.
{
  "id": "host:identity.demo.opsapi.org",
  "server_name": "identity.demo.opsapi.org",
  "rules": "opsapi-demo-default",
  "listens": [{ "listen": "80" }],
  "ssl_enabled": true,
  "ssl_force_https": true,
  "ssl_auto_renew": true,
  "custom_headers": []
}
{
  "id": "opsapi-demo-default",
  "priority": 1,
  "match": {
    "rules": { "path": "/", "path_key": "starts_with" },
    "response": {
      "code": 305,
      "redirect_uri": "<cluster ingress>:<port>",
      "backends": [{ "address": "<cluster ingress>:<port>", "weight": 100 }],
      "routing": { "mode": "least_conn" }
    }
  },
  "servers": ["host:identity.demo.opsapi.org", "host:dashboard.demo.opsapi.org"]
}
Rule codeWhat the edge does
305Proxy pass to the backend (the normal case)
301 / 302Redirect, e.g. www → apex
200 / 403Serve a stored HTML body, with that status
306CAPTCHA challenge, then proxy

One rule can serve many hosts because the ingress routes by the Host header: all the demo hosts share a single rule pointing at the cluster ingress.

Headers set at the edge apply to every request. We once had the edge force X-Namespace-Slug: demo on the identity host. It pinned every API call to one workspace, and when a second workspace was added, every request to it was refused. Tenant selection belongs in the client, not the proxy.

Edge records are GitOps

Server and rule files live in the repo under .github/wslproxy/data/. A workflow validates them (every rule a server references must exist), then imports them into the edge control plane with their ids preserved, and takes them live. The second PoP mirrors the first.

Edge records flow from git through a workflow to the PoPsRepo filesservers/prod/*.jsonrules/prod/*.jsonWorkflowvalidate, thenimport (same ids)Control planeservers + rulesconfig_statusPoPs A and BB mirrors A
A new hostname is a pull request: one server file and either a new rule or the host added to an existing rule's servers list.
  1. Add servers/prod/host:<fqdn>.json and point its rules at a rule id.
  2. Add the host to that rule’s servers list, or add a new rule file.
  3. Create the DNS-only CNAME to the edge.
  4. Run the workflow (dry run first), then check the certificate and a curl -I.

opsapi.org: a static site behind the same edge

This website is a Next.js static export published to GitHub Pages from the website/ folder of the bwalia/opsapi repo. Pages has no custom domain configured. The edge owns opsapi.org and www.opsapi.org, terminates TLS, and proxies to https://bwalia.github.io/opsapi, prefixing the repo path to every request.

opsapi.org served from GitHub Pages through the edgeopsapi.org/demo/browser requestEdge PoPTLS for opsapi.orgrule: 305 tobwalia.github.io/opsapipath prefixedGitHub Pages/opsapi/demo/Host: bwalia.github.iono custom domainHTMLstatica custom domain on Pages would redirect back to opsapi.org: a loop
Because the edge adds /opsapi to every path, the site is built without a basePath and its links stay root-relative.
  • No custom domain on Pages. If Pages knew about opsapi.org it would redirect requests for the github.io address back to opsapi.org, which comes back through the edge: a loop.
  • No basePath. The edge already maps /x to /opsapi/x, so /_next/… assets resolve. The flip side is that the raw github.io URL won’t render correctly; that’s fine, nobody should use it.
  • Trailing slashes. The export writes /demo/index.html; links use /demo/ so Pages serves the file without a redirect.

Checks that catch most problems

dig +short identity.demo.opsapi.org            # the edge, not a Cloudflare proxy address
curl -sI https://identity.demo.opsapi.org/ready  # 200 from the app
curl -sI https://opsapi.org/                     # 200, served by the edge from Pages
curl -s  https://opsapi.org/ | grep -o '<title>[^<]*'

A bare 404 page not found comes from the ingress (no route for that Host). A 502 or 504 with the edge’s own page means the edge couldn’t reach the backend. A certificate for the wrong name means the host isn’t registered at the edge yet, or DNS points somewhere else.