Skip to content

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

deploy:
  labels:
    - "traefik.http.routers.myapp.middlewares=staging-auth"

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 Email
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)

Client -> Cloudflare (CF cert) -> Traefik (Let's Encrypt cert) -> App (HTTP)
  • 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)

Client (LAN) -> Traefik (Let's Encrypt cert via DNS-01) -> App (HTTP)
  • DNS-01 challenge: Traefik uses Cloudflare API to create _acme-challenge TXT 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

  1. Check service has traefik.enable=true label
  2. Verify service is on public_net network
  3. Check traefik.docker.network=public_net label if on multiple networks
  4. Verify port matches container's listening port
  5. Check docker service logs edge_traefik for errors

Redeploy Traefik

docker stack deploy -c /opt/swarm/stacks/edge/stack.traefik.yml edge

Logs

# Follow service logs
docker service logs -f edge_traefik

# Access log location inside container
/logs/access.log
/logs/traefik.log