Traefik v3 у Docker Compose: 5 застосунків
Налаштуйте Traefik v3 для 5 застосунків через один Compose file. Вирішення проблем з acme.json та автоматичне отримання TLS сертифікатів Let's Encrypt.
Одна IP-адреса, п'ять застосунків, один порт 443
Ваш VPS має одну публічну IPv4-адресу та один TCP-порт 443. Вам потрібно розмістити на ньому Gitea, стейджинг-копію вашого застосунку, внутрішню панель керування, сторінку статусу та приймач webhook — п'ять хостнеймів на одному сервері. Reverse proxy — це процес, який займає порти :80 та :443, зчитує заголовок Host у кожному запиті та передає його потрібному контейнеру. Traefik виконує цю функцію, автоматично отримуючи та оновлюючи сертифікати для кожного хостнейма без ручного запуску certbot.
Головна відмінність Traefik від блоку nginx server {} полягає у способі отримання конфігурації. У nginx ви редагуєте файл і перезавантажуєте сервер, а керування життєвим циклом сертифікатів залишається окремим завданням. Це робочий процес, який використовується, коли ви випускаєте сертифікати Let's Encrypt за допомогою certbot на nginx, де таймер оновлення працює незалежно від вебсервера. Docker-провайдер Traefik відстежує потік подій Docker і зчитує labels (мітки) з ваших контейнерів: якщо запустити контейнер із міткою правила Host(), він стає доступним для маршрутизації протягом секунди; якщо зупинити контейнер, маршрут зникає. У цьому полягає і підвох. Конфігурація у вигляді міток розкидана по п'яти місцях одночасно, а помилка в мітці не викликає явних помилок — контейнер просто не буде маршрутизований, і Traefik не видасть жодного попередження.
Чотири іменники
- Entrypoints — це сокети прослуховування. Ви визначите два:
webна:80таwebsecureна:443. - Routers — співставляють запит (
Host(...)) із сервісом. Сертифікати запитуються для кожного роутера черезtls.certresolver. - Services — це бекенд: контейнер та порт, який він прослуховує всередині мережі Docker.
- Middlewares — знаходяться між роутером та сервісом: базова автентифікація, списки дозволених IP, переписування заголовків, перенаправлення.
Статична конфігурація (entrypoints, providers, ACME) передається через командний рядок Traefik або у traefik.yml; її зміна потребує перезапуску Traefik. Динамічна конфігурація (routers, services, middlewares) надходить через labels контейнера та перезавантажується "на льоту". Плутанина між цими двома типами — типова причина помилки "мій прапор не працює".
Файл compose
Основою є одна спільна мережа Docker під назвою proxy. Traefik отримує доступ до контейнера лише тоді, коли обидва об'єкти знаходяться в цій мережі.
name: edge
networks:
proxy:
name: proxy
services:
traefik:
image: traefik:v3.5
restart: unless-stopped
command:
- --providers.docker=true
- --providers.docker.exposedByDefault=false
- --providers.docker.network=proxy
- --entryPoints.web.address=:80
- --entryPoints.websecure.address=:443
- --entryPoints.web.http.redirections.entryPoint.to=websecure
- --entryPoints.web.http.redirections.entryPoint.scheme=https
- --certificatesresolvers.le.acme.email=you@example.com
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.le.acme.tlschallenge=true
# while you iterate, point at staging so a mistake costs nothing:
# - --certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
- --api.dashboard=true
- --log.level=INFO
- --accesslog=true
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
networks:
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)
- traefik.http.routers.dashboard.entrypoints=websecure
- traefik.http.routers.dashboard.tls.certresolver=le
- traefik.http.routers.dashboard.service=api@internal
- traefik.http.routers.dashboard.middlewares=dashboard-auth
- traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$REPLACE$$THIS
gitea:
image: gitea/gitea:1 # major-only pin keeps this demo copy-pasteable; pin an exact release in production
restart: unless-stopped
volumes:
- ./gitea:/data
networks:
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.gitea.rule=Host(`git.example.com`)
- traefik.http.routers.gitea.entrypoints=websecure
- traefik.http.routers.gitea.tls.certresolver=le
- traefik.http.services.gitea.loadbalancer.server.port=3000docker compose up -d, потім docker compose logs -f traefik. Кожен наступний додаток є копією блоку gitea з власним ім'ям роутера, власним Host() та власним внутрішнім портом. Встановлення Nextcloud у Docker з TLS та резервним копіюванням працює за таким самим принципом: приберіть його опубліковані порти, підключіть до proxy, а налаштування хостнейму та сертифіката довірте лейблам роутера.
Ось п'ять важливих деталей.
exposedByDefault=false робить контейнер невидимим для Traefik, доки він не отримає traefik.enable=true. Якщо не вказати цей параметр, для кожного запущеного контейнера — включно з тимчасовим postgres, який ви використовували для перевірки, — буде створено маршрут.
providers.docker.network=proxy вказує Traefik, яку мережу використовувати, якщо контейнер підключено до кількох мереж. Якщо пропустити цей параметр, Traefik може обрати неправильну IP-адресу контейнера, що призведе до помилки 502, яка виглядає як збій у роботі додатка.
loadbalancer.server.port=3000 — це порт всередині контейнера; у нашому випадку Gitea слухає порт 3000. Зверніть увагу, що жоден контейнер додатка не публікує порти — це робить лише Traefik.
Перенаправлення на entrypoint web перетворює HTTP-запити на 308 redirect на HTTPS. Порт 80 все одно залишається відкритим: він потрібен для ACME HTTP challenge, а також користувачам, які вводять лише хостнейм без протоколу.
Подвійний $$ у хеші basic-auth — це екранування Compose, а не помилка. Згенеруйте його за допомогою htpasswd -nbB admin 'your-password' (пакет apache2-utils), а потім подвійте кожен $.
Сертифікат та пастка acme.json
tlschallenge=true використовує TLS-ALPN-01: Let's Encrypt підключається до вашого сервера через порт 443, а Traefik відповідає на запит під час TLS-handshake. Альтернативою є HTTP-01 на порту 80 — замініть рядок tlschallenge у списку command: Traefik на ці два:
- --certificatesresolvers.le.acme.httpchallenge=true
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=webОбидва методи працюють. Для обох методів публічний DNS для хостнейму вже має вказувати на ваш VPS — центр сертифікації розпізнає ім'я та підключається ззовні. Спочатку створіть A (та AAAA) запис, перевірте його за допомогою dig +short git.example.com, а потім запустіть Traefik.
Тепер про пастку, через яку користувачі втрачають цілий вечір. Traefik зберігає ключ облікового запису ACME та всі видані сертифікати в одному файлі acme.json. Якщо цей файл доступний для читання групі або всім користувачам, Traefik виведе рядок, схожий на цей, і зупиниться:
error: unable to get ACME account: permissions 644 for /letsencrypt/acme.json are too open, please use 600Правильне рішення — описане вище: використовуйте bind-mount для директорії, щоб Traefik міг самостійно створити файл з правильним режимом доступу. Якщо ви створили acme.json за допомогою touch, ваш umask встановив права 644. Виправте це на хості:
chmod 600 ./letsencrypt/acme.json
docker compose restart traefikЗробіть резервну копію цієї директорії разом із томами (volumes) вашого додатка. Втрата файлу не критична — сертифікати можна перевипустити — але одночасний перевипуск для п'яти хостнеймів призведе до перевищення лімітів (rate limits).
Використовуйте staging CA під час тестування. Розкоментуйте рядок caserver, налаштуйте всі маршрути, а потім закоментуйте його назад і видаліть acme.json, щоб отримати нові робочі сертифікати. Production Let's Encrypt дозволяє видавати п'ять дублікатів сертифікатів на тиждень для одного набору хостнеймів і обмежує кількість повторних невдалих перевірок для одного імені. Staging видає неперевірені сертифікати — браузер видаватиме попередження, і це попередження є сигналом про успішну роботу — при набагато м'якших лімітах.
Панель керування — це інструмент адміністрування, а не демо-версія
Більшість швидких інструкцій налаштовують --api.insecure=true, що запускає панель керування на порту 8080 без автентифікації. На сервері з публічною IP-адресою це відкриває топологію маршрутизації, хостнейми, назви middleware та порти backend для будь-якого сканера.
Альтернативою є налаштування міток для сервісу traefik: панель керування маршрутизується як звичайний додаток на реальному хостнеймі через TLS за допомогою basicauth. service=api@internal забезпечує зв'язок маршрутизатора з вбудованим API Traefik. Для підвищення безпеки додайте список дозволених IP-адрес (IP allow-list), що застосовується зліва направо. Якщо ваша офісна IP-адреса є динамічною, вкажіть діапазон підмережі, яку видає WireGuard VPN, що самостійно хоститься на тому ж VPS, і доступ до панелі керування буде можливий лише через тунель:
- traefik.http.middlewares.office.ipallowlist.sourcerange=10.0.0.7/32
- traefik.http.routers.dashboard.middlewares=office,dashboard-authThe Docker socket is root
/var/run/docker.sock — це API, який може створити контейнер із монтуванням / з хост-системи. Доступ до цього API еквівалентний правам root на машині; Traefik потребує його для читання labels.
Залиште :ro у mount, але враховуйте наслідки: це робить файл сокета доступним лише для читання. Це не блокує POST запити до Docker API через цей сокет. Надійним методом захисту є відмова від передачі сокета Traefik та встановлення фільтруючого проксі-сервера між ними:
dockerproxy:
image: tecnativa/docker-socket-proxy # pin the current tag
restart: unless-stopped
environment:
CONTAINERS: 1
NETWORKS: 1
POST: 0
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- proxyВидаліть volume сокета з конфігурації Traefik і вкажіть provider на проксі:
--providers.docker.endpoint=tcp://dockerproxy:2375Traefik зберігає доступ на читання до контейнерів та мереж, але втрачає можливість створювати нові об'єкти.
Firewall, ports та помилка у правилах
Відкриті два порти та SSH:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enableОпубліковані порти Docker обходять ufw. Docker додає власні правила iptables, які перевіряються раніше за ланцюжки ufw. Контейнер, запущений з параметром ports: ["3000:3000"], буде доступний з інтернету, навіть якщо в ufw встановлено deny. Проблема полягає у структурі, а не у налаштуваннях firewall: публікуйте порти лише для Traefik, а для всіх інших контейнерів використовуйте networks: [proxy]. Якщо доступ до хоста дійсно необхідний, прив'яжіть його до loopback — "127.0.0.1:3000:3000".
Усунення несправностей: реальні помилки
404 page not found, повертається Traefik. Жоден router не підійшов. Найбільш імовірні причини: у контейнері відсутній traefik.enable=true (з установленим exposedByDefault=false); правило Host() не відповідає введеному імені; назва router в одному label відрізняється від назви в іншому (routers.gitea.rule та routers.gitea.entrypoints мають бути однаковими); або ви взяли hostname у лапки замість зворотних лапок. Traefik v3 вимагає використання зворотних лапок у matchers.
502 Bad Gateway. Router підійшов, але backend недоступний. Майже завжди контейнер не знаходиться в мережі proxy — перевірте docker inspect -f '{{json .NetworkSettings.Networks}}' gitea. Інша можлива причина — неправильний loadbalancer.server.port: ви вказали опублікований порт, або додаток слухає на іншому порту. У логах вказано спробу підключення: dial tcp 172.18.0.5:8080: connect: connection refused.
Браузер видає попередження, а сертифікат видано на TRAEFIK DEFAULT CERT. Для цього hostname не існує сертифіката, тому Traefik надав самопідписаний placeholder. Перегляньте рядки ACME:
unable to obtain ACME certificate for domains "git.example.com" ...
acme: error: 400 ... DNS problem: NXDOMAIN looking up A for git.example.comDNS ще не вказує на цей сервер. Виправте запис, зачекайте завершення TTL, перезапустіть Traefik.
Invalid response from http://git.example.com/.well-known/acme-challenge/... при HTTP challenge: порт 80 недоступний для Traefik ззовні — зазвичай це фаєрвол на рівні провайдера перед VPS, а не ufw.
Сертифікати не видаються, а у вашому DNS на Cloudflare увімкнено "orange cloud". Cloudflare завершує TLS на своєму edge, через що TLS-ALPN-01 не може бути виконано. Встановіть режим DNS-only на час видачі сертифіката або перейдіть на DNS-01 challenge за допомогою API token. DNS-01 також є єдиним методом challenge для видачі wildcard сертифікатів.
Redirect loop. Щось перед Traefik вже завершує TLS і пересилає plaintext на :80; redirect на entrypoint відправляє запит назад на HTTPS. Видаліть один із двох редиректів.
Keeping it running
Юніт Docker має бути увімкненим під час завантаження (systemctl is-enabled docker), а restart: unless-stopped відновлює стек після перезавантаження. Для явного керування можна використовувати невеликий юніт systemd, що запускає docker compose -f /srv/edge/compose.yml up -d з параметром RemainAfterExit=yes; це забезпечує systemctl status edge та контроль порядку запуску.
Використовуйте фіксований тег Traefik (traefik:v3.5, ніколи не latest). Оновлення з v2 до v3 змінило синтаксис правил та назви провайдерів. Автоматичне оновлення через latest призведе до перезавантаження конфігурації, яку система більше не розуміє. Оновлюйте контрольовано: прочитайте примітки щодо міграції, змініть тег, виконайте docker compose up -d traefik та стежте за логами. Якщо ви все ще використовуєте тег v2, the Traefik v2 to v3 migration guide описує кожну зміну назви, режим сумісності та процедуру відкату з збереженням сертифікатів.
Зробіть резервну копію ./letsencrypt та томів даних кожного додатка. Traefik не зберігає жодного іншого стану, який неможливо було б відновити з compose file.
Що стає перешкодою при масштабуванні
Перша перешкода — це не пропускна здатність, а обмеження одного вузла: один Traefik на одному VPS є єдиною точкою відмови для п'яти додатків. Оскільки acme.json використовує файлову систему, два екземпляри Traefik, що одночасно записують у неї, призведуть до пошкодження даних. Для масштабування необхідно винести зберігання сертифікатів з файлів або термінувати TLS в іншому місці.
Друга проблема — тривалі з'єднання. Server-sent events, великі завантаження та повільні клієнти можуть призвести до спрацювання таймаутів відповіді на entrypoint. Налаштування здійснюються через --entryPoints.websecure.transport.respondingTimeouts.readTimeout та його параметри writeTimeout і idleTimeout. WebSockets працюють без додаткового конфігурування.
Третя проблема — диск. --accesslog=true пише дані у stdout, а драйвер Docker json-file зберігає ці дані без обмежень. Встановіть logging.options.max-size для сервісу Traefik або записуйте access log у файл із подальшою ротацією.
Для цього не потрібен оркестратор. Необхідний лише сервер під вашим контролем з реальною IP-адресою та відкритими портами 80 і 443 для зовнішнього світу — це весь перелік необхідних умов.
FAQ
Чи потрібен мені certbot, якщо я використовую Traefik?
Ні. ACME-резольвер у Traefik запитує та оновлює сертифікат для кожного хостнейму, який він маршрутизує, і зберігає їх у acme.json. Certbot залишається відповідним інструментом, якщо TLS термінує nginx або інший сервер; одночасне використання обох інструментів для одних і тих самих хостнеймів призведе до перевищення лімітів Let's Encrypt.
Чому мій контейнер повертає 404 через Traefik?
Помилка 404 від Traefik означає, що запит не було підібрано жодним роутером. Перевірте, чи має контейнер traefik.enable=true (обов'язково, якщо встановлено exposedByDefault=false), чи збігається значення Host() з введеним вами ім'ям, і чи є назва роутера однаковою в усіх лейблах для цього додатка. У Traefik v3 усередині матчера потрібно використовувати зворотні лапки, а не звичайні лапки.
Яка різниця між 404 та 502 у цьому випадку?
404 означає, що маршрутизація не відбулася; 502 означає, що роутер підібрав запит, але бекенд відхилив з'єднання. Типовими причинами 502 є контейнер, який не підключений до мережі proxy, або loadbalancer.server.port, що вказує на опублікований порт замість порту, який додаток прослуховує всередині контейнера. Журнал доступу (access log) містить точну адресу, до якої звертався Traefik.
Чи достатньо монтувати Docker socket у режимі read-only?
Прапор :ro робить файл сокета доступним лише для читання, але не API, що стоїть за ним — POST запити все одно проходять через нього, а доступ до Docker API еквівалентний правам root на хості. Більш надійним варіантом є контейнер docker-socket-proxy, наведений вище, який надає Traefik лише права на читання контейнерів та мереж і повністю блокує запис.
Чи може Traefik видати wildcard-сертифікат?
Тільки через challenge DNS-01 за допомогою API-токена вашого DNS-провайдера. Методи TLS-ALPN-01 та HTTP-01 перевіряють лише один хостнейм і не можуть створити wildcard-сертифікат. DNS-01 також є рішенням, якщо CDN, як-от Cloudflare, термінує TLS перед вашим VPS і два інші методи перевірки не працюють.