Skip to main content
Traefik is a modern reverse proxy and load balancer that routes HTTP traffic to the appropriate services in the Joystick IoT platform.

Overview

Traefik automatically discovers services via Docker labels and configures routing rules, provides TLS termination, and adds middleware for CORS and path manipulation.

Configuration

Docker Compose

traefik:
  image: traefik:v2.11
  platform: linux/amd64
  restart: unless-stopped
  ports:
    - "80:80"
    - "8080:8080"
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock
  command:
    - "--log.level=DEBUG"
    - "--providers.docker"
    - "--providers.docker.exposedbydefault=false"
    - "--entrypoints.http.address=:80"
    - "--api.insecure=true"
    - "--api.dashboard=true"
  networks:
    - app-network
  extra_hosts:
    - "host.docker.internal:host-gateway"

Ports

PortPurpose
80HTTP entrypoint
8080Dashboard and API

Features

Automatic service discovery

Traefik discovers services via Docker labels:
labels:
  - "traefik.enable=true"
  - "traefik.http.routers.service.rule=Host(`localhost`) && PathPrefix(`/service`)"
  - "traefik.http.services.service.loadbalancer.server.port=8000"

Dynamic routing

Routes update automatically when:
  • Containers start or stop
  • Labels are added or changed
  • Services scale up or down

Middleware support

Traefik applies middleware for:
  • Path stripping
  • CORS headers
  • Authentication
  • Rate limiting
  • Compression

Routing configuration

All services are routed through Traefik:

App (frontend)

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.app.rule=Host(${HOST:-localhost})"
  - "traefik.http.services.app.loadbalancer.server.port=80"
Access at: http://localhost/

Joystick API

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.joystick.rule=Host(${HOST:-localhost}) && PathPrefix(`/joystick`)"
  - "traefik.http.middlewares.joystick-strip.stripprefix.prefixes=/joystick"
  - "traefik.http.routers.joystick.middlewares=joystick-strip,cors"
  - "traefik.http.services.joystick.loadbalancer.server.port=8000"
Access at: http://localhost/joystick/

Panel UI

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.panel.rule=Host(${HOST:-localhost}) && PathPrefix(`/panel`)"
  - "traefik.http.middlewares.panel-strip.stripprefix.prefixes=/panel"
  - "traefik.http.routers.panel.middlewares=panel-strip,cors"
  - "traefik.http.services.panel.loadbalancer.server.port=4000"
Access at: http://localhost/panel/

Baker API

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.baker.rule=Host(${HOST:-localhost}) && PathPrefix(`/baker`)"
  - "traefik.http.middlewares.baker-strip.stripprefix.prefixes=/baker"
  - "traefik.http.routers.baker.middlewares=baker-strip,cors"
  - "traefik.http.services.baker.loadbalancer.server.port=3000"
Access at: http://localhost/baker/

Switcher API

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.switcher.rule=Host(${HOST:-localhost}) && PathPrefix(`/switcher`)"
  - "traefik.http.middlewares.switcher-strip.stripprefix.prefixes=/switcher"
  - "traefik.http.routers.switcher.middlewares=switcher-strip,cors"
  - "traefik.http.services.switcher.loadbalancer.server.port=8080"
Access at: http://localhost/switcher/

Whisper API

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.whisper.rule=Host(${HOST:-localhost}) && PathPrefix(`/whisper`)"
  - "traefik.http.middlewares.whisper-strip.stripprefix.prefixes=/whisper"
  - "traefik.http.routers.whisper.middlewares=whisper-strip,cors"
  - "traefik.http.services.whisper.loadbalancer.server.port=8081"
Access at: http://localhost/whisper/

Studio API

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.studio.rule=Host(${HOST:-localhost}) && PathPrefix(`/studio`)"
  - "traefik.http.middlewares.studio-strip.stripprefix.prefixes=/studio"
  - "traefik.http.routers.studio.middlewares=studio-strip,cors"
  - "traefik.http.services.studio.loadbalancer.server.port=8001"
Access at: http://localhost/studio/

Middleware

CORS middleware

Shared CORS configuration:
x-cors-labels: &cors-labels
  - "traefik.http.middlewares.cors.headers.accesscontrolallowmethods=*"
  - "traefik.http.middlewares.cors.headers.accesscontrolallowheaders=*"
  - "traefik.http.middlewares.cors.headers.accesscontrolalloworiginlist=*"
  - "traefik.http.middlewares.cors.headers.addvaryheader=true"

