CodeWorlds
Back to collections
Guide16 min readCodeWorlds Team

Traefik, a reverse proxy that finds services on its own

Traefik reads routing config from Docker and Kubernetes without a reload. Version 3.7.11, MIT license, the npm package trap, and where Proxy ends and Hub begins.

Traefik, a reverse proxy that finds services on its own

Traefik is a reverse proxy and load balancer written in Go that reads its routing configuration straight from Docker, Kubernetes, Consul or Nomad instead of a hand written file. The current release is 3.7.11, the license is MIT, and Let's Encrypt certificates are ordered and renewed by the server process itself.

Where Traefik gets its configuration

The difference between Traefik and a classic proxy comes down to one concept: the provider. In nginx or HAProxy the configuration file is the single source of truth about routes, and every change means editing that file and reloading the process. Traefik splits configuration into two layers with completely different life cycles.

The static layer holds things fixed at process start that cannot change without a restart: which ports listen, which configuration sources are enabled, how the certificate authority is reached. The dynamic layer holds routers, services and middleware, meaning everything that changes on every deployment. That second layer does not come from a file, it comes from a provider.

A provider is an adapter to a specific system. The Docker provider attaches to the daemon socket and watches events about containers starting and stopping, reading labels that begin with traefik.. The Kubernetes CRD provider reads IngressRoute resources from the traefik.io/v1alpha1 group. There are also providers for Consul, Nomad, ECS and Redis, plus a plain file provider for when you do want to write routes by hand. Several providers can run at once and their results add up.

The practical effect is that a new container carrying the right set of labels starts receiving traffic without touching the proxy. There is no reload command, no template generating a file, no step in the deployment pipeline that could corrupt that file. Smoothing out bursts of events is handled by providers.providersThrottleDuration with a default of 2 seconds, so ten containers coming up at once produce one rebuild of the routing table rather than ten.

The price of that convenience is real too. Configuration stops living in one place. It spreads across docker-compose.yml files, Kubernetes manifests and annotations, and answering "why does this host go where it goes" requires opening the Traefik dashboard or the API rather than one text file.

Version, license and the third check

The current release on the third branch is 3.7.11 from 21 August 2026. That three lines are maintained in parallel shows in 3 August, when 3.7.10, 3.6.25 and 2.11.55 all shipped on the same day, one release from each of them. The code builds with go 1.26.0 declared in go.mod.

I checked the license in three places, because this is the most common source of mistakes in dependency audits.

The first source is the file in the repository. github.com/traefik/traefik keeps it as LICENSE.md, not LICENSE, and it holds the full MIT text with the notice "Copyright (c) 2016-2020 Containous SAS; 2020-2025 Traefik Labs". Containous is the former name of the company that renamed itself to Traefik Labs in 2020, and that trace shows up directly in the header.

The second source is the package registry. This is where it gets unpleasant, and it gets its own section below, because the npm package named traefik does not come from Traefik Labs.

The third source is the content of the artifact actually distributed, and here the result is split. The archive traefik_v3.7.11_linux_amd64.tar.gz from the releases section weighs about 48 MB and contains exactly three entries: CHANGELOG.md, LICENSE.md and the traefik binary of about 176 MB unpacked. The license text is therefore present, unlike many projects that ship bare binaries. The official container image, however, does not carry it. The Dockerfile in the traefik/traefik-library-image repository for the alpine variant unpacks the archive with a command that extracts a single entry from it: tar xzvf /tmp/traefik.tar.gz -C /usr/local/bin traefik. LICENSE.md and CHANGELOG.md stay behind in the discarded archive. If your image scanner looks for a license file inside the layers, it will not find one for traefik:v3.7.11, even though the project is plainly MIT. You then have to record the license manually or take it from the OCI labels the image does set, including org.opencontainers.image.source pointing at the repository.

The name trap: the npm package traefik

The traefik package in the npm registry is not published by Traefik Labs. The newest version is 1.0.0 released on 2 August 2021, the whole registry entry has not changed since May 2022, and the repository.url field points at github.com/hello-seam/node-traefik. It is one person's unofficial wrapper.

