Traefik с нуля: от пустого VPS до контейнера с HTTPS

Пошагово настраиваем Traefik на VPS: ставим Docker, подключаем контейнер, настраиваем роутинг, HTTPS через Let’s Encrypt и обновление конфигурации без рестарта.
Время чтения: 4 мин
месяц назад
Обновлено: месяц назад

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

 

Traefik решает эту задачу иначе: он смотрит на Docker socket и сам видит, какие контейнеры запущены и что у них написано в labels или в отдельном конфиге. 

Добавил или изменил правило — Traefik сам обновляет маршруты без своего перезапуска. 

Что понадобится

  • VPS на Debian 12 или 13 (Ubuntu 24.04)
  • Домен, у которого A-запись указывает на IP сервера

Ставим Docker

На чистой VPS Docker не установлен вообще — ни из репозитория, ни тем более последней. 

Ставим из официального репозитория Docker:

sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Проверка:

docker --version
docker compose version

Добавьте пользователя в группу docker, чтобы не писать каждую команду docker через sudo:

sudo usermod -aG docker $USER

После этого нужно перелогиниться, чтобы группа применилась.

Структура проекта и сеть

Заводим рабочую директорию и сразу создаём отдельную Docker-сеть — через неё Traefik будет видеть остальные контейнеры:

mkdir -p ~/traefik-demo/dynamic
cd ~/traefik-demo

docker network create proxy

Если Traefik и целевой контейнер не в одной сети, роутинг просто не будет работать. Traefik физически не сможет достучаться до контейнера, даже если лейблы выставлены правильно.

 

Готовим директорию под сертификаты.

touch ~/traefik-demo/acme.json
chmod 600 ~/traefik-demo/acme.json

Traefik не будет работать с ACME, если у файла права шире чем 600.

Способ 1 — конфигурация целиком через файлы

Статика в traefik.yml, динамика — в отдельном yml-файле, который Traefik умеет перечитывать сам, без единого рестарта.

# ~/traefik-demo/traefik.yml

api:
  dashboard: true
entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"
log:
  level: INFO
providers:
  docker:
    exposedByDefault: false
    network: proxy
  file:
    directory: /etc/traefik/dynamic
    watch: true
certificatesResolvers:
  letsencrypt:
    acme:
      email: your-email@example.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

Ключевой момент — providers.file.watch: true. Он показывает Traefik директорию, за изменениями в которой нужно следить. Все изменения подхватываются сразу, без перезапуска

Динамическая конфигурация, роутинг на сервис whoami:

# ~/traefik-demo/dynamic/dynamic.yml

http:
  routers:
    whoami:
      rule: "Host(`whoami.example.com`)"
      entryPoints:
        - web
      service: whoami
  services:
    whoami:
      loadBalancer:
        servers:
          - url: "http://whoami:80"

Здесь нет никаких labels на контейнере whoami — Traefik находит его напрямую по имени сервиса в Docker-сети (http://whoami:80), потому что оба контейнера сидят в одной сети proxy, а Docker резолвит имена контейнеров через встроенный DNS.

 

Docker Compose:

# ~/traefik-demo/docker-compose.yml

services:
  traefik:
    image: traefik:v3.7.8
    container_name: traefik
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./dynamic/:/etc/traefik/dynamic/:ro
      - ./acme.json:/letsencrypt/acme.json
    networks:
      - proxy
  whoami:
    image: traefik/whoami
    container_name: whoami
    restart: unless-stopped
    networks:
      - proxy
networks:
  proxy:
    external: true

 

Версия образа зафиксирована — v3.7.8, актуальная стабильная на момент публикации. Фиксировать версию, а не брать latest, необходимо чтобы через полгода конфиг не сломался из-за минорного обновления с несовместимыми изменениями. Docker socket монтируется в режиме :ro — Traefik читает список контейнеров, но не должен иметь возможность их менять.

 

Поднимаем стек:

docker compose up -d
docker compose logs traefik

 

Проверяем роутинг:

curl http://whoami.example.com

Должен прийти ответ whoami с hostname контейнера и заголовками запроса.

Теперь — живой reload. Смотрим, что смонтировано внутри контейнера Traefik:

docker exec traefik cat /etc/traefik/dynamic/dynamic.yml

 

Меняем правило на хосте — например, меняем сам Host — не трогая ни Traefik, ни whoami:

nano ~/traefik-demo/dynamic/dynamic.yml

# меняем Host(`whoami.example.com`) на Host(`whoami2.example.com`)

Снова смотрим файл внутри контейнера — он уже другой, хотя примонтирован :ro и никто ничего не перезапускал:

docker exec traefik cat /etc/traefik/dynamic/dynamic.yml

Traefik не может писать в этот файл — но само изменение на хосте всё равно долетает до контейнера через bind mount, потому что это просто файл на диске, куда контейнер смотрит. Traefik замечает изменение и перечитывает файл, обновляя роутинг без единого рестарта.

Способ 2 — вообще без файлов конфигурации: command + Docker labels

Это полная противоположность первому способу. Статика задаётся аргументами прямо в command у сервиса Traefik, а роутинг — labels на контейнере whoami. Весь стек описывается одним docker-compose.yml, без сторонних конфигов.

# ~/traefik-demo/docker-compose.yml

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

  whoami:
    image: traefik/whoami
    container_name: whoami
    restart: unless-stopped
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`whoami.example.com`)"
      - "traefik.http.routers.whoami.entrypoints=web"
      - "traefik.http.services.whoami.loadbalancer.server.port=80"

