source: https://jean.ribes.ovh/posts/traefik-outside-kubernetes/
# Træfik outside Kubernetes

I have a single-node Kubernetes cluster, but I also run docker workloads on it, and wanted to use traefik for both.

Sure, I could run two separated instances but it would be a waste.

Traefik is a versatile reverse proxy, and has many different backend for Configuration Discovery, such as *Docker* labels, *Kubernetes*`Ingress`, Nomad...

```mermaid {class="kg-width-wide" caption="example deployment with multiple targets"}
graph TB
Traefik-->pid1(local process)
Traefik-->pid2(local process)
subgraph Kubernetes
pod1
pod2
end
Traefik-->pod1(Pod)
Traefik-->pod2(Pod)
subgraph Docker
container1
container2
end
Traefik-->container1(container)
Traefik-->container2(container)

```

### Targets

I want my Traefik to proxy traffic and handle TLS termination for the following types of services:

- standalone Docker containers
- Kubernetes pods
- regular Linux processes

Because everything runs on a single machine, a lot of the network setup can be ignored. Traefik must be able to reach the IP adresses of the Docker containers and the Kubernetes pods.

## Installation

I wanted to run Traefik with systemd, but since Traefik does not provide Debian packages, you need to install the binary by hand and setup a service file.

[Setup Traefik as a systemd Service](https://gemawardian.com/setup-traefik-as-a-systemd-service/) (.kg-bookmark-container)

This post uses TOML configuration, but the result is the same

### Setup

Traefik will need to access the Docker socket and the Kubernetes API.

And everything else will be configured in [Traefik dynamic files](https://doc.traefik.io/traefik/providers/file/)

Instead of using the credentials in you kubeconfig, you should create a ServiceAccount so that Traefik can access the Kubernets API without root access.

Fortunately the work of identifying the correct permissions has been done by the Traefik team in their [Helm chart](https://github.com/traefik/traefik-helm-chart/tree/master/traefik/templates/rbac)

Because Traefik runs outside of Kubernetes, its ServiceAccount token will expire. You can issue tokens with no expiration by manually creating an empty Secret.

[Configure Service Accounts for Pods](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#manually-create-a-long-lived-api-token-for-a-serviceaccount)

I use the following configuration in kubernetes:

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik-account
  namespace: kube-public
---
apiVersion: v1
kind: Secret
metadata:
  annotations:
    kubernetes.io/service-account.name: traefik-account
  name: traefik-token
  namespace: kube-public
type: kubernetes.io/service-account-token
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: traefik-role
rules:
  - apiGroups:
      - ""
    resources:
      - services
      - secrets
      - nodes
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - discovery.k8s.io
    resources:
      - endpointslices
    verbs:
      - list
      - watch
  - apiGroups:
      - extensions
      - networking.k8s.io
    resources:
      - ingresses
      - ingressclasses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
      - networking.k8s.io
    resources:
      - ingresses/status
    verbs:
      - update
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: traefik-role-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-role
subjects:
  - kind: ServiceAccount
    name: traefik-account
    namespace: kube-public

```

Once this is created, you need to extract some informations: get the ServiceAccount token and CA using the following commands

```bash
kubectl --namespace kube-public get secret traefik-token \
 --output jsonpath='{.data.token}' \
 | base64 --decode # → the token
kubectl --namespace kube-public get secret traefik-token \
 --output jsonpath="{['data']['ca\.crt']}"\
 | base64 --decode > your-CA.pem # → the CA

```

And for the endpoint and CA, get it from your kubeconfig.

You can check you got the correct endpoint with `curl --cacert your-CA.pem https://your-endpoint:6443`

If the api-server certificate does not match the hostname/IP of the endpoint, then you can skip certificate verification with `kubectl --insecure-skip-tls-verify=false ...`.

Beware, this exposes you to man-in-the-middle attacks !

And now for the Traefik main config:

```yaml
providers:
  kubernetesIngress:
    endpoint: https://your-endpoint:6443 # your endpoint
    token: 'your-token'
    certAuthFilePath: 'path/to/your-CA.pem'
    ingressEndpoint:
      ip: your-traefik-ip # optional
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
  file:
    directory: "traefik/conf.d"
    watch: true

api: {}
ping:
  entryPoint: "ping"
entryPoints:
  ping:
    address: ":8082"
    reusePort: true
  web:
    address: ":80"
    reusePort: true
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"
    reusePort: true
    http3: {}
    http:
      tls: {}
defaultEntryPoints: 
  - websecure

```

## File configuration

The File provider will dynamically reload Traefik state as the files change.


© 2026 Jean Ribes