[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.

feat: Add outline for traefik migration

FoxxMD (Apr 7, 2025, 5:36 PM UTC) 58f8a4f4 e07187f5

+224
+224
_posts/2025-04-07-migrating-to-traefik.md
··· 1 + --- 2 + title: Migrating from SWAG/NGIX to Traefik 3 + description: >- 4 + Moving multi-host external/internal services, SSL, cloudflare workers, and crowdsec to Traefik without Swarm 5 + author: FoxxMD 6 + date: 2025-04-07 10:00:00 -0400 7 + categories: [Tutorial] 8 + tags: [nginx, docker, traefik, crowdsec, ssl, dns] 9 + pin: false 10 + image: 11 + path: /assets/img/traefik/traefik-dashboard.webp 12 + alt: Traefik dashboard 13 + --- 14 + 15 + ## Background 16 + 17 + ### Why? 18 + 19 + * NGINX designed for single server topology 20 + * No service discovery 21 + * Middleware is hard 22 + * Relying on SWAG doesn't feel first class 23 + * ENVs for cert generation are unwieldy 24 + * Still kind of a hack, wish it was 1st party 25 + * crowdsec mod encourages tight coupling with SWAG scripts 26 + * cloudflare tunnels encourages tight coupling with SWAG scripts 27 + * service discovery mod designed for single host 28 + * my fork works but feelsbadman.jpg 29 + * No birdseye 30 + * No dashboard or metrics 31 + * Diagnosing config errors is hard 32 + * Should not need https://github.com/dvershinin/gixy to do this 33 + 34 + ### Requirements/Spec 35 + 36 + * Must be able to host services with web routing parity WRT NGINX configs 37 + * Cert management must be easier than SWAG 38 + * Must be able to validate via dns challenge 39 + * Must be able to validate multiple domains with wildcard subdomains 40 + * Must be able to integrate crowdsec 41 + * Must be able to integrate with cloudflare tunnels 42 + * Must be able to integrate with authentik 43 + * Service discovery must be easier 44 + * Must be feasible without Swarm/changing current docker topology (5+ hosts) 45 + * Must be able to separate internal/external services 46 + 47 + ### Evaluating Other Solutions 48 + 49 + * [Caddy](https://caddyserver.com/) 50 + * Requires third party module just for docker discovery https://github.com/lucaslorentz/caddy-docker-proxy 51 + * [Plugins require custom builds](https://github.com/serfriz/caddy-custom-builds) including cloudflare/crowdsec 52 + * [GoDoxy](https://github.com/yusing/godoxy) 53 + * Promising but too new 54 + 55 + ## Satifying Requirements with Traefik 56 + 57 + Overviews of how each [Requirement](#requirementsspec) was met 58 + 59 + ### Web Routing Parity 60 + 61 + The hardest requirement to meet. Unlike NGINX, 62 + 63 + * chaining together multiple transformations of a route is not as straightforward 64 + * Doing anything that short-circuits a route and isn't already a middleware isn't well documented or designed for 65 + * Traefik doesn't have as many middlewares (plugins) and well-documented, non-trivial "how to do X" examples 66 + 67 + Majority are straightforward, examples of SWAG default configs vs traefik labels 68 + 69 + #### Chaining Middleware with Non-Trivial Examples 70 + 71 + Mastodon example 72 + 73 + #### Redirect on non-existent Route 74 + 75 + Return 302 instead of 404 for wildcare routes. 76 + 77 + #### Missing How Do To X Example 78 + 79 + * Minio for mastodon, `customresponseheaders` 80 + * `ignorecert` and `insecureSkipVerify` usage 81 + 82 + ### Cert Management 83 + 84 + Easy. Traefik has built in management. 85 + 86 + `websecure.http.tls.certResolver` and `domains.main domains.sans` 87 + 88 + https://doc.traefik.io/traefik/https/acme/#providers 89 + https://go-acme.github.io/lego/dns/cloudflare/ 90 + 91 + ### Crowdsec Integration 92 + 93 + https://doc.traefik.io/traefik/providers/file/#go-templating 94 + https://masterminds.github.io/sprig/ --> https://masterminds.github.io/sprig/os.html 95 + 96 + https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin 97 + 98 + #### Access Logs 99 + 100 + Using custom json format to prevent buffering, drop headers, and keep user agent 101 + 102 + ```yaml 103 + accessLog: 104 + filePath: "/var/log/traefik/access.log" 105 + format: json 106 + # filters: 107 + # statusCodes: 108 + # - "200-299" # log successful http requests 109 + # - "400-599" # log failed http requests 110 + # collect logs as in-memory buffer before writing into log file 111 + bufferingSize: 0 112 + fields: 113 + headers: 114 + defaultMode: drop # drop all headers per default 115 + names: 116 + User-Agent: keep # log user agent strings 117 + ``` 118 + 119 + Using [vegardit/docker-traefik-logrotate](https://github.com/vegardit/docker-traefik-logrotate) to keep log files manageable. 120 + 121 + A simple alpine container tails logs to stdout (docker logs) and crowdsec ingests this via a docker-socket-proxy connection. 122 + 123 + 124 + ```yaml 125 + source: docker 126 + container_name: 127 + - traefik-external-traefik-access-logs-1 128 + docker_host: tcp://192.168.CONTAINER.IP:2375 129 + labels: 130 + type: traefik 131 + ``` 132 + {: file="acquis.yaml" } 133 + 134 + ### Cloudflare Tunnels Integration 135 + 136 + https://github.com/PseudoResonance/cloudflarewarp Real IP 137 + 138 + cf tunnel container using config from dashboard, host is traefik container name 139 + 140 + ### Authentik Integration 141 + 142 + https://docs.goauthentik.io/docs/add-secure-apps/providers/proxy/server_traefik 143 + 144 + ### Service Discovery 145 + 146 + Not using Swarm yet so discovery is done using a stack with [traefik-kop](https://github.com/jittering/traefik-kop) and docker-socket-proxy. 147 + 148 + #### Separating Interal/External Services 149 + 150 + Will eventually by done with [Swarm using `constraints`](https://doc.traefik.io/traefik/providers/swarm/#constraints) but traefik-kop has equivalent functionality using [label `namespace`](https://github.com/jittering/traefik-kop?tab=readme-ov-file#namespaces) 151 + 152 + ## Additional Traefik Functionality 153 + 154 + ### Viewing Realtime Logs 155 + 156 + Using Logdy with [access logs json file](#access-logs) 157 + 158 + ### Metrics and Dashboard 159 + 160 + Internal dashboards for troubleshooting errors and checking status 161 + 162 + Metrics exported in prometheus format and collected into [Traefik Official Standalone Dashboard](https://grafana.com/grafana/dashboards/17346-traefik-official-standalone-dashboard/) with modifications for entrypoint/alias. 163 + 164 + ```yaml 165 + api: 166 + dashboard: true 167 + insecure: true 168 + metrics: 169 + prometheus: 170 + buckets: 171 + - 0.1 172 + - 0.3 173 + - 1.2 174 + - 5.0 175 + ``` 176 + {: file="static_config/traefik.yaml" } 177 + 178 + ## Traefik Gotchas 179 + 180 + ### Ambiguous Config Sources and Documentation 181 + 182 + * Difficult to easily grok what the difference between static/dynamic config is 183 + * Difficult to determine where static/dynamic config should be placed in dir/files 184 + * Not obvious that config from separate sources can be used anywhere (file middleware in labels, docker plugin config in files) 185 + * Parsed config sources not listed anywhere by name 186 + 187 + ### Finding Config Errors 188 + 189 + * Dynamic config issues immediate feedback, visible in dashboard 190 + * Static config does not appear until restart and traefik will bulldoze over silently 191 + * Errors only show in docker logs but traefik will startup anyways 192 + * May cause dashboard to entirely disappear if error affects internal routes 193 + * Errors may look like dynamic if the error trickles downstream 194 + * So..always check docker logs **first** and always restart traefik after any static config changes 195 + 196 + ### Clobbering Labels 197 + 198 + * Copy-pasting labels, may forget service/router name is same as existing 199 + * Traefik does not complain about this unless it causes actually issues (router issue) but will still cause reachability issues while running 200 + 201 + ### Multiple Services, Same Container 202 + 203 + Not an issue but not well documented 204 + 205 + * Traefik will complain if multiple routers on container labels but only one service 206 + * Can set service for router on label explicitly 207 + * Docs examples focus on service name being implicitly define 208 + Ex 209 + 210 + ```yaml 211 + # ... 212 + labels: 213 + traefik.http.routers.mastodon.service: mastodon 214 + #... 215 + traefik.http.routers.mastodon-root.service: mastodon 216 + # ... 217 + traefik.http.services.mastodon.loadbalancer.server.port: 443 218 + ``` 219 + 220 + ## Full Examples 221 + 222 + ```yaml 223 + here be stacks 224 + ```
assets/img/traefik/traefik-dashboard.png

This is a binary file and will not be displayed.

assets/img/traefik/traefik-dashboard.webp

This is a binary file and will not be displayed.