Traefik как reverse proxy для Docker
Каждый раз, когда в 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 Encrypt | certbot + крон на обновление | Встроено, продление автоматом |
| Порог входа | Ниже, конфиг привычен большинству | Выше, свой синтаксис правил и провайдеров |
| Поведение при изменениях | Требует 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-файле, если сервисы развёрнуты раздельно). Это и есть основное преимущество подхода — конфиг маршрутизации не собирается в одном общем файле, а живёт рядом с каждым сервисом.