In the self-hosted community, many tend towards setups involving NGINX or NGINX Proxy Manager to route to their containers. This article contrasts Nginx Proxy Manager and Traefik to elucidate why the latter might be the optimal choice for most containerized environments.
The downsides of NGINX Proxy Manager
Many appreciate that NGINX Proxy Manager features a user-friendly web interface, eliminating the need to manually configure nginx. Yet this convenience comes at a cost: it supports a limited set of configurations and features. For instance, a PR to add support for the PROXY Protocol has remained unattended for quite a while: GitHub PR Link.
Internally, Nginx Proxy Manager leverages templates to spawn Nginx configurations. Though this offers some kind of flexibility, it introduces another layer of abstraction, potentially resulting in more intricate or inconsistent configurations, especially when custom modifications are applied.
This challenge isn't unique to Nginx Proxy Manager. Within the Kubernetes sphere, when juxtaposing Traefik with ingress-nginx, Traefik's API-native nature gives it an edge. Being API-native, Traefik inherently understands and aligns with the language of your platform. It effortlessly melds with your orchestration system, streamlining configurations.
Routing with Traefik
Do we genuinely need a separate web interface? Traefik effortlessly communicates in the language of containers. Imagine creating a container, adding a label, and saying, "Route this domain to my container." With Traefik, it's one additional line in your compose.
services: mycontainer: image: traefik/whoami # the whoami container is simply a hello world container showing the IP labels: - traefik.enable=true - traefik.http.routers.mycontainer.rule=Host(`mycontainer.myapp.cc`)
With just a label for a container, whether it's via Docker, Docker Compose, or Portainer, Traefik identifies it and immediately routes accordingly. What's more, there's no need to specify a port; Traefik intelligently searches for one within your container. But that's just a glimpse of its capabilities. Traefik supports countless middlewares for request modifications, header additions, authentication, and more.
For instance, to secure your site with Basic Authentication:
labels: - traefik.enable=true - traefik.http.routers.mycontainer.rule=Host(`mycontainer.myapp.cc`) # Define Middleware, name it test-auth - "traefik.http.middlewares.test-auth.basicauth.users=test:passwordINhtpasswordformat" # Now add middleware to our router - traefik.http.routers.mycontainer.middlewares=test-auth
Deploying Traefik itself
Every application has its docker-compose file (or Portainer Stack), with Traefik having its own compose file/Stack. Below is a representative example:
version: '3' services: reverse-proxy: image: traefik:v2.9 restart: always command: - --entryPoints.http.address=:80 - --providers.docker - --providers.docker.exposedByDefault=false - --providers.file.directory=/opt/traefik ports: # The HTTP port - "80:80" volumes: # So that Traefik can listen to the Docker events - /var/run/docker.sock:/var/run/docker.sock # For additional traefik configuration files if needed - /opt/traefik:/opt/traefik network_mode: host
In this configuration, Traefik has an entrypoint set for port 80. Additionally, I've mounted the Docker socket, enabling Traefik to read labels from other containers. To simplify routing, I've set the stack to
network_mode: host. This allows Traefik to route to other containers without the need to include them in a separate Docker network.
For those interested in adding TLS support via LetsEncrypt, the process is straightforward. Once you've set up the configuration correctly to Traefik itself, You also just have to add another label to your container pointing to the name of the resolver. This action alone is sufficient for Traefik to automatically retrieve and apply the necessary certificate.
Using Traefik with the Authentik Middleware
Traefik is also a good choice when you want to use it in conjunction with Authentik for safeguarding websites using Single Sign-On (SSO).
Below is a configuration example that incorporates this setup. Note that the Traefik dashboard is enabled in this example, and it's further secured using Authentik.
version: '3' services: reverse-proxy: image: traefik:v2.9 restart: always command: - --api.dashboard=true - --entryPoints.http.address=:80 # If you have another reverse proxy in front doing tls termination # - --entryPoints.http.forwardedHeaders.insecure # - --entryPoints.http.proxyProtocol.trustedIPs=10.10.10.101 - --entryPoints.traefik.address=:9401 - --providers.docker - --providers.docker.exposedByDefault=false - --providers.file.directory=/opt/traefik ports: # The HTTP port - "80:80" # The Web UI (enabled by --api.insecure=true) - "9401:9401" volumes: # So that Traefik can listen to the Docker events - /var/run/docker.sock:/var/run/docker.sock - /opt/traefik:/opt/traefik network_mode: host labels: - traefik.enable=true - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)" - "traefik.http.routers.dashboard.service=api@internal" - "traefik.http.routers.dashboard.middlewares=authentik@docker" - "traefik.http.services.dashboard.loadbalancer.server.port=9401" authentik-proxy: image: ghcr.io/goauthentik/proxy ports: - 9000:9000 environment: AUTHENTIK_HOST: https://auth.example.com # your authentik url AUTHENTIK_INSECURE: "false" AUTHENTIK_TOKEN: THETOKENWHICHAUTHENTIKGIVES labels: traefik.enable: true traefik.port: 9000 traefik.http.routers.authentik.rule: PathPrefix(`/outpost.goauthentik.io/`) # `authentik-proxy` refers to the service name in the compose file. traefik.http.middlewares.authentik.forwardauth.address: http://127.0.0.1:9000/outpost.goauthentik.io/auth/traefik traefik.http.middlewares.authentik.forwardauth.trustForwardHeader: true traefik.http.middlewares.authentik.forwardauth.authResponseHeaders: X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid,X-authentik-jwt,X-authentik-meta-jwks,X-authentik-meta-outpost,X-authentik-meta-provider,X-authentik-meta-app,X-authentik-meta-version restart: unless-stopped
With the above setup in place, you can protect any application by simply associating it with the Authentik middleware in another stack:
services: mycontainer: image: traefik/whoami # the whoami container is simply a hello world container showing the IP labels: - traefik.enable=true - traefik.http.routers.mycontainer.rule=Host(`mycontainer.myapp.cc`) - traefik.http.routers.mycontainer.middlewares=authentik@docker
Routing traffic to Virtual Machines
When it comes to routing traffic to VMs with Traefik, labels aren't an option. However, the dynamic configuration, managed through files, offers an elegant solution. In the configuration above, I mounted
/opt/traefik within the Traefik container. To set up routing to a VM, place a YAML configuration file in that directory:
Cool thing is, there's no need to restart it after modifying the configuration. Just place or edit the configuration, and Traefik will refresh itself automatically.
If there are errors, they will be shown in the Traefik logs (or logs of the traefik container). Also you can check traefiks dashboard if your setup works as intended.
One more handy feature to keep in mind: Traefik also supports the routing of TCP and UDP traffic!
The Strengths of Nginx: Advanced Caching and Beyond
While Traefik excels in containerized environments, one cannot downplay Nginx's prowess. Renowned for its ability to efficiently serve static content, Nginx comes equipped with sophisticated caching mechanisms.
Nginx offers capabilities that Traefik currently doesn't:
- Directly serve static files
- Do advanced caching (like cache streaming video files)
While a Traefik middleware could, in theory, offer these services, none exists for such purposes as of now.
- Modify or inject HTML at multiple points using Regex
- Secure Links
- Google PageSpeed Module
However, it's worth noting that with NGINX Proxy Manager, the full suite of NGINX's advanced features mentioned above remains out of reach.
Tools like NGINX Proxy Manager filled a gap before the advent of comprehensive reverse proxies like Traefik. But in the present landscape, it seems to amalgamate the limitations of both Nginx and Traefik: restricted features paired with manual configurations. While Nginx itself offers a broad feature set with manual configurations, Traefik provides a somewhat limited feature set with the beauty of automatic configurations. It's thus reasonable to advocate for a choice between raw Nginx or API-native proxies like Traefik.