Unlike several other lookalikes, this one does contain code: fourteen files and about 14.8 kB unpacked. index.js exports a start function that converts a configuration object to TOML through json2toml and launches the binary as a child process. The problem lies elsewhere. The file download-traefik.js has the version to fetch hard coded and asks the GitHub programming interface for it without authentication.

JSnode_modules/traefik/download-traefik.js
JavaScript
// node_modules/traefik/download-traefik.js, version 1.0.0 from 2021
const releaseVersionToUse = "2.4.9"

const releaseAPIUrl =
  `https://api.github.com/repos/traefik/traefik/releases/tags/v${releaseVersionToUse}`

Installing the package in 2026 therefore pulls Traefik 2.4.9 from 2021, a release five years old and two major branches behind what is maintained. The download happens in the install script, so it runs automatically during npm install, and the unauthenticated request to GitHub hits rate limits at the first coincidence in a continuous integration environment.

A separate curiosity concerns the license. The npm package declares "license": "MIT" and includes a LICENSE.md file, but its content is the copied Traefik license text with the notice "Copyright (c) 2016-2020 Containous SAS; 2020-2021 Traefik Labs". A tool collecting license metadata from node_modules will therefore report a component covered by Traefik Labs rights in your project, even though somebody else wrote the wrapper code. This is a case where all three sources say "MIT" and the picture is still misleading.

Code
Bash
# this is NOT the package from Traefik Labs
npm view traefik version          # 1.0.0
npm view traefik repository.url   # git+https://github.com/hello-seam/node-traefik.git
npm view traefik time.modified    # 2022-05-22T03:25:33.105Z

# this is how the real Traefik is installed
docker pull traefik:v3.7.11

curl -sL https://github.com/traefik/traefik/releases/download/v3.7.11/traefik_v3.7.11_linux_amd64.tar.gz \
  | tar -xz traefik LICENSE.md

Traefik is a Go program distributed as a single binary or a container image. There is no official npm channel, no official PyPI package, and no reason for a proxy to enter your project through package.json.

Static and dynamic configuration

Static configuration goes into a file, into environment variables or into command line flags. By default the process looks for a file under four base paths, in this order: /etc/traefik/traefik, $XDG_CONFIG_HOME/traefik, $HOME/.config/traefik and ./traefik, trying the extensions toml, yaml and yml. A custom location is given by the --configFile flag.

/etc/traefik/traefik.yml
YAML
# /etc/traefik/traefik.yml, the static layer
entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: "proxy"
    watch: true

certificatesResolvers:
  letsencrypt:
    acme:
      email: admin@example.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

One field in that example deserves particular attention. providers.docker.exposedByDefault defaults to true, which means that without this line Traefik will expose to the internet every container visible on the daemon socket, including your database and your queue. The default rule is Host({{ normalize .Name }}), the container name used as the host name. Setting false inverts the logic: only containers carrying the traefik.enable=true label get routed.

The dynamic layer for Docker is labels. The router name and the service name inside a label are arbitrary and serve only to bind related entries together.

Code
YAML
services:
  traefik:
    image: traefik:v3.7.11
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./letsencrypt:/letsencrypt
    networks: [proxy]

  api:
    image: ghcr.io/example/api:1.4.2
    labels:
      - traefik.enable=true
      - traefik.http.routers.api.rule=Host(`api.example.com`)
      - traefik.http.routers.api.entrypoints=websecure
      - traefik.http.routers.api.tls.certresolver=letsencrypt
      - traefik.http.services.api.loadbalancer.server.port=8080
      - traefik.docker.network=proxy
    networks: [proxy]

In Kubernetes the same description takes the shape of a custom resource. The group is traefik.io/v1alpha1, and before first use you have to apply the resource definitions and access rules from the dynamic-configuration directory in the repository.

Code
YAML
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: api-route
  namespace: apps
spec:
  entryPoints:
    - websecure
  routes:
    - kind: Rule
      match: Host(`api.example.com`) && PathPrefix(`/v1`)
      priority: 10
      services:
        - kind: Service
          name: api
          port: 8080
          passHostHeader: true
  tls:
    certResolver: letsencrypt

