Traefik Deployment in Kubernetes with cert-manager

Kubernetes series (Article 4): Deploying Traefik Ingress and cert-manager in Kubernetes

After completing the initial setup of my Kubernetes cluster, I needed an Ingress controller. Kubernetes doesn’t include an Ingress controller by default; it’s up to the user to choose and deploy one that best fits their use case.

Traefik

My main options were NGINX and Traefik, two of the most popular choices in the community. While NGINX is well-documented and widely adopted, I decided to go with Traefik, primarily because of my previous experience using it in Docker-based environments. You can read more about that in my Traefik with Docker article.

That familiarity, Traefik’s powerful dynamic configuration, native support for Let’s Encrypt, and integration with modern cloud-native tools made it the right choice for my Kubernetes setup.

That said, deploying Traefik in Kubernetes slightly differs from deploying it in Docker. It involves configuring it via Helm charts and managing its CRDs (Custom Resource Definitions), middleware, and routing rules.

Helm-Driven Deployment

Since we already installed Helm in the previous part of this series (Helm, MetalLB, and Longhorn article), I used Helm to deploy Traefik. This simplifies the installation and makes upgrading, configuring, and version controlling the deployment easier.

Managing SSL Certificates

To handle automatic TLS/SSL certificate provisioning, I integrated cert-manager, a native Kubernetes certificate management controller that works well with Traefik using ACME protocols like Let’s Encrypt. This setup allows me to automate the issuance and renewal of certificates for any domain exposed via my Ingress.

In this article, I’ll walk through:

  • Installing Traefik via Helm
  • Configuring it as the default Ingress controller
  • Integrating cert-manager for SSL certificate management

Traefik Deployment in Kubernetes with cert-manager (Cloudflare DNS)

This guide documents deploying Traefik with HTTPS using cert-manager, Let’s Encrypt, and Cloudflare’s DNS for DNS-01 challenge validation.

Installation Steps

1. Install cert-manager CRDs

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.2/cert-manager.crds.yaml
Bash

2. Add Jetstack Helm Repo

helm repo add jetstack https://charts.jetstack.io --force-update
helm repo update
Bash

3. Create cert-manager-values.yaml

extraArgs:
  - --dns01-recursive-nameservers-only
  - --dns01-recursive-nameservers=8.8.8.8:53,1.1.1.1:53
  - --dns01-check-retry-period=30s

resources:
  requests:
    cpu: 200m
    memory: 256Mi
  limits:
    cpu: 2000m
    memory: 1Gi
YAML

The 30s retry delay resolves DNS propagation issues with Cloudflare.

4. Install or Upgrade cert-manager

helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.17.2 \
  -f cert-manager-values.yaml

# Or upgrade:
helm upgrade cert-manager jetstack/cert-manager \
  -n cert-manager \
  -f cert-manager-values.yaml
Bash

5. Create Cloudflare API Token Secret

# cloudflare-api-token-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-api-token
  namespace: cert-manager
type: Opaque
stringData:
  api-token: YOUR_CLOUDFLARE_API_TOKEN
YAML

Apply it:

kubectl apply -f cloudflare-api-token-secret.yaml
Bash

Create this before installing Traefik so it can use the token during certificate resolution.

DNS-01 Challenge with Cloudflare

ClusterIssuer (Staging)

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: cloudflare
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: youremail
    privateKeySecretRef:
      name: cloudflare-key
    solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              name: cloudflare-api-token
              key: api-token
YAML

Apply it:

kubectl apply -f clusterissuer.yaml
kubectl describe clusterissuer cloudflare
Bash

Traefik Certificate (Staging)

staging-certificate-traefik.yaml

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: traefik-cert
  namespace: traefik
spec:
  secretName: traefik-tls
  commonName: yourdomain.uk
  dnsNames:
    - yourdomain.uk
    - '*.yourdomain.uk'
  issuerRef:
    name: cloudflare
    kind: ClusterIssuer
  duration: 2160h # 90 days
  renewBefore: 168h # 7 days
YAML
FieldDescription
secretNameSecret Traefik uses for TLS
dnsNamesIncludes base and wildcard domains
issuerRefRefers to the ClusterIssuer cloudflare
duration90-day cert
renewBeforeAuto-renew 7 days before expiry

Switching to Production Certs

1. Create Production ClusterIssuer

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: cloudflare-prod
spec:
  acme:
    email: youremail
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: cloudflare-prod-key
    solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              name: cloudflare-api-token
              key: api-token
YAML

Apply:

kubectl apply -f clusterissuer-prod.yaml
Bash

2. Update the Certificate

issuerRef:
  name: cloudflare-prod
  kind: ClusterIssuer
YAML

Apply:

kubectl apply -f production-certificate.yaml
Bash

Verify:

kubectl describe certificate traefik-cert -n traefik
kubectl get secret traefik-tls -n traefik
Bash

Install Traefik via Helm

helm repo add traefik https://traefik.github.io/charts
helm repo update
helm install traefik traefik/traefik \
  --namespace traefik \
  --create-namespace \
  --values traefik-values.yaml
Bash

Add to values.yaml

tlsStore:
  default:
    defaultCertificate:
      secretName: traefik-tls
YAML

Traefik Dashboard IngressRoute

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-dashboard
  namespace: traefik
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`traefik.yourdomain.uk`) && (PathPrefix(`/dashboard`) || PathPrefix(`/api`))
      kind: Rule
      services:
        - name: api@internal
          kind: TraefikService
      middlewares:
        - name: dashboard-auth
  tls:
    secretName: traefik-tls
YAML

Apply:

kubectl apply -f traefik-ingressroute.yaml
Bash

Basic Auth for Dashboard

1. Generate Credentials

htpasswd -nbB yourusername yourpassword
Bash

Base64 encode:

echo -n 'yourusername:<hashed-password>' | base64
Bash

2. Create Secret

apiVersion: v1
kind: Secret
metadata:
  name: authsecret2
  namespace: traefik
type: Opaque
data:
  users: <base64-output>
YAML

Apply:

kubectl apply -f traefik-middleware-secret.yaml
Bash

3. Create Middleware

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: dashboard-auth
  namespace: traefik
spec:
  basicAuth:
    secret: authsecret2
    realm: "Traefik Dashboard"
YAML

Apply:

kubectl apply -f traefik-middleware.yaml
Bash

Maintenance Tips

Upgrade Traefik

helm upgrade traefik traefik/traefik \
  --namespace traefik \
  -f traefik-values.yaml
Bash

Restart Deployment

kubectl rollout restart deployment traefik -n traefik
Bash

Check Logs

kubectl logs -l app.kubernetes.io/name=traefik -n traefik --tail=100
Bash

Debug cert-manager

kubectl get certificaterequests -n traefik
kubectl describe certificaterequest <name> -n traefik
kubectl get orders -n traefik
kubectl describe order <name> -n traefik
kubectl get challenges -n traefik
Bash

For Longhorn, Nextcloud, or additional IngressRoutes, see their respective documentation or reach out to extend this guide.

Back to top arrow