Traefik: Docker, HTTPS и маршруты — мастерская и 10 практических опытов
Десять настоящих опытов с Traefik: Node.js и PHP, Docker discovery, TLS, балансировка, health check, BasicAuth, 429 и диагностика. Мастерская, исходники и Linux/VPS.

Traefik на практике: Docker-сервисы, HTTPS, middleware, балансировка и диагностика. Десять опытов, мастерская с реальными HTTP-ответами и исходники для Windows/Linux.
Traefik: один вход для нескольких приложений
У проекта появился сайт, Node.js API, PHP-сервис и отдельная административная панель. Каждый контейнер слушает свой порт, а пользователю нужны понятные адреса и HTTPS. При этом контейнеры обновляются, экземпляры добавляются, один backend может временно перестать отвечать. Traefik помогает организовать общий вход: принять запрос, выбрать маршрут, применить нужные правила и передать работу подходящему приложению.
В статье соберём настоящий Docker-стенд и разберём десять повседневных ситуаций. Отправим запросы в Node.js и PHP, добавим новый сервис без перезапуска proxy, изменим путь, проверим TLS, распределим запросы между двумя экземплярами и временно исключим нездоровый backend. Затем посмотрим авторизацию, ограничение всплеска, разные HTTP-ошибки и динамическую конфигурацию.
На странице есть мастерская с реальными записанными ответами, схемой пути запроса, заголовками, телом, исходниками и access log. Архив позволяет повторить опыты на Windows и Linux. Отдельно разобран перенос на VPS: домен, ACME, приватное состояние сертификатов, публикация портов, подключение PHP/Node и диагностика. Это поможет пройти путь от знакомства с инструментом до осмысленного использования в собственном сервисе.
Где полезен reverse proxy и что он даёт пользователю
Представьте небольшой сервис для клиентов. Основной интерфейс открыт на одном домене, API обслуживает таблицы и формы, PHP-часть генерирует документы, а сотрудники работают в отдельной панели. Reverse proxy позволяет сохранить понятные внешние адреса, пока внутренние контейнеры и их порты меняются. Клиенту не нужно знать, какой процесс сейчас обрабатывает заявку и на каком серверном порту он находится.
Traefik особенно удобно рассматривать там, где приложения уже запускаются контейнерами и их конфигурация сопровождает развёртывание. Новый сервис может объявить свой маршрут labels, а proxy обнаружит его через provider. Для фиксированного набора upstream можно использовать файл, без автоматического обнаружения. В нашей лаборатории задействованы оба способа, поэтому можно увидеть их разные задачи.
Практическая польза не заканчивается красивым доменом. Общий вход даёт место для TLS, технических заголовков, ограничений, журналирования и выбора здорового экземпляра. При этом правила бизнеса остаются в приложении. Reverse proxy не решает, имеет ли конкретный сотрудник доступ к конкретному заказу, и не проверяет корректность JSON вместо вашего API. Эти уровни должны дополнять друг друга.
Traefik, Nginx и Caddy: выбирать по задаче
У всех трёх инструментов есть задачи reverse proxy, но рабочий процесс вокруг них может отличаться. Для нашей статьи важна связка Traefik с обнаружением контейнеров и динамическими routers/services/middlewares. Мы хотим добавлять учебный backend и видеть появление маршрута по его labels. Это предмет текущего материала, а не соревнование по числу строк конфигурации.
Если у вас уже настроен Nginx, понятен его deploy-процесс и нужно несколько стабильных upstream, миграция ради нового названия может не дать пользы. Если основной запрос — простой сайт с удобной конфигурацией HTTPS, стоит посмотреть Caddy. Сначала сформулируйте конкретную операционную проблему: кто обновляет маршруты, как появляются приложения, где находятся сертификаты и как команда диагностирует ошибки.
На сайте уже есть практические материалы: Nginx: reverse proxy, кеш и диагностика, Caddy: HTTPS, PHP и Node.js и Docker Compose с готовыми стендами. Их удобно использовать рядом с этой статьёй: один и тот же путь запроса станет понятнее с нескольких сторон.
EntryPoint, router, middleware, service и provider
В нашем стенде запрос проходит четыре понятных этапа. EntryPoint принимает соединение на определённом порту. Router сопоставляет запрос правилу, например домену и началу пути. Middleware выполняет промежуточное действие: убирает префикс, проверяет BasicAuth или добавляет заголовок. Service описывает, куда отправить запрос, включая список backend и их health check.
Provider отвечает за получение конфигурации. Docker provider читает сведения о контейнерах и labels, file provider — подготовленный каталог файлов. Поэтому provider не является ещё одним звеном обработки каждого пользовательского запроса. Он сообщает Traefik, какие маршруты и сервисы должны существовать, а уже они участвуют в передаче трафика.
Это разделение помогает при диагностике. Если нет подходящего router, проверять код Node.js пока рано. Если router найден, но service указывает на закрытый порт, искать ошибку в TLS-сертификате тоже бессмысленно. В мастерской схема показывает путь от клиента к backend, а HTTP-код и журнал помогают определить участок, на котором запрос остановился. Официальное описание router есть в документации Traefik.
Что находится в нашем Docker-стенде
Лаборатория содержит proxy Traefik, два одинаковых Node-сервиса node-a и node-b, простой PHP HTTP-upstream и дополнительный контейнер extra. У каждого Node-ответа есть имя экземпляра, поэтому балансировку видно прямо в JSON. PHP возвращает небольшой ответ со своей стороны. Никаких реальных клиентов, платежей и персональных данных стенд не использует.
На хосте опубликованы только три loopback-порта: 8540 для HTTP, 8543 для HTTPS и 8541 для административного входа. Приложения общаются с proxy по внутренней Docker-сети. Порт Node 3000 и порт PHP 8000 не нужно публиковать наружу, чтобы Traefik мог до них добраться. Это полезный принцип для рабочего развёртывания: внешний вход и внутренние порты — разные уровни.
Мы намеренно оставили лабораторные настройки отдельными от VPS-примера. Внутри опыта нужен доступ к Docker discovery, временный пароль и возможность менять health-сигнал. Для публичного сервера эти учебные удобства не следует переносить автоматически. В архиве есть самостоятельный каталог deploy с file provider, публичным HTTPS и без dashboard или Docker socket.
ports:
- 127.0.0.1:8540:8080 # HTTP лаборатории
- 127.0.0.1:8543:8443 # HTTPS лаборатории
- 127.0.0.1:8541:8081 # Административный вход
Версии и воспроизводимость примеров
Проект использует Traefik 3.7.18 и Docker Compose v2. Node-контейнер закреплён на 24.22.2-bookworm-slim, PHP-контейнер — на ветке 8.3 CLI. Для локального драйвера нужен Node.js 24 LTS не ниже 24.15.0. Зависимости подготовки и оформления зафиксированы в package-lock.json: selfsigned 3.0.1 и Bootstrap 5.3.9.
Теги образов помогают повторить среду, но у некоторых тегов содержимое со временем может обновляться. Поэтому запись дополнительно содержит фактические image digests использованного запуска. Исходные compose, динамическая конфигурация, обработчики и capture связаны с записью хешами SHA-256. Проверка verify обнаружит, если вы изменили эти файлы и забыли пересоздать результаты.
Для простого просмотра готовой мастерской Docker не требуется: достаточно Node-сервера, который отдаёт public. Для исполнения запросов заново нужен работающий Docker Engine. Такое разделение удобно для читателя: сначала можно понять поведение на странице, затем скачать архив и проверить собственные изменения в реальных контейнерах. Нажатия в опубликованной мастерской не запускают инфраструктуру на вашем компьютере.
Мини-мастерская: десять опытов, ответы и исходники
Выберите первый опыт и сравните ответы Node.js и PHP. Затем перейдите к появлению нового контейнера: сначала маршрут extra вернёт 404, после discovery — 200. В опыте балансировки последовательно меняется имя backend, а в health check один экземпляр временно исчезает из ответов и возвращается после восстановления. Можно переключать отдельные ответы или включить последовательное воспроизведение.
У каждого ответа показаны HTTP-код, заголовки, тело и объяснение. Генератор конфигурации предлагает только проверенные фрагменты нашего стенда: Node под /api, PHP под /php, пул backend и ограничение всплеска. Он не применяет настройки к Docker. Фрагмент можно скачать и использовать как отправную точку, сверив имена entryPoints, providers и services со своим проектом.
Механизм мастерской обозначен прямо: это запись настоящего выполнения Traefik в Linux-контейнерах. Браузер показывает полученные результаты, а не эмулирует работу proxy отдельным алгоритмом. Исходники и access log доступны в нижней части. Длительности относятся к одному учебному запуску и не являются сравнением производительности серверов.
Install-конфигурация и динамические правила
Настройки входных портов, providers и certificate resolvers определяют основу процесса Traefik. В документации это install-конфигурация; раньше её часто называли static configuration. В нашей лаборатории она передана аргументами command в Compose. Изменение такого параметра требует пересоздания процесса proxy с новой конфигурацией.
Routers, services, middleware и сертификаты, загружаемые из файла, относятся к routing-конфигурации. Она может обновляться через выбранный provider. Именно это позволяет добавить контейнер или новый file-router без ручного перезапуска proxy. Но слово «динамическая» не означает, что любое сохранение любого файла автоматически сработало: нужно проверить формат, путь, watcher и фактический HTTP-результат.
В архиве исходная структура хранится в config/dynamic.json, а prepare помещает её в watched directory как routes.yml. JSON-структура здесь совместима с YAML, однако расширение файла для обнаружения имеет значение. Не оставляйте только файл .json в каталоге provider с ожиданием такого же поведения. Способы подачи конфигурации описаны в официальной документации.
command:
- --entrypoints.web.address=:8080
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --providers.file.directory=/etc/traefik/dynamic
- --providers.file.watch=true
Опыт 1. Node.js и PHP за одним адресом
Первый опыт отправляет /api/hello в Node.js, а /php — в PHP. Оба запроса приходят на один HTTP-вход с Host demo.localhost, но выбирают разные routers. У каждого service явно указан внутренний порт приложения. Это важнее, чем внешний port mapping: Traefik соединяется с контейнером по Docker-сети, а не через опубликованный порт вашего Windows или Linux-хоста.
В labels Node-сервиса видны сразу предметные части: разрешение discovery, правило router, entryPoint, middleware и порт. Названия node, web и strip-api должны согласовываться с остальной конфигурацией. Суффикс @file означает, что middleware берётся из file provider, хотя сам router объявлен через Docker labels.
Так можно объединить административный интерфейс и API, несколько внутренних приложений или старую PHP-часть с новым Node-сервисом. При этом префикс URL не делает приложения одной программой: сессии, cookies, права, CORS и форматы данных по-прежнему нужно согласовать. Успешный reverse proxy подтверждает доставку HTTP-запроса, а не готовность всей бизнес-интеграции.
labels:
- traefik.enable=true
- aen.workshop=traefik-b17
- traefik.http.routers.node.rule=Host(`demo.localhost`) && PathPrefix(`/api`)
- traefik.http.routers.node.entrypoints=web
- traefik.http.routers.node.middlewares=strip-api@file,clean@file
- traefik.http.services.node.loadbalancer.server.port=3000
Host, Path и PathPrefix: границы совпадения
Host помогает разделить приложения по доменам, Path — выбрать точный путь, а PathPrefix — группу путей с общим началом. Наш учебный /api использует простой PathPrefix. При переносе в продукт обратите внимание: текстовый префикс /api также может совпадать с /apiary. Если нужна граница раздела, задайте точный /api или начало /api/ явно.
Проверяйте пересекающиеся маршруты. Например, общее правило домена и специальное правило /admin могут подходить одному запросу. Не полагайтесь на то, что порядок строк YAML обязательно задаёт порядок обработки. Изучите правила priority и назначьте явный приоритет там, где это делает поведение понятнее команде. В тестах должны быть запросы по обе стороны границы.
Для локальной диагностики удобно передавать Host через curl, не меняя hosts-файл системы. Но у HTTPS есть ещё имя для TLS-проверки и SNI: простой заголовок Host не решает все вопросы сертификата. Ниже мы используем –resolve и –cacert, чтобы имя URL, подключение к loopback и проверка доверия работали согласованно.
# Пример строгой границы API-раздела:
rule: "Host(`app.example.com`) && (Path(`/api`) || PathPrefix(`/api/`))"
Опыт 2. Новый контейнер и автоматическое discovery
Второй опыт сначала запрашивает /extra и получает 404. Затем capture запускает профиль extra, ждёт появления маршрута и повторяет запрос до HTTP 200. Proxy при этом не пересоздаётся. Так видно, что Docker provider действительно обнаружил новый сервис, а результат не нарисован заранее по факту успешного docker compose up.
У дополнительного контейнера есть разрешающие labels и общая сеть с Traefik. Мы также включили exposedByDefault=false и ограничение по специальной метке лаборатории. Это помогает не подхватывать случайные соседние контейнеры. Если контейнер появился, но маршрут не работает, проверьте эти условия, имя сети, порт и наличие router в dashboard/API.
Практический сценарий — развёртывание отдельного сервиса или временной preview-среды с собственным доменом. Правила могут храниться рядом с Compose приложения и меняться вместе с ним. Однако автоматическое обнаружение не означает автоматическую безопасность: право добавлять доверенные labels и доступ к Docker control plane должны быть ограничены. Возможности и параметры provider перечислены в Traefik Docker provider.
docker compose --profile extra up -d extra
docker compose ps
docker compose logs --tail=80 proxy
# После опыта остановить только дополнительный сервис:
docker compose --profile extra stop extra
Опыт 3. StripPrefix и адрес, который видит приложение
Пользователь обращается к /api/orders, а Node-приложение привыкло обслуживать /orders. В третьем опыте StripPrefix удаляет /api перед передачей запроса. Ответ backend показывает фактический path=/orders и forwardedPrefix=/api. Так легко проверить, на каком уровне изменился URL, и не гадать по одному статусу 200.
Это полезно при размещении готового приложения под общим префиксом. Но удаление части пути не переписывает автоматически все ссылки, cookies и URL ресурсов, которые генерирует приложение. Если интерфейс возвращает абсолютные ссылки от корня сайта, потребуется его настройка base URL или поддержка forwarded prefix. Иначе API может работать, а стили, редиректы и формы — уходить не туда.
В нашей цепочке после StripPrefix middleware clean добавляет X-Workshop и X-Content-Type-Options. В мастерской виден и результат обработки пути, и заголовки ответа. Такой короткий опыт удобно повторить для собственного upstream до сложного развёртывания. Подробности передачи префикса описаны в документации StripPrefix.
http:
middlewares:
strip-api:
stripPrefix:
prefixes: [/api]
clean:
headers:
customResponseHeaders:
X-Workshop: Traefik
X-Content-Type-Options: nosniff
Порядок middleware: читать цепочку как обработку запроса
У middleware есть порядок, и он влияет на результат. Для защищённого учебного маршрута мы собираем chain: сначала auth, затем clean, затем compress. Без учётных данных запрос останавливается на авторизации и не становится обычным успешным ответом приложения. С корректными данными он проходит дальше, получает технические заголовки и может использовать сжатие при подходящих условиях запроса и ответа.
Не копируйте длинный набор middleware без понимания каждого шага. Сначала определите цель: убрать внешний префикс, проверить вход, ограничить всплеск, добавить конкретный заголовок. Затем проверьте положительный и отрицательный путь. Например, что увидит клиент без авторизации? Какой URL получит backend? Не изменился ли redirect после добавления префикса?
Компонент chain удобен, когда один согласованный набор нужен нескольким routers. Но он не заменяет тесты конкретных маршрутов. Разные приложения могут требовать разной политики cookies, CSP, CORS и кеша. Глобально скопированный «набор безопасности» иногда ломает легальные запросы или создаёт ложное чувство защищённости. В статье мы используем ограниченные настройки и показываем их наблюдаемый эффект.
secure-chain:
chain:
middlewares: [auth, clean, compress]
Опыт 4. Редирект HTTP → HTTPS и проверка имени
В четвёртом опыте HTTP-запрос с Host secure.localhost получает 301 и Location на HTTPS-порт лаборатории. Следующий записанный запрос обращается к HTTPS-адресу назначения с проверкой сертификата и имени secure.localhost. Это две явно видимые операции: ответ перенаправления и успешное защищённое соединение. Они не подменяются одним условным зелёным индикатором.
При настройке редиректа важно согласовать все части: внешний порт, имя домена, TLS-router и имена в сертификате. Если сертификат выдан для demo.localhost, а redirect ведёт на secure.localhost, браузер справедливо откажется доверять такому переходу. Успешный HTTPS-запрос к другому имени не доказывает, что реальный путь пользователя работает.
В лаборатории оба имени включены в локальный сертификат и разрешены TLS-router. Порт 8543 нужен только потому, что мы не занимаем стандартный 443 на компьютере. На VPS используется обычный HTTPS-порт и настоящий домен. Выносить лабораторный порт в публичные ссылки без необходимости не стоит: это усложняет пользователю адрес и диагностику.
redirect:
redirectScheme:
scheme: https
port: "8543"
permanent: true
Самоподписанный TLS, –cacert и отличие от ACME
Prepare создаёт короткоживущий самоподписанный сертификат для локальных имён. HTTP-клиент capture доверяет ему только для своих запросов и проверяет hostname. Проверка не отключена: флаг rejectUnauthorized=false не используется. Вручную ту же идею можно повторить через –cacert и –resolve, не меняя системное хранилище доверия.
Браузер по умолчанию не доверяет этому учебному сертификату. Это ожидаемое свойство локального стенда, а не повод рекомендовать пользователю игнорировать предупреждения на любых сайтах. Для просмотра мастерской достаточно обычного локального HTTP-сервера, а TLS-часть воспроизводится проверяемым клиентом. Временный приватный ключ остаётся в .runtime и не попадает в архив публикации.
ACME — отдельный механизм получения публичного сертификата с подтверждением контроля над доменом. Наш локальный тест его не выполняет. Ниже приведены настройки для VPS и порядок staging → production, но внешний DNS и выдачу на вашем сервере нужно проверить отдельно. Честное различие между этими режимами помогает избежать обещания «HTTPS готов», когда протестировано только чтение локального PEM-файла.
curl --cacert .runtime/certs/local.pem --resolve secure.localhost:8543:127.0.0.1 https://secure.localhost:8543/
# PowerShell: используйте curl.exe и одну строку команды.
Опыт 5. Балансировка между node-a и node-b
Service pool содержит два HTTP-upstream. Capture делает несколько запросов к /pool и проверяет, что среди ответов действительно присутствуют node-a и node-b. В мастерской можно пролистать последовательность и увидеть имя экземпляра в теле. Это нагляднее, чем просто написать в конфигурации два адреса и предположить, что оба используются.
Такой пул помогает распределять запросы между копиями приложения. Но сами приложения должны быть готовы к работе в нескольких экземплярах. Если корзина или сессия хранится только в памяти одного процесса, следующий запрос к другому backend её не увидит. Общие данные, загрузки, кеши и фоновые операции требуют отдельного проектирования.
Записанная последовательность иллюстрирует конкретный запуск, а не обещает строгого чередования при любой нагрузке. Соединения, состояние backend и конкурентные запросы могут изменить наблюдаемый порядок. Для проверки производительности нужен отдельный нагрузочный сценарий, а для пользовательской корректности — тесты перехода между экземплярами. Устройство HTTP-service и балансировки описано в документации services.
pool:
loadBalancer:
servers:
- url: http://node-a:3000
- url: http://node-b:3000
Опыт 6. Health check: контейнер работает, сервис не готов
В шестом опыте node-b продолжает работать как процесс, но его /healthz временно возвращает 503. После ожидания проверки запросы пула идут только в node-a. Затем сигнал восстанавливается, capture ждёт реального ответа от node-b, и экземпляр возвращается в балансировку. Здесь видно различие между «контейнер Up» и «приложение готово обслуживать пользователей».
Для реального сервиса health endpoint должен быстро отражать готовность, а не выполнять тяжёлый отчёт или последовательный опрос десятка внешних систем. Слишком дорогая проверка сама создаёт нагрузку. Слишком поверхностная проверка всегда возвращает 200, даже когда приложение не способно ответить на рабочий запрос. Нужен осознанный компромисс для вашей архитектуры.
Учитывайте время обнаружения и восстановления. Проверка раз в секунду в лаборатории выбрана для короткого опыта; на production интервалы, timeout и критерии следует подобрать по поведению приложения. Health check не перезапускает процесс автоматически и не чинит базу. Он помогает proxy выбрать доступный backend, пока мониторинг и оператор выясняют причину отказа.
healthCheck:
path: /healthz
interval: 1s
timeout: 500ms
Опыт 7. BasicAuth и защищённый административный вход
Сначала /protected вызывается без данных и возвращает 401. Затем capture добавляет временную BasicAuth и получает 200. Ещё один запрос обращается к административному API и показывает реально обнаруженные routers. Пароль генерируется случайно при подготовке, хранится только в .runtime и не включается в запись. Заголовок Authorization также не сохраняется в опубликованных ответах и access log.
BasicAuth удобна для ограниченного служебного входа, но не заменяет пользовательскую модель вашего приложения. Она не определяет права на отдельные записи и не создаёт аудит действий конкретных сотрудников автоматически. В рабочем окружении передавайте такие данные только по HTTPS, используйте подходящий парольный hash и ограничьте административный доступ сетью или отдельной системой аутентификации.
Наш loopback-стенд использует временный файл users и removeHeader=true, чтобы не пересылать входной Authorization backend. Учебный формат пароля не следует превращать в шаблон для публичной панели. Документация поддерживаемых настроек и форматов находится в разделе BasicAuth.
auth:
basicAuth:
usersFile: /users
removeHeader: true
Dashboard: наблюдать конфигурацию, а не публиковать control plane
Dashboard полезен для проверки того, что Traefik действительно загрузил router, middleware и service. При проблеме с маршрутом можно сравнить ожидаемые имена с фактическими объектами. В нашей лаборатории административный вход вынесен на 127.0.0.1:8541 и защищён BasicAuth. Адрес интерфейса заканчивается на /dashboard/ с завершающим слешем.
Не включайте административный интерфейс наружу только ради удобства просмотра. Для сервера обычно разумнее отдельный доступ через VPN или SSH-туннель и дополнительная защита. В приложенном VPS-примере dashboard вообще не включён: публичному Node-сервису он не нужен для обработки запросов. Его можно добавить позже как отдельную операционную задачу.
Dashboard показывает конфигурацию и состояние маршрутов, но не заменяет проверку пользовательского URL. Наличие зелёного router ещё не гарантирует правильность ответа приложения, cookies или TLS-имени. Поэтому после просмотра панели выполните запрос с теми же host/path, что использует клиент. Настройка административного API описана в официальном руководстве.
Опыт 8. RateLimit, всплеск и HTTP 429
Восьмой опыт одновременно отправляет восемь запросов при average=1 в секунду и burst=2. В записи есть ответы 429: proxy остановил часть всплеска до обычной обработки backend. Это полезно для защиты ограниченного ресурса от случайного многократного нажатия, агрессивного клиента или слишком частого polling.
Параметры average и burst решают разные задачи. Средняя скорость задаёт восстановление доступного бюджета, а burst позволяет короткую серию запросов. В интерфейсе 429 нужно обрабатывать понятно: показать задержку или ограничение и не запускать мгновенный бесконечный повтор. Иначе клиент сам усилит перегрузку, особенно если множество вкладок делает retry одновременно.
В стенде лимит живёт в памяти одного экземпляра proxy. Несколько Traefik не получают общий бюджет автоматически. Также нужно решить, по какому признаку группировать клиентов и каким forwarded-заголовкам доверять. За proxy или CDN неверная настройка может объединить всех пользователей в один лимит либо позволить подмену адреса. Параметры рассмотрены в документации RateLimit.
limit:
rateLimit:
average: 1
period: 1s
burst: 2
Опыт 9. Почему получились 404, 502 и 504
В девятом опыте три ошибки созданы намеренно. Для /missing нет подходящего router, поэтому ответ 404 приходит до приложения. Для /broken router существует, но service указывает на недоступный порт backend: получаем 502. Для /slow backend принимает запрос, однако не присылает заголовки в заданные 300 миллисекунд: получаем 504.
Эта последовательность помогает выстроить диагностику от входа к приложению. При 404 проверяйте Host, путь, entryPoint и правило router. При 502 — адрес, внутренний порт, общую сеть и доступность процесса. При 504 — задержку upstream и конкретный timeout. В другом приложении сам backend тоже может вернуть эти статусы, поэтому смотрите OriginStatus, RouterName и тело, а не только число в браузере.
Не лечите любую ошибку увеличением таймаута. Если выбран неверный порт, ожидание не поможет. Если тяжёлая операция занимает минуты, лучше вернуть идентификатор задачи и показывать прогресс через отдельный API. Если маршрут отсутствует, нужно исправить конфигурацию. Мастерская даёт безопасно увидеть все три ситуации и сравнить их ответы до встречи с похожей проблемой на VPS.
serversTransports:
short:
forwardingTimeouts:
responseHeaderTimeout: 300ms
Опыт 10. File provider и обновление без перезапуска
Последний опыт добавляет /file-added в динамическую конфигурацию, ждёт HTTP 200 и затем возвращает исходный файл. Это проверяет не только сохранение текста, но и фактическое применение нового router. File provider полезен для стабильных адресов upstream, общих middleware и среды, где автоматическое обнаружение контейнеров не требуется.
В нашем Windows/Docker Desktop запуске уведомления о правке bind mount с хоста не доходили до watcher. Поэтому capture выполняет изменение routes.yml из Linux-контейнера через специальный учебный mount, после чего проверяет результат запросом. Эта деталь отражена в записи и README. На рабочем сервере backend не должен иметь право менять конфигурацию reverse proxy: mount нужен только для воспроизводимого опыта.
При самостоятельном размещении монтируйте каталог конфигурации, проверяйте поведение редактора и atomic replace, смотрите логи. Если файл изменился, а router нет, не делайте вывод, что динамическая конфигурация «не работает вообще». Сначала отделите формат, путь, уведомление файловой системы и ошибки содержимого. Особенности watcher описаны в документации file provider.
Access log: связать запрос, router, service и результат
Внизу мастерской показан фрагмент настоящего JSON access log. В нём оставлены RouterName, ServiceName, RequestPath, DownstreamStatus, OriginStatus и Duration. Эти поля помогают понять, какой маршрут обработал запрос, был ли ответ от upstream и где возникла ошибка. Журнал приложения и журнал proxy полезно связывать общим request ID, чтобы проследить одну операцию через несколько слоёв.
Системный log Traefik и access log решают разные задачи. Первый сообщает о конфигурации и работе процесса, второй описывает HTTP-запросы. При ошибке file provider может понадобиться именно системный журнал, а при редком 502 — запись конкретного запроса. Сохраняйте формат структурированным, чтобы искать по полям, а не разбирать текст вручную.
В реальном сервисе продумайте объём и приватность. Authorization и cookies обычно не нужны в обычном access log; query-параметры тоже могут содержать чувствительные сведения. Настройте ротацию и срок хранения. В VPS-примере Docker ограничивает размер файлов журнала, а для долгой истории потребуется внешняя система. Возможности полей и фильтров описаны в разделе logs и access logs.
accessLog:
format: json
fields:
headers:
defaultMode: drop
log:
level: INFO
Windows: подготовка, запуск и просмотр
Распакуйте проект в отдельный каталог и откройте PowerShell в его корне. Нужны Node.js 24 LTS и Docker Desktop в режиме Linux containers. Сначала проверьте docker version: установленный CLI без работающего Engine не сможет запустить стенд. Затем установите зависимости из lock и выполните prepare:lab, который создаст временные файлы сертификата и учётных данных.
Команда compose config проверяет структуру Compose, после up запускается capture. Он самостоятельно выполняет запросы, временно добавляет extra и меняет условия health check. Verify сверяет записанные результаты и хеши исходников. После этого serve открывает статическую мастерскую на 127.0.0.1:8503. Если нужен только просмотр уже приложенной записи, Docker-часть можно пропустить и выполнить install, verify, serve.
В PowerShell используйте npm.cmd и curl.exe, чтобы не зависеть от политики выполнения npm.ps1 или старого alias curl. Не запускайте prepare повторно поверх уже работающих контейнеров: сначала остановите собственный Compose-стенд. Так новые временные файлы и смонтированные данные гарантированно относятся к одному запуску. Подробные команды и затрагиваемые файлы перечислены в README.
node --version
docker version
docker compose version
npm.cmd ci --ignore-scripts
npm.cmd run prepare:lab
docker compose config --quiet
docker compose up -d
npm.cmd run capture
npm.cmd run verify
node tools/http-smoke.mjs
npm.cmd run serve
Linux: Docker Engine, Compose и тот же набор опытов
На Linux используются те же исходники и Compose-файл. Установите Docker Engine с Compose plugin по официальной инструкции вашего дистрибутива и Node.js 24 LTS. Для Ubuntu порядок приведён в Docker Docs. Не смешивайте случайные старые пакеты Compose v1 с ожидаемой командой docker compose v2.
Команды ниже выполняются из каталога проекта. Capture обращается к Docker CLI самостоятельно, поэтому доступ к daemon должен работать из той же пользовательской сессии. Если в вашей системе принято использовать sudo или отдельную deployment-учётную запись, настройте это согласованно. Не исправляйте права на socket командой chmod 666: доступ к daemon даёт гораздо больше возможностей, чем чтение списка контейнеров.
На удалённом сервере лаборатория по-прежнему слушает loopback. Это позволяет запускать опыт, не открывая учебную BasicAuth и dashboard в Интернет. Для просмотра можно использовать привычный SSH-туннель. Публичное развёртывание вынесено в отдельный каталог deploy: оно использует домен, стандартные порты и другую конфигурацию. Так локальное знакомство и эксплуатация не смешиваются в одном небезопасном файле.
node --version
docker version
docker compose version
npm ci --ignore-scripts
npm run prepare:lab
docker compose config --quiet
docker compose up -d
npm run capture
npm run verify
node tools/http-smoke.mjs
npm run serve
Linux/VPS: отдельная конфигурация для публичного приложения
В архиве есть docs/LINUX-VPS.md и deploy/compose.vps.yaml. Это самостоятельный пример с file provider, Node HTTP-upstream, портами 80/443 и ACME. В нём нет Docker socket, dashboard и временной BasicAuth лаборатории. Учебный Node-обработчик нужно заменить собственным приложением; инфраструктурная схема остаётся понятной и небольшой.
До запуска проверьте, кто уже слушает 80 и 443, настройте A-запись домена и рабочую AAAA только при доступном IPv6. Откройте нужные порты на стороне провайдера и проверьте firewall хоста с учётом Docker. Опубликованные контейнерные порты могут взаимодействовать с правилами UFW иначе, чем ожидается; соответствующие ограничения описаны в документации Docker networking.
Замените app.example.com и ops@example.com своими значениями. Создайте закрытый каталог состояния ACME и файлы с правами 600 до запуска. Compose config проверит структуру контейнеров, но не докажет корректность DNS и выдачу сертификата. После up нужны журналы и запросы к реальному домену. Внешний VPS и публичная ACME-выдача не являются частью локального результата мастерской.
cd /opt/traefik-example
install -d -m 700 deploy/acme
umask 077
touch deploy/acme/acme-staging.json deploy/acme/acme.json
chmod 600 deploy/acme/acme-staging.json deploy/acme/acme.json
docker compose -f deploy/compose.vps.yaml config --quiet
docker compose -f deploy/compose.vps.yaml up -d
docker compose -f deploy/compose.vps.yaml logs --tail=100 proxy
ACME: сначала staging, затем рабочий сертификат
VPS-конфигурация намеренно начинается с Let’s Encrypt staging. Это позволяет проверить домен и challenge, не расходуя производственные лимиты при каждой опечатке. Браузер не доверяет staging-сертификату; на этом этапе успех подтверждается корректной выдачей в журнале, а не попыткой навсегда отключить TLS-проверку у клиентов.
Для HTTP-01 сервер должен быть доступен по порту 80 снаружи. Resolver объявляется в install-конфигурации и подключается у router через tls.certResolver. После успешной проверки удалите caServer staging и переключите storage на отдельный acme.json, как описано в runbook. Изменение resolver требует пересоздания proxy. Финальный запрос к HTTPS должен пройти без -k.
Для wildcard используется другой подход — DNS challenge с правами на DNS-зону. Токен провайдера храните как секрет и ограничивайте его возможности. Сам файл acme.json тоже приватный: в нём состояние учётной записи и сертификатов, его нельзя класть в публичный каталог или исходный архив. Настройки и ограничения resolver перечислены в официальном разделе ACME.
certificatesResolvers:
public:
acme:
email: ops@example.com
storage: /acme/acme-staging.json
caServer: https://acme-staging-v02.api.letsencrypt.org/directory
httpChallenge:
entryPoint: web
Docker socket: почему :ro недостаточно
В лаборатории Docker provider получает доступ к /var/run/docker.sock. Суффикс :ro относится к файловому mount, но не превращает Docker API в интерфейс только для чтения. Возможность общаться с daemon остаётся чувствительной границей. Поэтому нельзя считать такую строку самостоятельной защитой и бездумно переносить её в многопользовательскую среду.
Выбор зависит от инфраструктуры: изолированный control plane, ограниченный API endpoint, тщательно настроенный socket proxy или отказ от Docker discovery в пользу file provider. Любой промежуточный компонент тоже требует проверки разрешённых методов и ресурсов. В нашем VPS-примере выбран простой путь: маршруты заданы файлами, доступ к Docker socket самому Traefik не нужен.
Метки exposedByDefault=false и constraints полезны для управления обнаружением, но решают другой вопрос. Они помогают не публиковать лишние контейнеры по ошибке; они не ограничивают все полномочия процесса, которому доступен daemon. Такое различие стоит понимать ещё до первого публичного сервера, особенно если рядом работают чужие приложения или административные сервисы.
PHP и Node.js: какой upstream должен увидеть Traefik
Для Node.js нужен HTTP-сервер, слушающий доступный внутри сети адрес, обычно 0.0.0.0 и порт приложения. Если процесс слушает только 127.0.0.1 внутри собственного контейнера, соседний proxy до него не достучится. Не обязательно публиковать этот порт на хост: общей Docker-сети достаточно. Приложение должно корректно завершаться и иметь понятный health endpoint.
С PHP есть важное различие протоколов. PHP-FPM обычно принимает FastCGI, а HTTP loadBalancer Traefik ожидает HTTP-upstream. Нельзя просто направить service на FPM:9000 и считать PHP подключённым. Нужен HTTP-сервер перед FPM, например Nginx, либо готовый PHP HTTP runtime вроде FrankenPHP/Caddy. В нашем локальном опыте php -S используется только как минимальный учебный ответчик.
На сайте есть отдельный материал FrankenPHP: сервер и worker mode. Выбирайте связку, которая соответствует приложению и эксплуатации, затем проверяйте полный путь через Traefik. Успешный ответ по внутреннему порту ещё не проверяет внешние редиректы, base URL, cookies и доверие к forwarded-заголовкам.
Таймауты, большие операции и потоковые интерфейсы
Наш короткий timeout в 300 миллисекунд нужен для наглядного 504, а не как универсальная настройка рабочего API. В продукте отделяйте время установления соединения, ожидания заголовков и передачи ответа. Если пользователь запускает тяжёлый экспорт, разумнее вернуть ID задачи и показывать прогресс, чем держать один HTTP-запрос до завершения всей работы.
Для WebSocket, SSE и загрузки файлов проверьте все промежуточные слои: приложение, Traefik, внешний CDN или балансировщик. Даже если один proxy поддерживает нужный сценарий, другой может закрыть соединение по своему timeout. Важно тестировать реальный пользовательский поток: начались ли события, продолжается ли передача, корректно ли обрабатывается разрыв и отмена.
Ограничения тела запроса, скорость клиента и бизнес-лимиты должны быть согласованы с приложением. Reverse proxy помогает поставить техническую границу, но API всё равно проверяет данные и права. Не добавляйте автоматический retry для любого POST только ради уменьшения числа ошибок: повтор операции без идемпотентности способен создать дубликат заказа или платежа.
Проверки стенда и границы доказательств
Verify проверяет десять сценариев: ожидаемые статусы, изменение пути, forwarded prefix, заголовок X-Workshop, два backend, исключение нездорового экземпляра, 429, ошибки и успешный file reload. Отдельный http-smoke выполняет новые запросы к работающему proxy, а не только читает JSON. TLS-клиент проверяет доверенный локальный сертификат и имя назначения.
Сценарии исполнялись в Linux-контейнерах; драйвер запуска и HTTP-проверки проверяются отдельно на Windows и Linux. Это полезная граница: контейнерный Linux не равен проверке вашего удалённого VPS, DNS и firewall. В записи сохранены версии, digests и исходники, чтобы результат можно было воспроизвести и сопоставить с кодом.
Если меняете compose, обработчик или динамические правила, выполните capture заново, затем verify. Иначе мастерская может показывать старые ответы от другой конфигурации. Приёмка собственного сервера должна дополнительно включать внешний домен, HTTPS без отключения проверки, доступность приложений, запрет административных портов и поведение после перезапуска. Эти действия нужны именно в вашей среде, а не заменяются красивой локальной записью.
npm run capture
npm run verify
node tools/http-smoke.mjs
docker compose logs --tail=100 proxy
Диагностика, обновление и аккуратная остановка
Если Compose сообщает о конфликте подсети, сначала посмотрите существующие сети и маршруты VPN. Выберите свободную LAB_SUBNET для этого стенда, а не удаляйте все сети Docker подряд. Если порт занят, найдите его владельца и измените только собственную конфигурацию. Такие действия сохраняют соседние приложения и делают повторный запуск предсказуемым.
Перед обновлением Traefik сохраните рабочую конфигурацию и pinned image, проверьте новую версию на отдельном стенде с теми же маршрутам. Меняйте один слой за раз. При неудаче верните предыдущую конфигурацию и образ, затем пересоздайте proxy. Не удаляйте ACME storage при обычном обновлении: это приватное состояние, которое нужно сохранять и резервировать.
После локальных опытов остановите Node-сервер мастерской через Ctrl+C и выполните compose down с профилем extra. Команда относится только к данному проекту. Она не требует глобального prune, остановки чужих контейнеров или удаления Docker volumes. Временные .runtime-файлы остаются локально до вашей уборки; в скачиваемый архив и публичную мастерскую они не входят.
docker compose --profile extra down
# Только для отдельного VPS-примера:
docker compose -f deploy/compose.vps.yaml down
Исходники и следующий практический шаг
В архиве есть локальный Compose-стенд, Node/PHP upstream, подготовка сертификата, capture, verify, новые HTTP smoke-проверки, готовая мастерская, README и отдельный Linux/VPS runbook. Каталог deploy содержит самостоятельный пример file provider с ACME. Приватные ключи, временный пароль и Docker-образы в ZIP не упакованы; они создаются или скачиваются при локальном запуске.
Начните с задачи, которая уже есть в вашем проекте: объединить сайт и API, вынести техническую панель, подключить второй экземпляр backend или понять причину 502. Повторите соответствующий опыт, измените одно правило и проверьте результат запросом. Затем добавьте отрицательный сценарий: неправильный Host, закрытый порт, нездоровый backend или отсутствие авторизации. Так конфигурация становится проверяемым поведением, а не набором магических строк.
Для дальнейшей работы используйте официальные разделы Docker provider, file provider, services и ACME. На сайте рядом пригодятся Docker Compose, Caddy и Nginx.


