Traefik v3.6¶
Traefik serves as the edge router on apps-gateway, handling all incoming HTTP/HTTPS traffic, routing to services via Docker Swarm labels, and managing TLS certificates.
Current Configuration¶
Stack file: /opt/swarm/stacks/edge/stack.traefik.yml
image: traefik:v3.6
ports: 80, 443, 8080 (metrics/dashboard)
placement: node.hostname == apps-gateway
networks: public_net, data_net
resources: 256MB RAM / 0.5 CPU limit
Port 8080
Port 8080 is published for Prometheus metrics scraping from dockerrr. The dashboard is protected by BasicAuth middleware. Prometheus on dockerrr scrapes 192.168.50.10:8080/metrics.
Key Command Flags¶
| Flag | Purpose |
|---|---|
--providers.swarm=true |
Service discovery via Docker Swarm |
--providers.swarm.exposedbydefault=false |
Services must opt-in with traefik.enable=true |
--providers.swarm.network=public_net |
Default network for service discovery |
--entrypoints.web.address=:80 |
HTTP entrypoint |
--entrypoints.websecure.address=:443 |
HTTPS entrypoint |
--entrypoints.web.http.redirections... |
Auto-redirect HTTP to HTTPS |
--certificatesresolvers.letsencrypt... |
Let's Encrypt ACME via HTTP-01 challenge |
--certificatesresolvers.letsencrypt-dns... |
Let's Encrypt ACME via Cloudflare DNS-01 challenge |
--metrics.prometheus=true |
Prometheus metrics endpoint |
Cloudflare Trusted IPs¶
Traefik is configured to trust Cloudflare IP ranges for X-Forwarded-* headers, ensuring real client IPs are preserved through the proxy chain.
Routing¶
Label-Based Routing¶
Services configure their routing via Docker deploy labels:
deploy:
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`example.com`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
- "traefik.http.services.myapp.loadbalancer.server.port=3000"
Current Routes¶
| Domain | Service | Port |
|---|---|---|
recipicity.com |
app-recipicity-production_frontend | 80 |
recipicity.com/api |
app-recipicity-production_api | 3000 |
recipicity.com/admin |
app-recipicity-production_admin | 80 |
staging.recipicity.com |
app-recipicity-staging_frontend | 3000 |
staging.recipicity.com/api |
app-recipicity-staging_api | 3000 |
admin-staging.recipicity.com |
app-recipicity-staging-admin_admin | 80 |
jlwaller.com |
app-jlwaller_jlwaller | 80 |
pingcast.jlwaller.com |
app-pingcast_pingcast | 3000 |
downloads.jlwaller.com |
app-downloads_downloads | 80 |
apps.jlwaller.com |
app-portal_homepage | 80 |
docs.apps.jlwaller.com |
app-portal_docs | 80 |
registry.apps.jlwaller.com |
registry_registry | 5000 |
minio.apps.jlwaller.com |
data_minio (console) | 9001 |
vault.apps.jlwaller.com |
data_vault | 8200 |
traefik.apps.jlwaller.com |
api@internal (dashboard) | -- |
Middleware¶
Defined Middleware¶
| Middleware | Type | Purpose |
|---|---|---|
staging-auth |
BasicAuth | Protect staging environments (htpasswd secret) |
downloads-auth |
BasicAuth | Protect downloads site |
security-headers |
Headers | HSTS, X-Frame-Options, XSS protection |
rate-limit |
RateLimit | 100 req/min average, 50 burst |
dashboard-auth |
BasicAuth | Protect Traefik dashboard (htpasswd secret) |
Using Middleware¶
Multiple middleware can be chained: middlewares=staging-auth,security-headers
BasicAuth Setup¶
# Generate htpasswd entry (use single $ in file, $$ in YAML labels)
htpasswd -nbB username password
# For file-based (preferred for secrets):
- "traefik.http.middlewares.myauth.basicauth.usersfile=/run/secrets/htpasswd"
TLS / SSL¶
Certificate Resolvers¶
| Resolver | Challenge | Use Case | |
|---|---|---|---|
letsencrypt |
HTTP-01 | Public domains (recipicity.com, jlwaller.com, etc.) | admin@generationsoftrust.com |
letsencrypt-dns |
DNS-01 (Cloudflare) | Internal-only domains (apps.jlwaller.com, docs.apps.jlwaller.com) | admin@jlwaller.com |
Certificate Chain (Public Domains)¶
- Cloudflare FULL mode: CF validates that Traefik presents a valid certificate
- Let's Encrypt HTTP-01: Traefik auto-obtains certs via HTTP challenge
- Internal: Traefik to app containers is plain HTTP on overlay network
Certificate Chain (Internal Domains)¶
- DNS-01 challenge: Traefik uses Cloudflare API to create
_acme-challengeTXT records - No public access required: Domains only resolve on LAN via UDM local DNS
- Cloudflare API token: Stored as Docker secret
cf_dns_api_token(jlwaller.com zone only) - Certificates auto-renew 30 days before expiry
Certificate Storage¶
| File | Resolver | Purpose |
|---|---|---|
/letsencrypt/acme.json |
letsencrypt |
HTTP-01 challenge certs |
/letsencrypt/acme-dns.json |
letsencrypt-dns |
DNS-01 challenge certs |
Both stored in the traefik_letsencrypt Docker volume.
Dashboard¶
URL: https://traefik.apps.jlwaller.com (internal network only, NOT internet-exposed)
# API queries
curl http://localhost:8080/api/http/routers
curl http://localhost:8080/api/http/services
curl http://localhost:8080/api/http/middlewares
Troubleshooting¶
Service Not Routing¶
- Check service has
traefik.enable=truelabel - Verify service is on
public_netnetwork - Check
traefik.docker.network=public_netlabel if on multiple networks - Verify port matches container's listening port
- Check
docker service logs edge_traefikfor errors