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 убирает именно ту рутину, которая отнимает время без какой-либо ценности.