StripPrefix middleware

Removes path prefix before forwarding:
- "traefik.http.middlewares.joystick-strip.stripprefix.prefixes=/joystick"
Example:
  • Request: http://localhost/joystick/api/health
  • Forwarded: http://joystick:8000/api/health

Dashboard

Traefik dashboard available at:
http://localhost:8080

Dashboard features

  • Routers - View all configured routes
  • Services - See backend services and health
  • Middleware - Inspect middleware configuration
  • Providers - View Docker provider status

Docker provider

Traefik uses Docker provider for service discovery:
command:
  - "--providers.docker"
  - "--providers.docker.exposedbydefault=false"

Configuration

  • exposedbydefault=false - Only expose labeled services
  • Watches Docker socket for container changes
  • Updates routing in real-time

Entrypoints

HTTP entrypoint

command:
  - "--entrypoints.http.address=:80"
Listens on port 80 for HTTP traffic.

Future: HTTPS entrypoint

For production, add HTTPS:
command:
  - "--entrypoints.https.address=:443"
  - "[email protected]"
  - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
  - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http"

Host configuration

The HOST environment variable controls the hostname:
# Default
HOST=localhost

# Custom domain
HOST=iot.example.com
Services use this for routing rules:
- "traefik.http.routers.app.rule=Host(${HOST:-localhost})"

Load balancing

Traefik can load balance across multiple service instances:
# Scale service
docker-compose up -d --scale joystick=3
Traefik automatically discovers all instances and distributes traffic.

Health checks

Traefik monitors service health:
labels:
  - "traefik.http.services.joystick.loadbalancer.healthcheck.path=/api/health"
  - "traefik.http.services.joystick.loadbalancer.healthcheck.interval=10s"

Logging

Configure log level:
command:
  - "--log.level=DEBUG"  # DEBUG, INFO, WARN, ERROR
View logs:
docker logs -f joystick-traefik-1

Security

IP allowlist

Restrict access by IP:
labels:
  - "traefik.http.middlewares.admin-ip.ipallowlist.sourcerange=192.168.1.0/24"
  - "traefik.http.routers.admin.middlewares=admin-ip"

Basic authentication

Add basic auth:
labels:
  - "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$..."
  - "traefik.http.routers.service.middlewares=auth"

Rate limiting

Limit request rate:
labels:
  - "traefik.http.middlewares.ratelimit.ratelimit.average=100"
  - "traefik.http.middlewares.ratelimit.ratelimit.burst=50"

Troubleshooting

Service not accessible

  • Check traefik.enable=true label exists
  • Verify routing rule matches request
  • Review Traefik dashboard for router status
  • Check service is running and healthy

CORS errors

  • Verify CORS middleware is applied
  • Check middleware configuration
  • Test with curl to see headers

Path issues

  • Verify StripPrefix middleware
  • Check service expects correct path
  • Test direct service access

Dashboard not accessible

  • Verify --api.insecure=true is set
  • Check port 8080 is exposed
  • Ensure not blocked by firewall

Best practices

Use path prefixes

Organize services with clear paths:
/              -> Frontend app
/joystick/     -> Joystick API
/panel/        -> Panel UI
/baker/        -> Baker API

Enable CORS middleware

Always add CORS for API services:
middlewares=service-strip,cors

Monitor dashboard

Regularly check dashboard for:
  • Service health status
  • Routing configuration
  • Error rates

Secure in production

For production deployments:
  • Disable insecure API
  • Enable HTTPS
  • Add authentication
  • Implement rate limiting
  • Use IP allowlists

Migration to production

Production Traefik configuration:
traefik:
  image: traefik:v2.11
  ports:
    - "80:80"
    - "443:443"
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock
    - ./letsencrypt:/letsencrypt
  command:
    - "--log.level=INFO"
    - "--providers.docker"
    - "--providers.docker.exposedbydefault=false"
    - "--entrypoints.http.address=:80"
    - "--entrypoints.https.address=:443"
    - "[email protected]"
    - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http"
    - "--api.dashboard=true"

Build docs developers (and LLMs) love