networks:
  proxy:
    external: true

Роутинг читается только через docker-провайдер, по labels на контейнере. Traefik видит их в момент создания контейнера, через Docker API.

 

Какой способ выбрать — вопрос вкуса и удобства. Разница проявляется только в том, как каждый из них ведёт себя при изменениях.

 

Если поменять правило в label (например, домен) и выполнить docker compose restart whoami — ничего не изменится. Traefik узнаёт о labels в момент, когда видит Docker-событие создания контейнера, а обычный restart не создаёт контейнер заново, он просто перезапускает существующий процесс внутри него. Чтобы Traefik подхватил новые labels, контейнер нужно пересоздать:

docker compose up -d whoami

up -d увидит, что конфигурация сервиса изменилась, снесёт старый контейнер и создаст новый — на это событие Traefik и отреагирует. 

Это и есть ключевая разница между способами на практике: файл (способ 1) Traefik перечитывает сам по изменению на диске, без каких-либо рестартов; labels (способ 2) требуют пересоздания контейнера, обычного restart недостаточно.

HTTPS через Let's Encrypt (HTTP challenge)

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

Резолвер сертификатов уже описан осталось для каждого способа сделать две вещи: включить редирект с web на websecure и указать настройка роута, что он должен слушать websecure и использовать этот резолвер.

Способ 1 — правим оба файла.

Редирект — в traefik.yml:

# ~/traefik-demo/traefik.yml

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

А привязку к HTTPS — в dynamic.yml, у route whoami:

# ~/traefik-demo/dynamic/dynamic.yml
http:
  routers:
    whoami:
      rule: "Host(`whoami.example.com`)"
      entryPoints:
        - websecure
      service: whoami
      tls:
        certResolver: letsencrypt
  services:
    whoami:
      loadBalancer:
        servers:
          - url: "http://whoami:80"

Поменяли entryPoints с web на websecure и добавили блок tls.certResolve. Поскольку providers.file уже с watch: true, Traefik подхватит это без рестарта.

Способ 2 — правим command и labels.

Редирект — двумя аргументами в command у Traefik:

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

Привязку к HTTPS — в labels у whoami:

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

Traefik подхватывает labels только при пересоздании контейнера. Нужно выполнить docker compose up -d whoami, чтобы контейнер пересоздался и Traefik увидел новые labels.

Перезапускаем стек:

docker compose up -d

Первый запрос на HTTPS может занять несколько секунд — Traefik в этот момент обращается в Let's Encrypt, проходит challenge и сохраняет сертификат в acme.json. Вот как это выглядит в логах при удачном запросе:

2026-08-04T07:10:11Z INF Obtaining bundled SAN certificate. domains=whoami.example.com lib=lego
2026-08-04T07:10:12Z INF Use solver. domain=whoami.example.com lib=lego type=http-01
2026-08-04T07:10:12Z INF http01: Trying to solve HTTP-01. domain=whoami.example.com lib=lego
2026-08-04T07:10:25Z INF The server validated our request. domain=whoami.example.com lib=lego
2026-08-04T07:10:25Z INF Validations succeeded; requesting certificates. domains=whoami.example.com lib=lego
2026-08-04T07:10:27Z INF Server responded with a certificate. domains=whoami.example.com lib=lego

От первой попытки до полученного сертификата в этом прогоне ушло около 16 секунд — большую часть времени занимает не сам обмен с Let's Encrypt, а проверка HTTP-01: сервер Let's Encrypt должен успеть постучаться на ваш домен по 80-му порту и получить ответ.

Проверить, что сертификат выпустился, можно так:

curl -v https://whoami.example.com 2>&1 | grep -i "issuer\|subject"

Если в acme.json к этому моменту появилась запись с вашим доменом — сертификат получен и будет обновляться автоматически, без дополнительных действий.

Что может пойти не так:

  • домен не резолвится на IP сервера в момент запроса — Let's Encrypt не сможет пройти challenge
  • порт 80 закрыт файрволом облака или iptables на самом сервере — challenge не пройдёт, даже если Traefik настроен верно
  • неверные права на acme.json — Traefik откажется писать в файл и будет молча пропускать резолвер

Итог

На этом стенде — один Traefik и один тестовый контейнер, но принцип масштабируется без изменений в конфигурации самого Traefik: добавляете новый сервис в сеть proxy, вешаете на него нужные labels (или строку в dynamic.yml) — и он появляется в роутинге сам. 

Для такого стенда не нужен мощный сервер — VPS с 1-2 vCPU и парой гигабайт RAM хватает и на сам Traefik, и на десяток легких контейнеров за ним. 

Протестировать или изучить как работает Traefik удобно на Serverum: берёте минимальный тариф на Debian или Ubuntu, прогоняете весь стек из статьи.

Поделиться: