MAATRIX / Блог / Traefik как reverse proxy для Docker

Traefik как reverse proxy для Docker

Traefik как reverse proxy для Docker

MAATRIX

Каждый раз, когда в docker-compose.yml добавляется новый сервис, начинается одно и то же: лезть в конфиг nginx, прописывать server_block с доменом и upstream на нужный порт, перезапускать nginx, потом ещё не забыть заказать SSL-сертификат для нового домена. Если сервисов пять и они не меняются годами — это терпимо. Если их пятнадцать и они появляются и исчезают каждую неделю — рутина съедает время и рано или поздно кто-то опечатается в конфиге в три часа ночи. Traefik берёт эту работу на себя: он следит за Docker напрямую и настраивает маршрутизацию по меткам (labels) прямо в docker-compose.yml, а сертификаты Let's Encrypt получает и продлевает сам, без крон-задач и ручных команд certbot.

Как Traefik узнаёт о новых контейнерах

Ключевое отличие от nginx — не в конкретных фичах, а в модели работы. Nginx хранит статический конфиг: вы правите файл, потом обязательно перечитываете его (nginx -s reload), иначе изменения не применятся. Traefik вместо этого подключается к Docker API (через сокет /var/run/docker.sock) и слушает события — запуск и остановку контейнеров. Как только контейнер с нужными метками появляется, Traefik на лету добавляет для него маршрут, без перезапуска и без даунтайма для остальных сервисов.

Метки читаются из самого контейнера — обычно прямо из его блока в docker-compose.yml:

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.myapp.rule=Host(`app.example.com`)"
  - "traefik.http.routers.myapp.entrypoints=websecure"
  - "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
  - "traefik.http.services.myapp.loadbalancer.server.port=3000"

Ничего не нужно прописывать отдельно в конфиге прокси — вся маршрутизация для сервиса живёт рядом с его собственным описанием. Это удобно ещё и потому, что при переносе сервиса на другой сервер конфиг маршрутизации переезжает вместе с docker-compose.yml, а не остаётся забытым куском в /etc/nginx/sites-enabled.

Отдельно стоит сказать про сам сокет /var/run/docker.sock: доступ к нему фактически равносилен root-правам на хосте, потому что через Docker API можно поднять привилегированный контейнер. Монтировать его в Traefik :ro (read-only) — минимальная защита, но не панацея: на проде для доступа к сокету лучше использовать socket-прокси вроде tecnativa/docker-socket-proxy, который отдаёт только те эндпоинты, которые реально нужны Traefik для чтения меток.

Установка Traefik в Docker Compose

Базовый вариант — один сервис traefik со статической конфигурацией через аргументы команды. Создайте директорию для проекта и файл docker-compose.yml:

version: "3.9"

services:
  traefik:
    image: traefik:v3.1
    container_name: traefik
    restart: unless-stopped
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencrypt.acme.email=you@example.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
      - "--api.dashboard=true"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./letsencrypt:/letsencrypt"
    networks:
      - web

networks:
  web:
    name: web

Флаг --providers.docker.exposedbydefault=false важен: без него Traefik попытается создать маршрут для вообще любого контейнера с открытым портом, включая базы данных и служебные сервисы, которым наружу лучше не торчать вовсе. С этим флагом маршрут получают только контейнеры с явной меткой traefik.enable=true.

Перед первым запуском создайте файл под сертификаты и выставьте на него права 600 — Traefik откажется работать с acme.json, если права на файл шире:

mkdir -p letsencrypt
touch letsencrypt/acme.json
chmod 600 letsencrypt/acme.json
docker compose up -d

Если Docker на сервере ещё не установлен, шаги по установке на Ubuntu 24.04 или Debian 12 описаны в отдельных статьях — сама Traefik-часть от дистрибутива не зависит.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Автоматическое получение SSL от Let's Encrypt

В примере выше используется HTTP-01 challenge — Let's Encrypt обращается на 80-й порт вашего домена, Traefik сам отвечает на проверочный запрос и получает сертификат. Это самый простой вариант: он не требует доступа к DNS-провайдеру, но домен обязан резолвиться на IP сервера, а 80-й порт — быть открыт наружу.

Если нужны wildcard-сертификаты (*.example.com) или домен временно не смотрит на сервер, используется DNS-01 challenge через API вашего DNS-провайдера:

- "--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare"

Для DNS-01 провайдеру нужны переменные окружения с API-токеном (для Cloudflare — CF_DNS_API_TOKEN), их передают через environment: в сервисе traefik или через .env-файл, который не должен попадать в git.

Полученные сертификаты Traefik хранит в acme.json и продлевает автоматически примерно за месяц до истечения — никакого отдельного крона для certbot не требуется. Это одно из главных практических отличий от связки nginx + certbot, где нужно либо настраивать certbot renew по расписанию, либо использовать сторонние обвязки вроде acme.sh.

Практический пример: два сайта за одним Traefik

Добавим в тот же docker-compose.yml два независимых сервиса с разными доменами — Traefik разведёт трафик между ними по заголовку Host без единой строчки в конфиге самого Traefik:

services:
  traefik:
    # ... как в примере выше

  blog:
    image: nginx:alpine
    container_name: blog
    restart: unless-stopped
    volumes:
      - "./blog-html:/usr/share/nginx/html:ro"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.blog.rule=Host(`blog.example.com`)"
      - "traefik.http.routers.blog.entrypoints=websecure"
      - "traefik.http.routers.blog.tls.certresolver=letsencrypt"
      - "traefik.http.services.blog.loadbalancer.server.port=80"
    networks:
      - web

  app:
    image: myregistry/myapp:latest
    container_name: app
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`app.example.com`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls.certresolver=letsencrypt"
      - "traefik.http.services.app.loadbalancer.server.port=3000"
    networks:
      - web

networks:
  web:
    name: web

Обратите внимание: у сервисов blog и app нет секции ports — их порты не пробрасываются на хост напрямую, весь трафик заходит только через Traefik по сети web. Это удобнее и безопаснее, чем держать десяток портов открытыми наружу.

Стоит сразу добавить редирект с HTTP на HTTPS, иначе 80-й порт останется рабочим входом без шифрования. Проще всего — глобальным middleware на уровне entrypoint:

- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"

После docker compose up -d оба домена, если их DNS-записи уже указывают на IP сервера, поднимутся с валидными сертификатами без дополнительных действий — Traefik запросит их при первом обращении к каждому роутеру.

Дашборд: где смотреть, какие маршруты живы

Дашборд Traefik — это встроенная веб-панель, где видно все активные роутеры, сервисы, middleware и entrypoints в реальном времени. Он полезен именно потому, что конфиг распределён по меткам разных контейнеров — без дашборда сложно быстро понять, какие маршруты сейчас реально работают.

По умолчанию с флагом --api.dashboard=true панель доступна на порту 8080 без авторизации, и оставлять её открытой наружу в интернет не стоит. Два рабочих варианта:

  • Пробросить порт 8080 только на localhost (127.0.0.1:8080:8080) и смотреть панель через SSH-туннель.
  • Завести для дашборда отдельный роутер с basic-auth middleware и своим поддоменом:
labels:
  - "traefik.enable=true"
  - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"
  - "traefik.http.routers.dashboard.service=api@internal"
  - "traefik.http.routers.dashboard.middlewares=dashboard-auth"
  - "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$hashedpassword"

Пароль для basicauth генерируется через htpasswd -nb admin ваш_пароль, а доллары в docker-compose.yml нужно экранировать удвоением ($$), иначе Compose попытается подставить переменную окружения.

Полный пошаговый разбор установки Traefik с нуля, включая систему на уровне ОС, — в отдельной статье про установку и настройку Traefik на VPS.

Traefik или nginx: когда что выбирать

Честный ответ: для одного-двух сайтов nginx всё ещё проще. Конфиг статичен, понятен, гуглится по любой ошибке, и не нужно разбираться с отдельным синтаксисом меток. Traefik начинает окупаться, когда сервисов становится много и они часто меняются — тогда экономия на ручной правке конфига перевешивает более высокий порог входа.

Критерийnginx (классический)Traefik
Конфиг при новом сервисеПравить файл + reloadМетки в самом контейнере, ничего вручную
SSL от Let's Encryptcertbot + крон на обновлениеВстроено, продление автоматом
Порог входаНиже, конфиг привычен большинствуВыше, свой синтаксис правил и провайдеров
Поведение при измененияхТребует reload (обычно без даунтайма)Динамическая перестройка без reload
Дашборд из коробкиНет (нужен сторонний модуль)Есть, показывает все маршруты живьём
Годится дляНебольшое число сайтов, статичная инфраструктураМного сервисов, частые деплои, Docker/Swarm/k8s

Если у вас уже есть рабочий nginx и всё стабильно — переезжать на Traefik ради самого переезда смысла нет. Сравнение с ещё одним популярным вариантом для тех же задач, Nginx Proxy Manager, разобрано в статье Traefik или Nginx Proxy Manager — что выбрать. А если вы только настраиваете окружение под продакшен-деплой контейнеров, стоит заодно посмотреть на структуру docker-compose.yml для продакшена — Traefik туда встраивается как ещё один сервис, без переделки остального.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Нужно ли открывать порт 8080 наружу для дашборда?

Нет, и не стоит. Либо смотрите его через SSH-туннель на localhost, либо закройте отдельным роутером с basic-auth, как показано выше.

Что будет с маршрутом, если контейнер упадёт?

Traefik уберёт роутер и сервис из динамического конфига в течение секунд после того, как Docker сообщит об остановке контейнера. Пользователи увидят ошибку 502, пока контейнер не поднимется снова, но сам Traefik не упадёт и продолжит обслуживать остальные сервисы.

Можно ли использовать Traefik без Docker, просто как reverse proxy?

Да, у него есть провайдеры для файлового конфига, Kubernetes, Consul и других источников — Docker-провайдер лишь один из них, просто самый частый сценарий для VPS с контейнерами.

Как обновить Traefik без даунтайма?

Опубликуйте новую версию образа в command/image, выполните docker compose pull traefik && docker compose up -d traefik. Контейнер пересоздастся, но HTTP-порты хоста освобождаются и занимаются заново быстро — на практике это доли секунды простоя, ощутимого разве что под очень высокой нагрузкой.

Обязательно монтировать docker.sock именно в Traefik?

Нет, это самый простой вариант, но не самый безопасный. Для продакшена лучше вынести доступ к сокету в отдельный сервис-прокси (docker-socket-proxy) с ограниченным набором прав и обращаться к нему из Traefik по сети, не давая контейнеру Traefik прямой доступ к хостовому сокету.

Нужны ли отдельные метки для каждого домена, если сервисов много?

Да, у каждого сервиса свои метки в его собственном блоке docker-compose.yml (или в отдельном compose-файле, если сервисы развёрнуты раздельно). Это и есть основное преимущество подхода — конфиг маршрутизации не собирается в одном общем файле, а живёт рядом с каждым сервисом.