4 - Registering a pod and routing with traefik

Registering a pod from a dockerhub image that needs volumes and env variables
Published

April 20, 2026

If you have control over your DHCP, you can install a metalLB (Load balancer) and attribut an available IP range. This would be the production stack in that case:

Internet → MetalLB (IP) → Traefik (routing) → Ingress → Service → Pod

Install metalLB in your kubernetes controler like this:

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.5/config/manifests/metallb-native.yaml

# Wait for it to be ready
kubectl wait --namespace metallb-system \
  --for=condition=ready pod \
  --selector=app=metallb \
  --timeout=90s

Then create the metalLb configuration

# /home/gitlabci/conf/metalLB/pool.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: lan-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.1.200-192.168.1.210     # ← pick free IPs on your LAN
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: l2-advert
  namespace: metallb-system
spec:
  ipAddressPools:
  - lan-pool
kubectl apply -f /home/gitlabci/conf/metalLB/pool.yaml

For our purpose we are doing this in a externally managed network so we’ll be using the node IPs. Our stack:

Internet → DNS (multiple node IPs) → Traefik DaemonSet → Ingress → Service → Pod

Pod and service registration

First lets create the pod and service for our hello-world app (an empty nginx container) Either manualy or with CI add this file in your controler server:

# /home/gitlabci/conf/websites/my-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: my-app-svc
  namespace: default
spec:
  selector:
    app: my-app
  ports:
  - port: 80
    targetPort: 80
  type: ClusterIP 
kubectl apply -f /home/gitlabci/conf/websites/my-app.yaml

Traefik

Install Traefik:

snap install helm --classic
helm repo add traefik https://helm.traefik.io/traefik
helm repo update

metalLB will take care of the rest

helm install traefik traefik/traefik \
  --namespace traefik \
  --create-namespace \
  --set service.type=LoadBalancer 
kubectl get svc -n traefik

You should see the TYPE LoadBalancer and your externalIPs listed there or as this is managed by the metalLB

Create a values.yaml for Traefik with your real server IP(s): This configuration works with the traefik v40.2.0, sometimes major upgrades also require slightly different config formats

There are multiple options like choosing only one node to be responsible for the traefik for example but this creates a sigle failure point. With a LoadBalancer we could create a DaemonSet which will create a traefik node in every specified node then the load balancer would automatically forward to one of the nodes.

For ou situation withoutbthe loadBalancer we will still use the DeamonSet and the traffic will be forwarded depending on DNS settings

# /home/gitlabci/conf/traefik/values.yaml
deployment:
  kind: DaemonSet

service:
  spec:
    type: ClusterIP
    externalIPs:
        - 10.95.35.4       # node-1
        # - x.x.x.x          # node-2
        # add control plane IP here only if untainted

ports:
  web:
    port: 80
    hostPort: 80
  websecure:
    port: 443
    hostPort: 443

ingressClass:
  enabled: true
  isDefaultClass: true

install:

helm install traefik traefik/traefik \
  --namespace traefik \
  --create-namespace \
  --values /home/gitlabci/conf/traefik/values.yaml

verify:

kubectl get pods -n traefik
kubectl get svc -n traefik

# NAME      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
# traefik   ClusterIP   10.96.150.110   10.95.35.4    80/TCP,443/TCP   10s

You should see the TYPE clusterIP and your externalIPs listed there

Create Ingress

# /home/gitlabci/conf/ingress/my-app.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  namespace: default
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
  ingressClassName: traefik
  rules:
  - host: ipese-test.epfl.ch        # ← your domain
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app-svc
            port:
              number: 80
kubectl apply -f /home/gitlabci/conf/ingress/my-app.yaml
$ curl http://ipese-test.epfl.ch
# <!DOCTYPE html>
# <html>
# <head>
# <title>Welcome to nginx!</title>
# <style>
# html { color-scheme: light dark; }
# body { width: 35em; margin: 0 auto;
# font-family: Tahoma, Verdana, Arial, sans-serif; }
# </style>
# </head>
# <body>
# <h1>Welcome to nginx!</h1>
# ...

TLS — Manual Certificates

Create secrets with your crt and private key. In our case those files are saved at /home/gitlabci/conf/certs/ipese-test.epfl.ch.crt and /home/gitlabci/conf/certs/ipese-test.epfl.ch.key

kubectl create secret tls myapp-tls \
  --cert=/home/gitlabci/conf/certs/ipese-test.epfl.ch.crt \
  --key=/home/gitlabci/conf/certs/ipese-test.epfl.ch.key \
  --namespace default

Recrete the ingress with TLS

# /home/gitlabci/conf/ingress/my-app.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  namespace: default
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure # web-> websecure
spec:
  ingressClassName: traefik
  tls:                             # Add TLS part 
  - hosts:
    - ipese-test.epfl.ch           # ← your domain
    secretName: myapp-tls          # ← matches the secret created above
  rules:
  - host: ipese-test.epfl.ch
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app-svc
            port:
              number: 80
kubectl apply -f /home/gitlabci/conf/ingress/my-app.yaml