[READ-ONLY] Mirror of https://github.com/FoxxMD/blog. blog.foxxmd.dev
0

Configure Feed

Select the types of activity you want to include in your feed.

fix(traefik_migrate): Proofread cleanup pt 1.

Co-authored-by: thenoiseinspace <thenoiseinspace@users.noreply.github.com>

FoxxMD (Apr 16, 2025, 8:30 AM EDT) 1cd7d302 53ac941b

+24 -24
+24 -24
_posts/2025-04-07-migrating-to-traefik.md
··· 21 21 22 22 This is the single most important reason I had for migrating away from NGINX. 23 23 24 - In a homelab environment where services are being spun up/down, created, moved between hosts, renamed...having to 1) remember to update NGINX config and then restart it 2) keep track of IP:PORT or specific config per application is a pain. NGINX was designed during a time when network topology wasn't so dynamic, it's not built with a homelab use-case in mind. It's also not built with today's paradigms in mind such as [first-class environmental substitution](https://www.baeldung.com/linux/nginx-config-environment-variables) or [easy-to-read config validation.](https://github.com/dvershinin/gixy) 24 + In a homelab environment where services are being spun up/down, created, moved between hosts, renamed, etc...having to 1) remember to update NGINX config and then restart it and 2) keep track of IP:PORT or specific config per application is a pain. NGINX was designed during a time when network topology wasn't so dynamic, so it's not built with a homelab use-case in mind. It's also not built with today's paradigms in mind, such as [first-class environmental substitution](https://www.baeldung.com/linux/nginx-config-environment-variables) or [easy-to-read config validation.](https://github.com/dvershinin/gixy) 25 25 26 26 ##### NGINX Service Configuration Ownership 27 27 28 - As my homelab continues to grow I have gravitated towards each Stack owning its own configuration. Through mechanisms like `environment` in `compose.yaml` or configs committed to git alongside `compose.yaml` etc.. having the service-as-code live next to all the data needed run the service makes it more portable and reduces the cognitive scope required to configure it. NGINX config files need to be physically accessible to it and that is not a *feasible* option when services run on other hosts. 28 + As my homelab continues to grow I have gravitated towards each Stack owning its own configuration. Through mechanisms like `environment` in `compose.yaml` or configs committed to git alongside `compose.yaml` etc., having the service-as-code live next to all the data needed run the service makes it more portable and reduces the cognitive scope required to configure it. NGINX config files need to be physically accessible to it and that is not a *feasible* option when services run on other hosts. 29 29 30 30 ##### SWAG is Tightly Coupled and Opinionated 31 31 32 - LSIO does an excellent job making setup with NGINX easy by using [SWAG](https://docs.linuxserver.io/general/swag/). For simple setups and users just dipping their toes into the space it's a fantastic tool for getting started quickly without requiring any hand holding. 32 + LSIO does an excellent job making setup with NGINX easy by using [SWAG](https://docs.linuxserver.io/general/swag/). For simple setups and users just dipping their toes into the space it's a fantastic tool for getting started quickly without requiring any hand-holding. 33 33 34 - However, it has shortfalls which appear for more complex use-cases. Some of these are limitations of nginx such as needing the user to [edit .ini files for DNS ACME challenge while other solutions only need ENVs](http://localhost:4000/posts/migrating-to-traefik/#wildcards). 34 + However, it has shortfalls which appear for more complex use-cases. Some of these are limitations of nginx, such as needing the user to [edit .ini files for DNS ACME challenge, while other solutions only need ENVs](http://localhost:4000/posts/migrating-to-traefik/#wildcards). 35 35 36 - Others are due to the reality of limited developer-hours needing to fulfill only the most common use-case, like LSIO's [cloudflare docker mod](https://github.com/linuxserver/docker-mods/tree/universal-cloudflared) configuration only working with one domain even though a tunnel can be used for multiple domains -- one domain is the most common use-case and easiest to script for. In this scenario "fixing" the problem means refactoring the entire stack to remove universal-cloudflare and implementing your own `cloudflared` container. 36 + Others are due to the reality of limited developer-hours needing to fulfill only the most common use-case, like LSIO's [cloudflare docker mod](https://github.com/linuxserver/docker-mods/tree/universal-cloudflared) configuration only working with one domain even though a tunnel can be used for multiple domains -- one domain is the most common use-case and easiest to script for. In this scenario, "fixing" the problem means refactoring the entire stack to remove universal-cloudflare and implementing your own `cloudflared` container. 37 37 38 - If most scenarios end with the user having to implement the decoupled solution anyways...why not consider other reverse proxy solutions since we aren't tied to SWAG anymore? 38 + If most scenarios end with the user having to implement the decoupled solution anyway...why not consider other reverse proxy solutions since we aren't tied to SWAG anymore? 39 39 40 40 ##### Lack of Dashboard 41 41 ··· 47 47 48 48 SWAG offers the docker mod [swag-auto-proxy](https://github.com/linuxserver/docker-mods/tree/swag-auto-proxy) which generates nginx confs for services discovered by docker label on the same machine NGINX is running on. I forked this as [swag-auto-proxy-multi](https://github.com/FoxxMD/docker-mods/tree/swag-auto-proxy-multi) and wrote new functionality to enable it to work with multiple hosts using [docker-socket-proxy](https://docs.linuxserver.io/images/docker-socket-proxy/). 49 49 50 - While this does work it's not *good.* The inner workings are a mess of bash scripts that generate nginx confs and reload *the entire application* every time new changes are made. It's also barebones compared to applications like Traefik that have [service discovery as a first-class feature.](https://traefik.io/glossary/service-discovery/) 50 + While this does work, it's not *good.* The inner workings are a mess of bash scripts that generate nginx confs and reload *the entire application* every time new changes are made. It's also barebones compared to applications like Traefik that have [service discovery as a first-class feature.](https://traefik.io/glossary/service-discovery/) 51 51 52 52 ### Requirements/Spec 53 53 54 54 Before YOLO'ing another reverse-proxy solution I came up with a list of requirements that needed to met. I have 60+ stacks, over 100 containers, running on 7 machines. 55 55 56 - If I am going to switch and do all the work to get most of these served then the new solution was going to have to 1) have **feature parity** with SWAG + features I use with it (auth) and 2) be **easier** to implement with all my machines than the current swag + auto-proxy setup. 56 + If I am going to switch and do all the work to get most of these served, then the new solution was going to have to 1) have **feature parity** with SWAG + features I use with it (auth) and 2) be **easier** to implement with all my machines than the current swag + auto-proxy setup. 57 57 58 58 The requirements: 59 59 ··· 85 85 86 86 I eventually settled on [Traefik](https://traefik.io/) after due-diligence gave me enough confidence to think I could satisfy all of the [Requirement](#requirementsspec). 87 87 88 - The actual implementation is always more difficult than how it looks on paper but in the end it met every requirement and ended up being easy to maintain and work with! 88 + The actual implementation is always more difficult than how it looks on paper, but in the end, it met every requirement and ended up being easy to maintain and work with! 89 89 90 90 In each section below I lightly cover the differences between SWAG/Traefik, what was needed to migrate to Traefik, and how I implemented it along with examples, if necessary. 91 91 ··· 280 280 281 281 Traefik requires setting up a [cert resolver and entrypoint](https://doc.traefik.io/traefik/https/acme/#configuration-examples) which can be done via file or as labels on a docker container (not recommended). 282 282 283 - One advantage Traefik has is that it will [automatically generate certs](https://doc.traefik.io/traefik/https/acme/#domain-definition) for **all domains** found during service discovery. That is, if you have the label 283 + One advantage Traefik has is that it will [automatically generate certs](https://doc.traefik.io/traefik/https/acme/#domain-definition) for **all domains** found during service discovery (that is, if you have the label) 284 284 285 285 ```yaml 286 286 labels: ··· 304 304 305 305 #### Wildcards 306 306 307 - But this is where Traefik really shines. To use wildcard certs with Traefik we add a few more lines to our existing static config, specifying the dns challenge provider and explicitly defining the domains: 307 + But this is where Traefik really shines. To use wildcard certs with Traefik, we add a few more lines to our existing static config, specifying the dns challenge provider and explicitly defining the domains: 308 308 309 309 ```diff 310 310 entryPoints ··· 366 366 367 367 #### Cloudflare Tunnels {#certs-and-cf-tunnels} 368 368 369 - When using [Cloudflare Tunnels](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) no cert generation/management is required. 369 + When using [Cloudflare Tunnels](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/), no cert generation/management is required. 370 370 371 371 <details markdown="1"> 372 372 ··· 451 451 452 452 * Separating access logs from regular Traefik logs 453 453 * Troubleshooting traefik-specific issues from logs is much easier (less noise in container logs) 454 - * Write to file persists access logs after restart 454 + * Write to file persists the access logs after restart 455 455 * Exposing/using `log-tail` container enables 456 456 * [traefik log acquistion](https://docs.crowdsec.net/docs/next/log_processor/data_sources/docker) to be done with a docker connection locally or remotely, and by container name. Instead of needing to mount log folders/files into a crowdsec container (locally only) and having to deal with permissions. 457 457 * access logs are consumable/viewable in other apps (when using `json` access log format, readable in [Dozzle](https://dozzle.dev/) or Logdy) ··· 732 732 733 733 ### Cloudflare Tunnels Integration 734 734 735 - CF Tunnels setup is markedly different than SWAG but functionally the same once setup. 735 + CF Tunnels setup is markedly different than SWAG but functionally the same once set up. 736 736 737 737 For CF Tunnels with SWAG there are two [LSIO docker mods](https://docs.linuxserver.io/general/container-customization/#docker-mods) that are used: 738 738 ··· 825 825 826 826 Finally, we need to configure traefik to substitute the value of the header `Cf-Connecting-IP` CF Tunnel attaches to our traffic into the `X-Forwarded-For` header. This will ensure that logs/metrics and downstream applications see the IP of the actual origin host rather than CF's edge server IPs. 827 827 828 - To this we first install the [traefik plugin](https://doc.traefik.io/traefik/plugins/) [cloudflarewarp](https://github.com/PseudoResonance/cloudflarewarp) by defining it in our **static config**: 828 + To do this we first install the [traefik plugin](https://doc.traefik.io/traefik/plugins/) [cloudflarewarp](https://github.com/PseudoResonance/cloudflarewarp) by defining it in our **static config**: 829 829 830 830 ```yaml 831 831 # add this to the /etc/traefik/traefik.yaml example above ··· 853 853 ``` 854 854 {: file="/config/dynamic/global.yaml" } 855 855 856 - To use with the entrypoint we setup earlier in our static config add `entryPoints.cf.http.middlewares` with our middlename@provider: 856 + To use with the entrypoint we setup earlier in our static config add `entryPoints.cf.http.middlewares` with our `middleware@provider`: 857 857 858 858 ```yaml 859 859 # ... building on previous static config ··· 960 960 service: 961 961 # ... 962 962 authentik-proxy: 963 - image: ghcr.io/goauthentik/proxy:${AUTHENTIK_TAG:-2024.10.5} 963 + image: ghcr.io/goauthentik/proxy:${AUTHENTIK_TAG:-2024.10.5}are 964 964 # ... 965 965 environment: 966 966 - AUTHENTIK_HOST: https://your-authentik.tld ··· 992 992 993 993 Finally, the easy part! In [Authentik's Traefik guide](https://docs.goauthentik.io/docs/add-secure-apps/providers/proxy/server_traefik) setup the authentik middleware using the **Standalone traefik** sample for `http.middlewares.authentik` in a [dyanamic file config](https://doc.traefik.io/traefik/providers/file/), which creates the middleware `authentik@file`. 994 994 995 - Alternatively, if you included all the labels from the **docker-compose** sample for `authentik-proxy` then it is already setup (`traefik.http.middlewares.authentik.forwardauth.*`) and can be used with the middleware `authentik-proxy@docker`. 995 + Alternatively, if you included all the labels from the **docker-compose** sample for `authentik-proxy`, then it is already setup (`traefik.http.middlewares.authentik.forwardauth.*`) and can be used with the middleware `authentik-proxy@docker`. 996 996 997 997 ### Service Discovery 998 998 ··· 1072 1072 1073 1073 #### Multi-Host Docker Discovery 1074 1074 1075 - If you have multiple machines running Docker and want Traefik to route to all of them you have a few choices. 1075 + If you have multiple machines running Docker and want Traefik to route to all of them, you have a few choices. 1076 1076 1077 - ##### Dynamic files and Docker Discovery {#multi-host-files} 1077 + ##### Dynamic files and Docker Discovery {#multi-host-files}are 1078 1078 1079 - Use [**Dynamic files**](#nginx-equivalent) if it's only one or two, unchanging services. Using this approach as well as the [docker provider](#docker-discovery) for services on the same machine is *doable* if the "other host" services are exceptions/unchanging and can be reached over the bridge network (host IP:PORT). 1079 + Use [**Dynamic files**](#nginx-equivalent) if it's only one or two unchanging services. Using this approach as well as the [docker provider](#docker-discovery) for services on the same machine is *doable* if the "other host" services are exceptions/unchanging and can be reached over the bridge network (host IP:PORT). 1080 1080 1081 1081 ##### Docker Swarm 1082 1082 ··· 1136 1136 ``` 1137 1137 {: file="/etc/traefik/traefik.yaml"} 1138 1138 1139 - Next, on each docker host create a new stack for kop. I prefer to connect it to docker using [docker-socket-proxy](https://docs.linuxserver.io/images/docker-socket-proxy) since it only needs limited, read-only capabilities. 1139 + Next, on each docker host, create a new stack for kop. I prefer to connect it to docker using [docker-socket-proxy](https://docs.linuxserver.io/images/docker-socket-proxy) since it only needs limited, read-only capabilities. 1140 1140 1141 1141 ```yaml 1142 1142 services: ··· 1171 1171 1172 1172 **Watch out for port used with traefik!** 1173 1173 1174 - If you are using **host/bridge IP** for the container then the port must be published and the port for traefik must be the "external" port... 1174 + If you are using **host/bridge IP** for the container, then the port must be published and the port for traefik must be the "external" port... 1175 1175 1176 1176 ```yaml 1177 1177 services: ··· 1750 1750 1751 1751 ___ 1752 1752 1753 - ## Footnotes 1753 + ## Footnotes