Traefik: обратный прокси для Docker без головной боли

Когда на VPS работает один сайт на Nginx — всё понятно. Когда их пятнадцать в Docker, каждый на своём порту, плюс нужен HTTPS на каждый домен — начинается ручное управление reverse proxy, certbot cron-job-ами и таблицами портов в голове. Traefik решает именно эту проблему: он смотрит на запущенные контейнеры, читает их метки и сам строит маршруты. Вы не трогаете конфиг Traefik при каждом деплое нового сервиса — он обнаруживает его автоматически.

Как Traefik отличается от Nginx в роли reverse proxy

Nginx — отличный reverse proxy, но он статичный. Вы добавляете новый сервис — добавляете новый server block, делаете reload. В контейнерном окружении при 10-20 сервисах это превращается в рутину. Traefik изначально спроектирован под динамические окружения: он слушает Docker socket и обновляет конфигурацию маршрутизации без перезапуска.

Второе отличие — встроенный Let's Encrypt. Traefik сам получает и обновляет TLS-сертификаты через ACME (HTTP challenge или DNS challenge), хранит их и применяет к нужным маршрутам. Certbot становится не нужен.

Третье — встроенный dashboard с визуализацией всех роутеров, сервисов и middleware.

Базовая установка через Docker Compose

Минимальная конфигурация для production-окружения с Let's Encrypt:

version: "3.8" services:  traefik:    image: traefik:v3.0    container_name: traefik    restart: unless-stopped    command:      - "--api.dashboard=true"      - "--providers.docker=true"      - "--providers.docker.exposedbydefault=false"      - "--entrypoints.web.address=:80"      - "--entrypoints.websecure.address=:443"      - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"    ports:      - "80:80"      - "443:443"    volumes:      - "/var/run/docker.sock:/var/run/docker.sock:ro"      - "./letsencrypt:/letsencrypt"    labels:      - "traefik.enable=true"      - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"      - "traefik.http.routers.dashboard.service=api@internal"      - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"      - "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$xyz$$hash"    networks:      - traefik-public networks:  traefik-public:    external: true

Важная деталь: providers.docker.exposedbydefault=false — Traefik не будет публиковать все контейнеры автоматически. Только те, у которых указан лейбл traefik.enable=true.

mkdir -p letsencrypt touch letsencrypt/acme.json chmod 600 letsencrypt/acme.json docker network create traefik-public docker-compose up -d

Подключение сервисов через лейблы

Любой контейнер в той же Docker-сети подключается через лейблы. Пример с WordPress:

services:  wordpress:    image: wordpress:latest    environment:      WORDPRESS_DB_HOST: db      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}    labels:      - "traefik.enable=true"      - "traefik.http.routers.wordpress.rule=Host(`blog.example.com`)"      - "traefik.http.routers.wordpress.entrypoints=websecure"      - "traefik.http.routers.wordpress.tls.certresolver=letsencrypt"      - "traefik.http.services.wordpress.loadbalancer.server.port=80"    networks:      - traefik-public      - internal  db:    image: mysql:8.0    networks:      - internal

Traefik видит новый контейнер, читает лейблы и автоматически создаёт роутер для домена с HTTPS. Если сервис слушает на нестандартном порту, укажите его явно через loadbalancer.server.port — иначе Traefik попытается угадать и иногда ошибается.

Middleware: auth, rate limiting, заголовки

Middleware — цепочка преобразований запроса до сервиса. Basic Auth для staging:

docker run --rm httpd:2.4-alpine htpasswd -nbB admin mypassword # В лейблах ($ экранируется как $$) - "traefik.http.middlewares.staging-auth.basicauth.users=admin:$$2y$$05$$xyz..." - "traefik.http.routers.myapp-staging.middlewares=staging-auth"

Rate limiting:

- "traefik.http.middlewares.ratelimit.ratelimit.average=100" - "traefik.http.middlewares.ratelimit.ratelimit.burst=50" - "traefik.http.middlewares.ratelimit.ratelimit.period=1m" - "traefik.http.routers.myapi.middlewares=ratelimit"

Security headers:

- "traefik.http.middlewares.security-headers.headers.stsSeconds=31536000" - "traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true" - "traefik.http.middlewares.security-headers.headers.browserXssFilter=true" - "traefik.http.middlewares.security-headers.headers.frameDeny=true"

Несколько middleware через запятую: traefik.http.routers.myapp.middlewares=ratelimit,security-headers

Статическая конфигурация через файлы

Лейблы хороши для простых случаев. Для сложных конфигураций удобнее файлы YAML:

# traefik.yml api:  dashboard: true entryPoints:  web:    address: ":80"    http:      redirections:        entrypoint:          to: websecure          scheme: https  websecure:    address: ":443" providers:  docker:    exposedByDefault: false  file:    directory: /etc/traefik/dynamic    watch: true certificatesResolvers:  letsencrypt:    acme:      email: admin@example.com      storage: /letsencrypt/acme.json      httpChallenge:        entryPoint: web# dynamic/middlewares.yml http:  middlewares:    secure-headers:      headers:        stsSeconds: 31536000        contentTypeNosniff: true        browserXssFilter: true    rate-limit-api:      rateLimit:        average: 200        burst: 100        period: 1m

Файлы в директории dynamic Traefik перечитывает на лету без перезапуска.

Балансировка нагрузки между репликами

Несколько реплик одного сервиса — Traefik автоматически балансирует round-robin. Sticky sessions для stateful приложений:

- "traefik.http.services.myapp.loadbalancer.sticky.cookie=true" - "traefik.http.services.myapp.loadbalancer.sticky.cookie.name=lb_session" - "traefik.http.services.myapp.loadbalancer.sticky.cookie.secure=true"

Health check — исключает unhealthy контейнеры:

- "traefik.http.services.myapp.loadbalancer.healthcheck.path=/health" - "traefik.http.services.myapp.loadbalancer.healthcheck.interval=10s" - "traefik.http.services.myapp.loadbalancer.healthcheck.timeout=3s"

Типичные проблемы

  • Сертификат не получается. Порт 80 заблокирован firewall. Проверьте: curl http://yourdomain.com/.well-known/acme-challenge/test
  • Контейнер не появляется в маршрутах. Контейнер и Traefik в разных Docker-сетях — самая частая причина
  • Лейбл применяется, но маршрут не работает. Проверяйте dashboard — там видны все роутеры и ошибки конфигурации
  • Испорченный acme.json. Несколько реплик Traefik без общего хранилища пишут в один файл. Для HA нужен Redis или Consul
  • Rate limit не по реальному IP. За CDN нужно доверять X-Forwarded-For: --entrypoints.websecure.forwardedHeaders.trustedIPs=1.2.3.4/32

Сразу настройте логирование в JSON-формате:

log:  level: INFO  format: json accessLog:  format: json  fields:    defaultMode: keep    headers:      defaultMode: drop      names:        User-Agent: keep        X-Forwarded-For: keep

Traefik — это не серебряная пуля. Для одного-двух сервисов Nginx проще и понятнее. Но если у вас Docker-окружение с несколькими сервисами, частыми деплоями и потребностью в HTTPS на каждый домен — Traefik убирает именно ту рутину, которая отнимает время без какой-либо ценности.