Skip to content

Docker Compose Deployment behind Traefik Reverse Proxy

Snawoot edited this page Jul 30, 2025 · 14 revisions

This example exposes dumbproxy via Traefik reverse proxy, routing connections by domain in SNI of TLS handshake. TLS connection is handled on Traefik side, dumbproxy gets plaintext HTTP connection. Original client address is preserved and passed to dumbproxy (-proxyproto option).

Full configuration files are provided for

Important

Don't forget to replace <your-domain.com> and <your-email> with corresponding literal values.

docker-compose.yaml:

networks:
  traefik:

services:
  dumbproxy:
    image: ghcr.io/senseunit/dumbproxy:1.27.0
    command:
      - -bind-address=:8080
      - -proxyproto
    deploy:
      labels:
        traefik.enable: "true"
        traefik.tcp.routers.dumbproxy.service: dumbproxy
        traefik.tcp.routers.dumbproxy.rule: HostSNI(`<your-domain>`)
        traefik.tcp.routers.dumbproxy.tls: "true"
        traefik.tcp.routers.dumbproxy.tls.passthrough: "false"
        traefik.tcp.services.dumbproxy.loadBalancer.server.port: 8080
        traefik.tcp.services.dumbproxy.loadbalancer.proxyProtocol.version: 2
    networks:
      traefik:

  traefik:
    image: traefik:v3.5.0
    networks:
      traefik:
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - db-data:/var/lib/backup/data
      - /var/run/docker.sock:/var/run/docker.sock
      - ./traefik.yaml:/etc/traefik/traefik.yaml
      - ./dynamic.yaml:/etc/traefik/dynamic.yaml
      - ./acme.json:/etc/traefik/acme.json

configs/traefik.yaml:

global:
  checkNewVersion: false
  sendAnonymousUsage: false

log:
  level: INFO
  noColor: false
  filePath: /dev/stdout

accessLog:
  filePath: /dev/stdout

providers:
  swarm:
    exposedByDefault: false
    endpoint: unix:///var/run/docker.sock
    network: traefik_proxy
    watch: true
  file:
    filename: /etc/traefik/dynamic.yaml
    watch: true

api:
  dashboard: false

entryPoints:
  web:
    address: :80
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
          permanent: true

  websecure:
    address: :443
    forwardedHeaders:
      insecure: true
    http:
      tls:
        certResolver: letsencrypt
      middlewares:
        - default@file

certificatesResolvers:
  letsencrypt:
    acme:
      email: <your-email>
      storage: /etc/traefik/acme.json
      tlsChallenge: {}

configs/dynamic.yaml:

tls:
  options:
    default:
      minVersion: VersionTLS12
      maxVersion: VersionTLS13
      sniStrict: true
      curvePreferences:
        - CurveP521
        - CurveP384
      alpnProtocols:
        - http/1.1
        - h2
        - acme-tls/1

http:
  middlewares:
    default:
      headers:
        browserXssFilter: true
        referrerPolicy: strict-origin-when-cross-origin
        stsPreload: true
        sslRedirect: true
        sslTemporaryRedirect: true
        sslForceHost: true
        stsSeconds: "31536000"
        forceSTSHeader: true
        frameDeny: true
        contentSecurityPolicy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'
        contentSecurityPolicyReportOnly: default-src 'self'; script-src 'self' 'nonce-{random}'
        contentTypeNosniff: true
        customResponseHeaders:
          X-Forwarded-Server: traefik
          Permissions-Policy: camera=(), microphone=(), geolocation=()
          Cross-Origin-Embedder-Policy: require-corp
          Cross-Origin-Resource-Policy: same-origin
          Cross-Origin-Opener-Policy: same-origin

Clone this wiki locally