← Learn··Updated 31 May 2026·4 min read

Ingress: getting traffic in (with Traefik on k3s)

An Ingress is the cluster's HTTP front door — one entry point that routes by hostname and path to many Services. k3s ships Traefik as the controller that makes it work, and cert-manager gives you automatic HTTPS.

Kubernetes
Kubernetes from scratch with k3sPart 7 of 9
#kubernetes
#k3s
#ingress
#traefik
#ai-assisted

level:Intermediate verified:Jun 2026

ℹ️ Read-along — concepts only; nothing to install. The snippets are illustrative. The one exception is the ingress.yaml manifest below, which you can apply against your own k3s cluster if you want to follow along hands-on.

You can now expose a Service — but giving every app its own LoadBalancer means one external IP each, no HTTPS, and no way to route shop.example.com and blog.example.com to different apps on the same address. If you read the homelab series, you already met the fix in plain Docker: a reverse proxy that takes ports 80/443 and routes by hostname. Ingress is exactly that idea, native to Kubernetes.

What an Ingress is

One HTTP front door, many services

An Ingress is a Kubernetes object that defines HTTP routing rules: which hostname and which URL path should go to which Service. It is the cluster analog of a reverse proxy — one public entry point standing in front of many internal (ClusterIP) Services, dispatching each request by Host: header and path. (Kubernetes docs: Ingress)

flowchart TD
    C["Browser<br/><i>https://shop.example.com/cart</i>"] --> I["Ingress controller<br/>Traefik : 80 / 443"]
    I -->|"host shop.example.com"| S1["Service shop<br/><i>ClusterIP</i>"]
    I -->|"host blog.example.com"| S2["Service blog<br/><i>ClusterIP</i>"]
    S1 --> P1["shop pods"]
    S2 --> P2["blog pods"]

One entry point routes by hostname to internal Services; each Service load-balances across its own pods. The apps themselves never touch the public network.

Crucially, an Ingress is just a set of rules — a piece of declarative config. By itself it does nothing. Something has to read those rules and actually move packets. That something is an Ingress controller.

ℹ️ Note — An Ingress resource needs an Ingress controller to have any effect. On vanilla Kubernetes you must install one yourself (ingress-nginx, Traefik, HAProxy, etc.). On k3s you do not — it ships a controller pre-installed. (Kubernetes docs: Ingress controllers)

Traefik: the controller k3s gives you

k3s bundles Traefik as its default Ingress controller and deploys it automatically on first server start. Recent k3s (v1.32 and later) bundle Traefik v3; v1.21–v1.31 shipped Traefik v2. (k3s docs: Networking Services)

Traefik runs as a Deployment in kube-system and is itself exposed via a LoadBalancer Service — which on bare metal is handled by k3s's ServiceLB. So out of the box, ports 80 and 443 on your node land on Traefik, Traefik reads your Ingress objects, and traffic flows. You write Ingress manifests and never touch Traefik's own config for the basics.

flowchart TD
    EXT["port 80 / 443<br/><i>node external IP</i>"] --> SLB["ServiceLB<br/><i>k3s, kube-system</i>"]
    SLB --> T["Traefik<br/><i>reads Ingress rules</i>"]
    T -->|"matches Ingress"| SVC["your ClusterIP Services"]
    SVC --> PODS["your pods"]

ServiceLB exposes Traefik on the node's 80/443; Traefik turns your Ingress rules into live HTTP routing to internal Services.

A minimal Ingress routing one host to the web Service from the Services post:

ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
spec:
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

Save and apply it, then confirm Traefik picked it up:

kubectl apply -f ingress.yaml
kubectl get ingress web
NAME   CLASS     HOSTS             ADDRESS         PORTS   AGE
web    traefik   app.example.com   192.168.1.10    80      10s

Point app.example.com at your node's IP, and Traefik serves your app on port 80 — no per-app LoadBalancer, no high-numbered NodePort.

💡 Tip — Add more rules (different hosts) or more paths (e.g. /api to one Service, / to another) to put dozens of apps behind a single external IP. That consolidation is the whole point of an Ingress.
⚠️ Warning — Because k3s's Traefik already owns ports 80 and 443 via ServiceLB, do not also create your own LoadBalancer Service on those ports — they will collide on the node's host network. Route through the Ingress instead. (If you genuinely want a different controller, disable the bundled one with --disable=traefik.) (k3s docs: Networking Services)

HTTPS: TLS with cert-manager and Let's Encrypt

The Ingress above serves plain HTTP. For HTTPS you need a TLS certificate, and the standard, hands-off way to get one in Kubernetes is cert-manager — an add-on that obtains and automatically renews certificates from Let's Encrypt, storing each as a Kubernetes Secret. It is the cluster equivalent of the automatic HTTPS you got from Caddy in the reverse proxy post.

You install cert-manager, define an Issuer/ClusterIssuer pointing at Let's Encrypt, then annotate your Ingress and add a tls: block. cert-manager solves the ACME challenge, writes the cert into the named Secret, and Traefik picks it up to terminate TLS. The same ingress.yaml, now with TLS:

ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - app.example.com
      secretName: web-tls
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

The certificate lands in the web-tls Secret and renews itself — exactly the kind of thing Kubernetes' declarative model is good at. (cert-manager docs)

⚠️ Warning — Let's Encrypt must reach your cluster on a public DNS name for the HTTP-01 challenge to succeed: a real A record for app.example.com pointing at your node, with ports 80/443 reachable from the internet. If the name does not resolve to you or the ports are closed, issuance fails — get DNS and firewall right before debugging cert-manager. (Secrets, by the way, are covered in the config and storage post.)

A short close

An Ingress is a set of HTTP routing rules — host and path to Service — and it does nothing without a controller to enforce them. k3s hands you that controller, Traefik, pre-wired to ports 80/443 through ServiceLB, so you write Ingress manifests and traffic flows. Add cert-manager and you get automatic, renewing HTTPS to match. With pods, Deployments, Services, and Ingress in hand, you have the full request path — next, give your app its config and durable data in config, secrets, and storage. The whole path is in the k3s series hub.