The next thing I’m adding to my cluster is Traefik.

In this post, I’ll cover installing Traefik with Argo CD and using Traefik’s built-in basic auth middleware to control access to its control panel.

Overview

This is part 8 of my kubernetes homelab cluster setup series - Add Traefik to the cluster

In this article, we’re going to:

  • Install Traefik with Argo CD,
  • Create an Argo CD Application resource with the git generator to install the extra k8s manifests needed to enable using the Traefik dashboard with the Traefik basic auth middleware to control access.

Software Versions

Here are the versions of the software I used while writing this post. Later versions should work, but this is what these instructions were tested with.

Software Version
argocd 3.2.6 (chart 9.4.8)
cert-manager 1.19.2
kubectl 1.34
kubernetes 1.34.1
traefik 3.3.1
sops 3.11.0

Pre-requisites

To follow along with this post, you will need:

Installation

First, create the traefik namespace and a SSL certificate

Make it a privileged namespace so Talos doesn’t complain later.

# traefik-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  labels:
    kubernetes.io/metadata.name: traefik
    pod-security.kubernetes.io/enforce: privileged
  name: traefik

Creating a SSL certificate can take a minute or two for LetsEncrypt to issue the cert, so let’s get that started ASAP. Even if you have a certificate in another namespace, go ahead and create one in the traefik namespace, it’ll make things less of a hassle later.

# traefik-cert.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-YOUR_DOMAIN-tls
  namespace: traefik
spec:
  secretName: wildcard-YOUR_DOMAIN-tls
  issuerRef:
    name: letsencrypt-dns-r53
    kind: ClusterIssuer
  commonName: "*.YOUR_DOMAIN"
  dnsNames:
    - "*.YOUR_DOMAIN"
    - "YOUR_DOMAIN"

In the interest of speed, apply them now - Argo CD will take over the resources when we create the Application resource.

kubectl apply -f traefik-cert.yaml && \
    kubectl apply -f traefik-namespace.yaml

Create a traefik-values.yaml to configure Traefik

We’re going to use Argo CD to install Traefik using a helm chart. To configure it, we’re first going to set up a traefik-values.yaml file in our configuration git repository.

I created mine in argocd/infrastructure/traefik/traefik-values.yaml

# argocd/infrastructure/traefik/traefik-values.yaml
api:
  dashboard: true
  insecure: false # We're going to put the dashboard behind basic auth

deployment:
  kind: Deployment
  replicas: 2
  podAnnotations: 
    "networking.k8s.io/mtu": "1450"

service:
  enabled: true
  type: LoadBalancer
  externalTrafficPolicy: Local
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/port: "9100"

ports:
  web:
    port: 8000
    exposedPort: 80
    expose:
      default: true
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
          permanent: true
  
  websecure:
    port: 8443
    exposedPort: 443
    expose:
      default: true
    tls:
      enabled: true

  traefik: # This is the dashboard port
    port: 9000
    expose:
      default: true
    exposedPort: 9000
    protocol: TCP

providers:
  kubernetesIngress:
    publishedService:
      enabled: true
      pathOverride: "traefik/traefik"
  kubernetesGateway:
    enabled: true
    infrastructure:
      entryPoints:
        - web
        - websecure
    # Let traefik find resources in all namespaces to make using git-generated
    #  apps easier
    namespaces: []

logs:
  general:
    level: INFO
  access:
    enabled: true

Create a Argo CD app to install traefik

We’re going to use a multi-source Argo application to install Traefik. We’ll use one source to pull in the helm chart and the second source to pull in our traefik-values.yaml file.

# argocd/infrastructure/traefik.application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: traefik
  namespace: argocd
spec:
  project: default
  sources:
    - repoURL: https://traefik.github.io/charts
      chart: traefik
      targetRevision: 34.0.0
      helm:
        valueFiles:
          - $values/argocd/infrastructure/traefik/traefik-values.yaml
    - repoURL: https://github.com/YOUR_USERNAME/YOUR_CONFIGURATION_REPO.git
      targetRevision: main
      ref: values
  destination:
    server: https://kubernetes.default.svc
    namespace: traefik
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true
      # # This prevents the "Invalid Task" lockup if an API is missing
      # - SkipDryRunOnMissingResource=true

Commit them and push them to your git repository so that Argo will be able to find the traefik-values.yaml file.

Now we can add the application to Argo

kubectl apply -f traefik.application.yaml

In a minute or so, you should see that Traefik is installed

$ kubectl get -n argocd Application/traefik
NAME      SYNC STATUS   HEALTH STATUS
traefik   Synced        Healthy
$ k get -n traefik all
NAME                           READY   STATUS    RESTARTS   AGE
pod/traefik-69d9f94ff9-8p6tr   1/1     Running   0          16m
pod/traefik-69d9f94ff9-tht4x   1/1     Running   0          16m