The priority field settles collisions between rules. Without it Traefik sorts rules by the length of their textual form, which can be surprising when two routes match the same request.

ACME certificates and their limits

Certificates are handled by a mechanism called the certificate resolver. You declare one once in the static layer and then refer to it by name from a route through tls.certresolver. The required fields are email and storage, where storage defaults to acme.json in the working directory.

You pick one of three challenges. httpChallenge.entryPoint names an entry point listening on port 80 that must be reachable from outside. tlsChallenge uses TLS-ALPN-01 on port 443. dnsChallenge.provider reaches for a TXT record in the DNS zone, and only this route yields wildcard certificates, because writing a DNS record needs credentials for the provider, passed through environment variables according to the list maintained by the Lego library.

The default authority is https://acme-v02.api.letsencrypt.org/directory, the default key type is RSA4096, and Traefik assumes 90 day certificates and begins renewal 30 days before expiry. For authorities issuing certificates with a different validity period there is certificatesDuration, given in hours, with a default of 2160.

Here lies the most important limitation of the free variant, and you need to know it before designing a deployment. The acme.json store is an ordinary file on disk, not a distributed store with consistency guarantees. Two Traefik replicas reading the same file over a shared volume will fight over it and both will order their own certificates, burning through the authority's limits. The vendor's answer is a feature called Distributed Let's Encrypt, available only in the paid Traefik Hub. In the open variant the correct exits are a single replica terminating TLS, certificates issued outside Traefik and injected as secrets, or an external mechanism such as cert-manager in Kubernetes.

Traefik Proxy, Hub and Enterprise

The documentation and the product site mix three products on one level of navigation, so it is very easy to write an example that uses a feature the free version simply does not have.

FeatureTraefik Proxy (MIT)Traefik Hub API GatewayTraefik Hub API Management
Automatic service discoveryyesyesyes
HTTP/2, HTTP/3, TCP, UDP, gRPC, WebSocketsyesyesyes
OpenTelemetry metrics and tracesyesyesyes
Canary deployments, ingress view dashboardyesyesyes
OIDC, LDAP, JWT, HMAC, API keys, Open Policy Agentnoyesyes
Native web application firewallnoyesyes
Distributed Let's Encrypt, distributed rate limitingnoyesyes
HTTP caching, cluster view dashboard, multi-clusternoyesyes
FIPS 140-2 and 140-3noyesyes
Developer portal, API versioning and plansnonoyes
Air-gapped mode, API mockingnonoyes

There is no price list. The traefik.io/pricing page lines up three columns, but Traefik Proxy carries a download button and a separate support offering for the open version, while both Hub variants show only "Get pricing". No figure, no tier, no information about the billing unit. A quote has to be extracted through a conversation with sales, which when comparing against competitors means weeks rather than minutes.

Traefik Enterprise, described a year ago as a separate product, now carries a banner on its own page stating plainly that it has been moved into Traefik Hub. The documentation at doc.traefik.io/traefik-enterprise/ still responds, so existing deployments have somewhere to look, but a new project has no reason to go there. That is also a signal about vendor lock-in risk: the paid line changed its name and shape, and customers had to migrate.

In fairness, Traefik Proxy on its own is complete. Reverse proxy, load balancer, ingress controller, middleware with redirects, headers, basic authentication and per-instance request rate limiting, plus a catalogue of plugins written in Go. For a single server or a single cluster that is the whole set. Paid variants start making sense only with multiple clusters and compliance requirements.

Traefik versus Caddy and nginx

Caddy solves part of the same problem: it is also written in Go, it also fetches certificates on its own, and it also wants to replace the nginx plus certbot pair. The difference sits in where the list of routes comes from.

CriterionTraefik Proxy 3.7Caddy 2nginx with certbot
Source of routesproviders: Docker, Kubernetes, Consul, Nomad, fileCaddyfile or JSON through the admin APIfiles in a configuration directory
Reaction to a new containerautomatic, no reloadedit the file and reloadedit the file and reload
CertificatescertificatesResolvers with file storagebuilt in, on by defaultseparate process and scheduler
Whole configuration in one fileno, routes live in labelsyesyes
LicenseMITApache 2.0BSD 2-clause
Paid variantTraefik Hub, price on requestnone, funded by sponsorshipnone in the core

