Traefik, when acting as your Kubernetes ingress controller, can dynamically route traffic to your services based on annotations you define directly on those services.

Let’s see it in action. Imagine you have a simple web application deployed as a Kubernetes Deployment and exposed via a Service:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app-container
        image: nginxdemos/hello:plain-text
        ports:
        - containerPort: 80

---
apiVersion: v1
kind: Service
metadata:
  name: my-app-service
  labels:
    app: my-app
spec:
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

By default, Traefik wouldn’t know to route traffic to my-app-service just because it exists. You need to tell Traefik how to discover and route to it. This is where annotations on the Service object come into play.

Here’s how you’d annotate the my-app-service to make Traefik route traffic for the host myapp.example.com to this service on port 80:

apiVersion: v1
kind: Service
metadata:
  name: my-app-service
  labels:
    app: my-app
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
    traefik.ingress.kubernetes.io/router.tls: "true"
    traefik.ingress.kubernetes.io/router.tls.certresolver: myresolver
    traefik.ingress.kubernetes.io/router.rule: Host(`myapp.example.com`)
spec:
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

With these annotations, Traefik will:

  1. Discover the Service: Traefik continuously watches Kubernetes API for Services.
  2. Identify Traefik Annotations: It looks for annotations prefixed with traefik.ingress.kubernetes.io/.
  3. Configure Routing:
    • traefik.ingress.kubernetes.io/router.entrypoints: websecure: This tells Traefik to associate this route with the websecure entrypoint (typically HTTPS on port 443).
    • traefik.ingress.kubernetes.io/router.tls: "true": This enables TLS termination for this route.
    • traefik.ingress.kubernetes.io/router.tls.certresolver: myresolver: This specifies which Cert-Manager resolver (or Traefik’s built-in ACME resolver) to use for obtaining certificates.
    • traefik.ingress.kubernetes.io/router.rule: Host(myapp.example.com): This is the core routing rule. Traefik will match incoming requests where the Host header is exactly myapp.example.com.

When a request arrives at Traefik on the websecure entrypoint for myapp.example.com, Traefik will decrypt the TLS, consult its routing table, find this rule, and forward the request to my-app-service on port 80.

This annotation-based approach is powerful because it decouples routing configuration from the Traefik deployment itself. You can define routing rules directly alongside your application’s service definition, making it easier to manage routing for individual microservices without creating separate Ingress resources for each. Traefik’s Kubernetes CRD provider can also achieve similar results using IngressRoute custom resources, offering a more structured and feature-rich alternative for complex routing scenarios.

The most surprising true thing about Traefik’s annotation-based routing is that it doesn’t actually create an Ingress resource in Kubernetes. Instead, Traefik watches your Service objects for specific annotations and dynamically builds its internal routing configuration based on those annotations. This means you can configure sophisticated routing, TLS, and middleware directly on your Service objects without needing a separate Kubernetes Ingress object for each route.

Consider a scenario where you want to apply a specific middleware, like rate limiting, to requests hitting myapp.example.com. You can do this directly via annotations:

apiVersion: v1
kind: Service
metadata:
  name: my-app-service
  labels:
    app: my-app
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
    traefik.ingress.kubernetes.io/router.tls: "true"
    traefik.ingress.kubernetes.io/router.tls.certresolver: myresolver
    traefik.ingress.kubernetes.io/router.rule: Host(`myapp.example.com`)
    traefik.ingress.kubernetes.io/router.middlewares: default-rate-limit@kubernetescrd # Example middleware
spec:
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

Here, traefik.ingress.kubernetes.io/router.middlewares: default-rate-limit@kubernetescrd would apply a rate-limiting middleware named default-rate-limit that Traefik has discovered or defined via its CRDs. The @kubernetescrd suffix is crucial for Traefik to locate the middleware defined within the Kubernetes CRD provider.

The key advantage here is that Traefik’s Kubernetes CRD provider is designed to watch for these annotations on Service objects, Endpoints objects, and Ingress objects. When it finds them, it translates them into Traefik’s internal router configuration. This allows for a very granular level of control, where you can specify routing rules and middleware on a per-service basis, making your Kubernetes services directly discoverable and configurable by Traefik without needing to define a separate, top-level Ingress resource.

You’ll next want to explore Traefik’s IngressRoute Custom Resource Definition (CRD) for more advanced routing scenarios.

Want structured learning?

Take the full Traefik course →