NAME              TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                                     AGE
service/traefik   LoadBalancer   10.106.40.190   10.0.1.43     9000:30443/TCP,80:31178/TCP,443:31727/TCP   16m

NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/traefik   2/2     2            2           18m
deployment.apps/whoami    1/1     1            1           18m

NAME                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/traefik-69d9f94ff9   2         2         2       16m

Dashboard Access

Now that traefik is running, let’s get access set up. We’re going to set up a traefik-dashboard Argo App so we can have Argo CD install and update our dashboard setup.

In your configuration repository, create an argocd/apps/traefik-dashboard directory. Put all the following manifests in that directory.

Update DNS

It can take a couple of minutes for DNS to propagate after you make a change, so start that now and hopefully by the time you create the manifests and Argo installs them DNS will be up to date.

Find out what IP the LoadBalancer was assigned:

$ kubectl get -n traefik svc
NAME      TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                                     AGE
traefik   LoadBalancer   10.106.40.190   10.9.8.7      9000:30443/TCP,80:31178/TCP,443:31727/TCP   18m

Add an A record in your DNS of traefik-dashboard.YOUR_DOMAIN that is set to the EXTERNAL-IP shown by kubectl.

Set up basic auth

We’re going to need a username and password.

# basic-auth.yaml
apiVersion: v1
kind: Secret
metadata:
    name: dashboard-auth
    namespace: traefik
type: Opaque
stringData:
    # Generate the BASE64 string with echo $(htpasswd -nb user password) | base64
    users: admin:BASE64_STRING

If you installed the sops operator, run sops encrypt -i basic-auth.yaml to encrypt it. If you didn’t, committing unencrypted secrets is a bad habit to get into, so go install sops and then encrypt basic-auth.yaml before you commit it to the repository.

Go ahead and kubectl apply -f basic-auth.yaml to that the secret is available when Argo installs the dashboard configuration.

traefik-gateway.yaml

We need to set up a Traefik Gatewayclass and a traefik-gateway Gateway to route traffic for the dashboard.

---
# traefik-gateway-class.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: traefik
spec:
  controllerName: traefik.io/gateway-controller
---
# traefik-owned-gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: traefik-gateway
  namespace: traefik
spec:
  gatewayClassName: traefik
  listeners:
    - name: http
      port: 80
      protocol: HTTP
      allowedRoutes:
        namespaces:
          from: All
    - name: https
      port: 443
      protocol: HTTPS
      allowedRoutes:
        namespaces:
          from: All
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-your-domain-tls
            namespace: traefik

traefik-dashboard.yaml

Finally, we’re going to create a manifest to:

  • Create a basic-auth Middleware resource to control access to the dashboard
  • An IngressRoute to route traffic
  • An HTTPRoute to redirect traffic from https://traefik-dashboard.YOUR_DOMAIN to https://traefik-dashboard.YOUR_DOMAIN/dashboard/ so you don’t have to remember to add the /dashbaord.
# argocd/apps/traefik-configuration/traefik-dashboard.yaml
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: dashboard-auth
  namespace: traefik
spec:
  basicAuth:
    secret: dashboard-auth
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-dashboard-api
  namespace: traefik
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`traefik-dashboard.YOUR_DOMAIN`) && (PathPrefix(`/dashboard`) || PathPrefix(`/api`))
      kind: Rule
      services:
        - name: api@internal
          kind: TraefikService
      # This is the "Lock" that uses the Middleware above
      middlewares:
        - name: dashboard-auth
  tls:
    secretName: wildcard-yourdomain-tls
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: traefik-dashboard-redirect
  namespace: traefik
spec:
  parentRefs:
    - name: traefik-gateway
      namespace: traefik
  hostnames:
    - "traefik-dashboard.YOUR_DOMAIN"
  rules:
    - matches:
        - path:
            type: Exact
            value: /
      filters:
        - type: RequestRedirect
          requestRedirect:
            scheme: https
            path:
              type: ReplaceFullPath
              replaceFullPath: /dashboard/
            statusCode: 301

Add an argo-config.yaml to trigger installation by ArgoCD

Commit all the yaml files and push them to github if you haven’t already. Now we can create an argo-config.yaml file to trigger Argo to install our manifests. The argo-config.yaml file must be in the same directory as traefik-cert.yaml, basic-auth.yaml, traefik-gateway.yaml and traefik-dashboard.yaml. The Argo CD git generator ApplicationSet only processes directories that contain an argo-config.yaml file.

# argo-config.yaml
app:
  name: "traefik-configuration"
  namespace: traefik
  clusterName: YOURCLUSTERNAME
  # Use the full URL for the destination
  clusterUrl: https://kubernetes.default.svc
  project: default

Add, commit and push argo-config.yaml. With the default configuration, Argo CD will detect the new app and start installing it in two to three minutes. You can force it to check right away by clicking Sync Apps or running

