Skip to content

The single edge: Traefik + cert-manager + DNS

All inbound HTTPS enters through one door: Traefik, the Ingress controller bundled with k3s. Nothing else listens on public 80/443. cert-manager obtains and renews TLS. Run kubectl on the node, or locally once set up — Access.

flowchart LR
  B([Browser]) -->|DNS lookup| DNS[(public A record)]
  DNS -->|node public IP| N[node :443]
  N --> T[Traefik<br/>terminates TLS]
  T -->|by hostname| Svc[Service]
  Svc --> P[Pod]

A request to api.<domain> resolves to the node’s public IP, hits :443, and Traefik terminates TLS and routes by hostname. Each Ingress declares its host, entrypoint, and the cert-manager issuer:

metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
ingressClassName: traefik
tls:
- hosts: [api.<domain>]
secretName: <app>-tls

Backends serve plain HTTP behind Traefik — e.g. Keycloak runs httpEnabled: true and trusts X-Forwarded-* (proxy.headers: xforwarded).

cert-manager solves Let’s Encrypt’s HTTP-01 challenge through Traefik on port 80:

  1. The public DNS A record for the hostname points at the node’s public IP.
  2. Port 80 is reachable from the internet (cloud firewall / VCN).

Let’s Encrypt fetches http://<host>/.well-known/... and stores the cert in the Ingress Secret (<app>-tls, keycloak-tls). cert-manager renews it automatically.

flowchart TD
  H1[ingress hosts<br/>control / vault / auth / app APIs] -->|public IP| Edge[node :443 Traefik]
  H2[kubectl: node or local] --> API[kube API]
  • Ingress hostnames → public IP. On this company domain: control.webbies.dev (Argo), vault.webbies.dev (Infisical), uat.auth.webbies.dev / auth.webbies.dev (Keycloak), plus each app API host. An app’s web frontend may live off-cluster (e.g. Vercel).
  • kubectl → on the node, or locally over Tailscale (Access). Either way, you do not need 6443 open on the public firewall.