Skip to content

Installing Traefik v3 on Kubernetes with the official Helm chart

Install Traefik v3 on Kubernetes via Helm with CRDs, RBAC scoped to the namespace, and dashboard exposure controlled — operator-grade defaults.

A Helm install of Traefik on Kubernetes is the quickest path to a working ingress controller, but a wide-open default install creates a dashboard on the public LoadBalancer, gives the controller cluster-wide read on every secret, and lets a developer in any namespace claim any hostname. This article walks through a Traefik v3 install with the CRD provider, RBAC trimmed to what the controller actually needs, the dashboard fenced behind an IngressRoute with auth, and Prometheus metrics scraped through a separate ServiceMonitor.

How to verify

kubectl get pods -n traefik -l app.kubernetes.io/name=traefik
kubectl get crd | grep traefik.io
kubectl get svc -n traefik traefik
kubectl logs -n traefik -l app.kubernetes.io/name=traefik --tail=30 | grep -iE 'configuration loaded|cert'
kubectl get ingressroute -A

The service should be a LoadBalancer with an external IP and the dashboard router should NOT appear in any namespace except traefik. The CRDs ingressroutes.traefik.io, middlewares.traefik.io, tlsstores.traefik.io, serverstransports.traefik.io are present.

What’s happening

The Traefik Helm chart deploys three things together: a Deployment (or DaemonSet for hostNetwork patterns), a Service of type LoadBalancer that fronts the pod, and the CRDs and RBAC that let Traefik watch IngressRoute and Middleware objects across the cluster. By default the controller reads ingresses cluster-wide, which is fine for shared infrastructure but a problem for multi-tenant clusters — there a per-namespace kubernetesIngress.namespaces or a label selector is the right answer.

The CRD path is the way to use Traefik on Kubernetes. The plain Ingress resource maps to a subset of Traefik’s features, and almost every operator-grade pattern (TLS options, middleware chains, TCP routes, sticky sessions) needs the CRD. Once teams adopt IngressRoute, they rarely go back, but mixed installs that accept both Ingress and IngressRoute drift quickly — pick one as the canonical surface and treat the other as deprecated.

The procedure

  1. Add the Helm repo and pull the chart values.

    helm repo add traefik https://traefik.github.io/charts
    helm repo update
    helm show values traefik/traefik > /tmp/traefik-defaults.yaml
  2. Write a tight values.yaml. Note additionalArguments, the metrics + dashboard layout, and ingressClass set so the controller does not silently grab every old Ingress on the cluster.

    # values.yaml
    image:
      tag: v3.1.6
    deployment:
      replicas: 2
    service:
      type: LoadBalancer
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-type: nlb
    ingressClass:
      enabled: true
      isDefaultClass: false
      name: traefik
    providers:
      kubernetesIngress:
        enabled: true
        allowExternalNameServices: false
        ingressClass: traefik
      kubernetesCRD:
        enabled: true
        allowCrossNamespace: false
    ports:
      web:
        redirectTo:
          port: websecure
          priority: 10
      websecure:
        tls:
          enabled: true
    metrics:
      prometheus:
        service:
          enabled: true
        serviceMonitor:
          enabled: true
    logs:
      general:
        level: INFO
        format: json
      access:
        enabled: true
        format: json
    additionalArguments:
      - "--api.dashboard=true"
      - "--api.insecure=false"
      - "--global.checknewversion=false"
      - "--global.sendanonymoususage=false"
    resources:
      requests:
        cpu: 200m
        memory: 256Mi
      limits:
        memory: 512Mi
    podSecurityContext:
      fsGroup: 65532
    securityContext:
      readOnlyRootFilesystem: true
      runAsNonRoot: true
      runAsUser: 65532
  3. Install into a dedicated namespace.

    kubectl create namespace traefik
    helm install traefik traefik/traefik -n traefik -f values.yaml
    kubectl rollout status -n traefik deploy/traefik
  4. Expose the dashboard through an IngressRoute with basic-auth — never via the chart’s dashboard.ingressRoute quickstart, which has no auth.

    apiVersion: v1
    kind: Secret
    metadata:
      name: traefik-dashboard-auth
      namespace: traefik
    stringData:
      users: |
        admin:$2y$05$REPLACE_WITH_HASH
    ---
    apiVersion: traefik.io/v1alpha1
    kind: Middleware
    metadata:
      name: dashboard-auth
      namespace: traefik
    spec:
      basicAuth:
        secret: traefik-dashboard-auth
    ---
    apiVersion: traefik.io/v1alpha1
    kind: IngressRoute
    metadata:
      name: traefik-dashboard
      namespace: traefik
    spec:
      entryPoints: [websecure]
      routes:
        - match: Host(`traefik.example.com`)
          kind: Rule
          services:
            - name: api@internal
              kind: TraefikService
          middlewares:
            - name: dashboard-auth
      tls:
        secretName: traefik-dashboard-tls
  5. Test with a sample IngressRoute in another namespace.

    apiVersion: traefik.io/v1alpha1
    kind: IngressRoute
    metadata:
      name: whoami
      namespace: default
    spec:
      entryPoints: [websecure]
      routes:
        - match: Host(`whoami.example.com`)
          kind: Rule
          services:
            - name: whoami
              port: 80
      tls:
        secretName: whoami-tls

Common pitfalls

  • Leaving allowCrossNamespace: true — any IngressRoute can reference a Service in any namespace, which lets one team’s app proxy another team’s backend.
  • Installing without ingressClass.isDefaultClass: false on a cluster that already has another controller — both controllers grab the same Ingress object and you get races on cert issuance.
  • Trusting the chart’s default dashboard.enabled IngressRoute — recent versions disable it, but if you flip it back on you get an unauthenticated dashboard publicly.
  • Not pinning image.tag — Helm upgrades that flip the appVersion can roll a new major mid-incident; pin the tag and bump it intentionally.
  • Forgetting the ServiceMonitor is in a namespace that Prometheus actually selects — by default Prometheus Operator only picks up monitors in namespaces it watches.

For Kubernetes-native ACME, see traefik-letsencrypt-acme; cert-manager is the more common production choice and the IngressRoute references it the same way. In the clusters Stack Harbor operates we run managed Kubernetes operations end-to-end — Traefik upgrades, RBAC drift, IngressRoute audits, and ACME rotation are tracked the same way we track any other cluster-critical component.