argocd repo get "$YOUR_CONFIG" --refresh

Update DNS

Once Argo finishes installing, find out what IP the LoadBalancer was assigned

$ kubectl get -n traefik svc
NAME      TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                                     AGE
traefik   LoadBalancer   10.106.40.190   10.9.8.7      9000:30443/TCP,80:31178/TCP,443:31727/TCP   18m

Update DNS for your domain with an the external IP.

Checking

Once Argo finishes installing your manifests, try going to traefik-dashboard.YOUR_DOMAIN in a browser. If DNS hasn’t propagated, you can check with curl anyway by setting the host header and basic auth information.

curl -vL -k -u admin:YOUR_ADMIN_PASSWORD -H "Host: traefik-dashboard.YOUR_DOMAIN" https://SERVICE_EXTERNAL_IP/dashboard/

What the cli flags do:

  • -u admin:YOUR_ADMIN_PASSWORD: The magic flag. It takes username:password and converts it into the required HTTP header.
  • -k (or --insecure): The TLS certificate (which is for *.YOUR_DOMAIN) won’t match SERVICE_EXTERNAL_IP. This flag tells curl to ignore that mismatch.
  • -v: Verbose mode. This lets you see the HTTP/1.1 200 OK and confirm the Authorization header is being sent.
  • -L: Follows the redirect (though since you’re hitting /dashboard/ directly, it likely won’t need to).

You should see something similar to

*   Trying SERVICE_EXTERNAL_IP:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* SSL Trust: peer verification disabled
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_CHACHA20_POLY1305_SHA256 / x25519 / RSASSA-PSS
* ALPN: server accepted h2
* Server certificate:
*   subject: CN=TRAEFIK DEFAULT CERT
*   start date: Apr  4 23:11:05 2026 GMT
*   expire date: Apr  4 23:11:05 2027 GMT
*   issuer: CN=TRAEFIK DEFAULT CERT
*   Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
*  SSL certificate verification failed, continuing anyway!
* Established connection to SERVICE_EXTERNAL_IP (SERVICE_EXTERNAL_IP port 443) from 10.11.12.13 port 59344
* using HTTP/2
* Server auth using Basic with user 'admin'
* [HTTP/2] [1] OPENED stream for https://SERVICE_EXTERNAL_IP/dashboard/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: traefik-dashboard.YOUR_DOMAIN]
* [HTTP/2] [1] [:path: /dashboard/]
* [HTTP/2] [1] [authorization: Basic FEEDBABEDEADBEEFFEEDBABEDEADBEEFFEEDBABE]
* [HTTP/2] [1] [user-agent: curl/8.18.0]
* [HTTP/2] [1] [accept: */*]
> GET /dashboard/ HTTP/2
> Host: traefik-dashboard.YOUR_DOMAIN
> Authorization: Basic FEEDBABEDEADBEEFFEEDBABEDEADBEEFFEEDBABE
> User-Agent: curl/8.18.0
> Accept: */*
>
* Request completely sent off
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/2 200
< content-security-policy: frame-src 'self' https://traefik.io https://*.traefik.io;
< content-type: text/html; charset=utf-8
< content-length: 1193
< date: Sat, 04 Apr 2026 23:15:25 GMT
<
<!DOCTYPE html><html><head><script>window.APIURL = "/api/"</script><title>Traefik</title><meta charset=utf-8><meta name=description content="Traefik UI"><meta name=format-detection content="telephone=no"><meta name=msapplication-tap-highlight content=no><meta name=viewport content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width"><link rel=icon type=image/png href=./app-logo-128x128.png><link rel=icon type=image/png sizes=16x16 href=./icons/favicon-16x16.png><link rel=icon type=image/png sizes=32x32 href=./icons/favicon-32x32.png><link rel=icon type=image/png sizes=96x96 href=./icons/favicon-96x96.png><link rel=icon type=image/ico href=./icons/favicon.ico><link rel=apple-touch-icon href=./icons/apple-icon-152x152.png><link rel=apple-touch-icon sizes=152x152 href=./icons/apple-icon-152x152.png><link rel=apple-touch-icon sizes=167x167 href=./icons/apple-icon-167x167.png><link rel=apple-touch-icon sizes=180x180 href=./icons/apple-icon-180x180.png>  <script type="module" crossorigin src="./assets/index-CLgUCYBL.js"></script>
  <link rel="stylesheet" crossorigin href="./assets/index-jjiyEA_O.css">
* Connection #0 to host SERVICE_EXTERNAL_IP:443 left intact
</head><body><div id=q-app></div></body></html>HTTP CODE 200

Congratulations

You should now have:

  • An Argo CD dual-source Application resource to install Traefik
  • An Argo CD application using the git generator that
    • Enables access to the Traefik dashboard
    • Protects the dashboard with basic auth