The choice is settled by how volatile the environment is, not by taste in syntax. With a fixed set of a few services whose addresses change once a quarter, the Caddy file is simply better: it fits on a screen, goes into the repository, can be reviewed in a minute, and during an outage the whole truth is visible in one place. Traefik in that setup adds a daemon watching events to detect a change that does not happen anyway.

With dozens of containers coming up and going down at the rhythm of deployments the proportions flip. Generating a Caddy file from a template and reloading it on every change reproduces exactly the mechanism Traefik has built in, only with your own code that has to be maintained and that can fail quietly. A label next to a container lives beside the service definition, so the deployment and the route change in one revision.

It is also worth recording that many deployment platforms settle this choice for you. Coolify sets up Traefik as its default proxy and adds the labels to launched containers itself. Railway and Fly.io have their own networking layers and the question never arises there. If the services behind the proxy are mostly PostgreSQL and Redis, meaning protocols other than HTTP, you need a TCP router with a HostSNI rule rather than a plain HTTP router.

Common mistakes

Leaving exposedByDefault at its default value. The effect is a public address for every container on the network, including those that were never meant to leave the host.

Mounting /var/run/docker.sock writable. Access to the daemon socket is equivalent to administrator rights on the host, so a proxy exposed to the internet with such a volume is a serious risk. The minimum is the :ro suffix, better still a broker limiting the range of calls.

Two replicas on one acme.json file. The result is duplicated certificate orders and exhaustion of the authority's weekly limit, after which the domain sits without a valid certificate until the limit window closes.

The container and Traefik on different Docker networks. The route is created, the dashboard shows it, and the answer is a gateway error because the proxy has no way to reach the target address. The traefik.docker.network label or the providers.docker.network field settles the matter.

Omitting loadbalancer.server.port when the image exposes more than one port. Traefik then guesses the port and guesses wrong.

Exposing the dashboard with api.insecure=true on a public entry point. The dashboard shows the full routing table and the middleware configuration, meaning a map of your infrastructure.

Porting configuration from the second branch to the third without reading the migration guide. Provider names, rule syntax and some fields went through backwards incompatible changes, and an old file can come up with part of the routes quietly skipped.

FAQ

Is the npm package named traefik the official Traefik?

No. Version 1.0.0 from 2 August 2021 comes from the hello-seam/node-traefik repository and is one person's unofficial wrapper. It contains working code, but it has Traefik 2.4.9 hard coded and fetches it through an unauthenticated request to the GitHub interface. The official distribution is a binary from the releases section or the traefik image.

What is Traefik's license and where do I find it?

MIT, in the LICENSE.md file at the root of the repository, with a copyright notice for Containous SAS and Traefik Labs. The file is also in the release archive, but it is absent from the official container image, because the Dockerfile extracts only the binary from that archive.

Can Traefik handle automatic certificates across several replicas?

Not in the open version. The acme.json store is a file and there is no coordination between instances inside it. The Distributed Let's Encrypt feature belongs to the paid Traefik Hub. With multiple replicas it makes more sense to issue certificates outside the proxy, for example with cert-manager.

How much does Traefik Hub cost?

The price list is not public. The pricing page shows only a "Get pricing" button for both Hub variants, with no rate, no tier and no billing unit. A quote has to come from the sales department.

Traefik or Caddy for one server with a few services?

With a fixed set of services Caddy usually comes out ahead, because the entire configuration fits in one short file. Traefik starts paying off when services come up and go down on their own and the list of routes changes on every deployment.

Will Traefik pass traffic other than HTTP?

Yes. Besides HTTP routers there are TCP routers with rules based on HostSNI and UDP routers. That lets you put database services behind a single entry point too, although without a name in SNI you cannot distinguish several services on one port.

Sources: the project repository, the pricing page, the package entry in the npm registry.

Read next

We use cookies to enhance your experience on the site