Traefik v3: 5 aplikacji w jednym pliku Docker Compose
Konfiguracja Traefik v3 dla wielu domen na jednym adresie IP. Instrukcja routingu Host, automatyzacji TLS Let's Encrypt oraz poprawnego uprawnienia pliku acme.json.
Jeden adres IP, pięć aplikacji, jeden port 443
Twój serwer VPS posiada jeden publiczny adres IPv4 oraz jeden port TCP 443. Chcesz uruchomić na nim Gitea, kopię testową aplikacji, wewnętrzny panel sterowania, stronę statusu oraz odbiornik webhooków – pięć nazw hostów na jednej maszynie. Reverse proxy to proces, który przejmuje porty :80 oraz :443, odczytuje nagłówek Host w każdym żądaniu i przekazuje je do właściwego kontenera. Traefik realizuje to zadanie, automatycznie uzyskując i odnawiając certyfikaty dla każdej nazwy hosta bez konieczności ręcznego uruchamiania certbot. Nginx oraz Caddy również obsłużą te pięć nazw hostów, więc jeśli wybór nie został jeszcze dokonany, warto porównać te trzy proxy pod kątem obsługi certyfikatów i kosztu konfiguracji dla każdej aplikacji przed przypisaniem wszystkiego do jednego z nich.
Tym, co odróżnia Traefik od bloku server {} w nginx, jest źródło konfiguracji. W przypadku nginx edytujesz plik i przeładowujesz usługę, a cykl życia certyfikatu pozostaje oddzielnym zadaniem – jest to proces, który wykonujesz, gdy generujesz certyfikaty Let's Encrypt za pomocą certbot na nginx, gdzie timer odnowienia działa całkowicie niezależnie od serwera WWW. Dostawca Docker w Traefik monitoruje strumień zdarzeń Docker i odczytuje etykiety (labels) z Twoich kontenerów: uruchom kontener z etykietą reguły Host(), a stanie się on dostępny w ciągu sekundy; zatrzymaj go, a trasa zniknie. To również stanowi pułapkę. Konfiguracja zawarta w etykietach znajduje się w pięciu miejscach jednocześnie, a błędna etykieta nie generuje komunikatu o błędzie – kontener po prostu nie jest kierowany do ruchu, a Traefik nie zgłasza żadnej informacji.
Cztery pojęcia
- Entrypoints to gniazda nasłuchujące. Zdefiniujesz dwa:
webna:80orazwebsecurena:443. - Routers dopasowują żądanie (
Host(...)) i przypisują je do usługi. Certyfikaty są żądane dla każdego routera przeztls.certresolver. - Services to backend, czyli kontener oraz port, na którym nasłuchuje on wewnątrz sieci Docker.
- Middlewares znajdują się między routerem a usługą: uwierzytelnianie basic auth, listy dozwolonych adresów IP, nadpisywanie nagłówków, przekierowania.
Te cztery pojęcia to nazwy Traefik dla zadań, które w innym przypadku wykonywałbyś ręcznie: router to server_name, usługa to cel proxy_pass, a middleware to dyrektywy nagłówków i autoryzacji, które ustawiasz samodzielnie, gdy budujesz blok serwera reverse proxy nginx linia po linii.
Konfiguracja statyczna (entrypoints, providers, ACME) jest przekazywana w wierszu poleceń Traefik lub w traefik.yml, a jej zmiana wymaga restartu Traefik. Konfiguracja dynamiczna (routers, services, middlewares) pochodzi z etykiet kontenerów i jest przeładowywana w locie. Mylenie tych dwóch rodzajów konfiguracji to najczęstsza przyczyna sytuacji, w której "flaga nie działa".
Plik compose
Wspólna sieć Docker o nazwie proxy stanowi fundament. Traefik uzyskuje dostęp do kontenera tylko wtedy, gdy oba znajdują się w tej samej sieci.
name: edge
networks:
proxy:
name: proxy
services:
traefik:
image: traefik:v3.7
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, a następnie docker compose logs -f traefik. Każda kolejna aplikacja to kopia bloku gitea z własną nazwą routera, własnym Host() oraz własnym portem wewnętrznym. Instalacja Nextcloud w Docker z TLS i kopiami zapasowymi integruje się w ten sam sposób: należy usunąć publikowane porty, podłączyć kontener do proxy i pozwolić etykietom routera obsłużyć nazwę hosta oraz certyfikat.
Pięć poniższych szczegółów ma kluczowe znaczenie.
exposedByDefault=false sprawia, że kontener jest niewidoczny dla Traefik, dopóki nie otrzyma etykiety traefik.enable=true. W przypadku pominięcia tego ustawienia, dla każdego uruchomionego kontenera — w tym tymczasowych instancji postgres użytych do testów — zostanie wygenerowana trasa.
providers.docker.network=proxy wskazuje Traefik, której sieci użyć, gdy kontener jest podłączony do kilku z nich. Pominięcie tego parametru może spowodować, że Traefik wybierze błędny adres IP kontenera, co objawia się błędem 502, wyglądającym jak awaria aplikacji.
loadbalancer.server.port=3000 to port wewnątrz kontenera; Gitea nasłuchuje tam na porcie 3000. Należy zwrócić uwagę, że żaden kontener z aplikacją nie publikuje portów na zewnątrz; robi to wyłącznie Traefik.
Przekierowanie na punkcie wejścia web jest domyślnie trwałe, więc żądanie GET w czystym tekście (wysyłane przez przeglądarkę) otrzymuje kod 301 do HTTPS. Traefik odpowiada kodem 308 tylko dla innych metod, takich jak POST, dzięki czemu metoda jest zachowywana podczas przekierowania. Port 80 pozostaje otwarty: jest niezbędny dla wyzwania ACME HTTP oraz dla użytkowników wpisujących samą nazwę hosta.
Podwójny znak $$ w hashu basic-auth to wynik eskapowania w Compose, a nie błąd. Należy wygenerować go za pomocą htpasswd -nbB admin 'your-password' (pakiet apache2-utils), a następnie podwoić każdy znak $.
Certyfikat i pułapka pliku acme.json
tlschallenge=true wybiera wyzwanie TLS-ALPN-01: Let's Encrypt łączy się z serwerem na porcie 443, a Traefik odpowiada na wyzwanie wewnątrz handshake'u TLS. Alternatywą jest HTTP-01 na porcie 80; należy zamienić linię tlschallenge na liście command: w konfiguracji Traefik na te dwie:
- --certificatesresolvers.le.acme.httpchallenge=true
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=webObie metody działają. Obie wymagają, aby publiczny rekord DNS dla nazwy hosta wskazywał już na Twój VPS, ponieważ urząd certyfikacji musi rozwiązać nazwę i nawiązać połączenie z zewnątrz. Najpierw utwórz rekord A (oraz AAAA), potwierdź poprawność za pomocą dig +short git.example.com, a następnie uruchom Traefik.
Oto pułapka, która kosztuje wielu użytkowników cały wieczór. Traefik przechowuje klucz konta ACME oraz wszystkie wydane certyfikaty w jednym pliku acme.json. Jeśli plik ten jest dostępny do odczytu dla grupy lub wszystkich użytkowników, Traefik zapisuje w logach komunikat zbliżony do poniższego i wyłącza resolver. Usługa nadal działa i kieruje ruch, ale certyfikaty nie są pobierane, więc każda nazwa hosta jest obsługiwana przez TRAEFIK DEFAULT CERT:
ERR The ACME resolve is skipped from the resolvers list error="unable to get ACME account: permissions 644 for /letsencrypt/acme.json are too open, please use 600" resolver=lePoprawnym rozwiązaniem jest metoda opisana powyżej: zamontuj katalog (bind-mount) i pozwól Traefikowi samodzielnie utworzyć plik z odpowiednimi uprawnieniami. Jeśli plik acme.json został utworzony za pomocą touch, maska umask prawdopodobnie ustawiła uprawnienia na 644. Napraw je na hoście:
chmod 600 ./letsencrypt/acme.json
docker compose restart traefikWykonuj kopię zapasową tego katalogu wraz z wolumenami aplikacji. Jednorazowa utrata jest do naprawienia, ponieważ certyfikaty zostaną wydane ponownie, ale usuwanie pliku przy każdym wdrożeniu doprowadzi do przekroczenia limitu pięciu certyfikatów na ten sam zestaw nazw hostów w ciągu 7 dni.
Podczas testów używaj środowiska staging CA. Odkomentuj linię caserver, skonfiguruj wszystkie trasy, a następnie zakomentuj ją ponownie i usuń acme.json, aby pobrać certyfikaty produkcyjne. Produkcyjne środowisko Let's Encrypt pozwala na wydanie pięciu duplikatów certyfikatów tygodniowo dla identycznego zestawu nazw hostów i nakłada blokady w przypadku powtarzających się nieudanych walidacji dla tej samej nazwy. Środowisko staging wydaje certyfikaty niezaufane, przeglądarka wyświetli ostrzeżenie, a to ostrzeżenie jest sygnałem, że konfiguracja działa, przy znacznie łagodniejszych limitach.
Dashboard to panel sterowania, a nie demonstracja
Większość szybkich startów ustawia --api.insecure=true, co udostępnia dashboard na porcie 8080 bez uwierzytelniania. Na serwerze z publicznym adresem IP ujawnia to topologię routingu, nazwy hostów, nazwy middleware oraz porty backendów każdemu, kto przeskanuje maszynę.
Etykiety w usłudze traefik powyżej stanowią alternatywę: dashboard jest kierowany jak każda inna aplikacja, pod prawdziwą nazwą hosta, przez TLS, za basicauth. service=api@internal to element łączący router z wbudowanym API Traefik. Zabezpiecz go dodatkowo, stosując listę dozwolonych adresów IP, przetwarzaną od lewej do prawej. Jeśli adres biurowy jest dynamiczny, ustaw zakres na podsieć przydzielaną przez samodzielnie hostowany na tym samym VPS serwer WireGuard VPN i uzyskuj dostęp do dashboardu wyłącznie przez tunel:
- traefik.http.middlewares.office.ipallowlist.sourcerange=10.0.0.7/32
- traefik.http.routers.dashboard.middlewares=office,dashboard-authJedno współdzielone hasło basicauth przestaje być wystarczające, gdy pięć aplikacji wymaga własnych kont. W tym samym miejscu middleware można umieścić forwardauth, który przekazuje decyzję do Authentik, samodzielnie hostowanego serwera single sign-on. Dzięki temu dashboard oraz każda inna trasa znajdują się za jednym logowaniem, które można unieważnić w jednym miejscu.
Gniazdo Docker jako root
/var/run/docker.sock to API, które może utworzyć kontener montujący / z hosta. Dostęp do niego jest równoznaczny z uprawnieniami root na maszynie, a Traefik potrzebuje go do odczytu etykiet.
Zachowaj :ro przy montowaniu, ale miej świadomość, co to daje: czyni to plik gniazda tylko do odczytu. Nie blokuje to żądań POST do Docker API przesyłanych przez to gniazdo. Prawdziwym zabezpieczeniem jest nieudostępnianie gniazda bezpośrednio dla Traefik i umieszczenie pomiędzy nimi proxy filtrującego:
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:
- proxyUsuń wolumen gniazda z konfiguracji Traefik i skieruj dostawcę na proxy:
--providers.docker.endpoint=tcp://dockerproxy:2375Traefik zachowuje dostęp do odczytu kontenerów i sieci, tracąc jednocześnie możliwość tworzenia jakichkolwiek zasobów.
Firewall, porty i reguła, w której wszyscy popełniają błąd
Dwa otwarte porty oraz SSH:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enableOpublikowane porty Docker omijają ufw. Docker wstawia własne reguły iptables, które są przetwarzane przed łańcuchami ufw. W efekcie kontener uruchomiony z ports: ["3000:3000"] jest dostępny z Internetu, nawet jeśli w ufw ustawiono regułę deny. Obrona musi mieć charakter strukturalny, a nie polegać na konfiguracji firewalla: publikuj porty wyłącznie z poziomu Traefik, a wszystkim pozostałym kontenerom nadawaj networks: [proxy] i nic więcej. Jeśli usługa musi mieć dostęp do hosta, należy powiązać ją z interfejsem zwrotnym (loopback), "127.0.0.1:3000:3000".
Rozwiązywanie problemów: najczęstsze błędy
404 page not found, serwowany przez Traefik. Brak dopasowania routera. Przyczyny w kolejności prawdopodobieństwa: kontener nie posiada traefik.enable=true (przy ustawionym exposedByDefault=false); reguła Host() nie pasuje do wpisanej nazwy; nazwa routera w jednej etykiecie różni się od nazwy w drugiej (routers.gitea.rule oraz routers.gitea.entrypoints muszą być tym samym słowem); lub nazwa hosta została ujęta w pojedyncze cudzysłowy. Traefik v3 wymaga użycia backticków lub znaków ucieczki dla podwójnych cudzysłowów (\") wewnątrz dopasowań i odrzuca pojedyncze cudzysłowy.
502 Bad Gateway. Router został dopasowany, ale backend jest nieosiągalny. Prawie zawsze oznacza to, że kontener nie znajduje się w sieci proxy, sprawdź docker inspect -f '{{json .NetworkSettings.Networks}}' gitea. Innym powodem jest błędny loadbalancer.server.port: podano opublikowany port lub aplikacja nasłuchuje na innym porcie. Dziennik wskazuje próbę: dial tcp 172.18.0.5:8080: connect: connection refused.
Przeglądarka wyświetla ostrzeżenie, a certyfikat jest wystawiony dla TRAEFIK DEFAULT CERT. Certyfikat dla tej nazwy hosta nie istnieje, a Traefik serwuje swój domyślny certyfikat z podpisem własnym. Przeczytaj komunikaty ACME:
unable to obtain ACME certificate for domains "git.example.com" ...
acme: error: 400 ... DNS problem: NXDOMAIN looking up A for git.example.comDNS nie wskazuje jeszcze na serwer. Popraw rekord, odczekaj czas TTL i zrestartuj Traefik.
Invalid response from http://git.example.com/.well-known/acme-challenge/... podczas wyzwania HTTP: port 80 nie jest dostępny dla Traefik z zewnątrz. Zazwyczaj jest to firewall na poziomie dostawcy VPS, a nie ufw.
Certyfikaty nie są wystawiane, a DNS znajduje się w Cloudflare z włączoną "pomarańczową chmurką". Cloudflare kończy połączenie TLS na swojej krawędzi, co uniemożliwia przejście wyzwania TLS-ALPN-01. Ustaw rekord na DNS-only podczas wystawiania certyfikatu lub przełącz się na wyzwanie DNS-01 przy użyciu tokena API. DNS-01 to również jedyna metoda pozwalająca na wystawianie certyfikatów typu wildcard.
Pętla przekierowań. Inny komponent przed Traefik kończy już TLS i przesyła ruch czystym tekstem na port :80; przekierowanie punktu wejścia odsyła go z powrotem na HTTPS. Usuń jedno z dwóch przekierowań.
Utrzymanie ciągłości działania
Jednostka Docker musi być włączona przy starcie systemu (systemctl is-enabled docker), a restart: unless-stopped przywraca stos po restarcie. W celu uzyskania jawnej kontroli, mała jednostka systemd uruchamiająca docker compose -f /srv/edge/compose.yml up -d z flagą RemainAfterExit=yes zapewnia systemctl status edge oraz możliwość zarządzania kolejnością startu.
Należy przypiąć konkretną wersję obrazu Traefik (traefik:v3.7, nigdy latest) oraz wskazać gałąź, która nadal otrzymuje wydania. Według stanu na 2026-10-12, Traefik wymienia v3.7 jako jedyną wspieraną gałąź v3. Wersja v3.5 otrzymała ostatnie wydanie, v3.5.6, w dniu 7 listopada 2025 r., a jej dostawca Docker ma na sztywno zakodowaną wersję API 1.24. Docker Engine 29 i nowsze odrzucają to połączenie z błędem client version 1.24 is too old, co powoduje brak routingu dla kontenerów. Traefik 3.6.1 wprowadził negocjację wersji Docker API, dlatego powyższy plik compose używa v3.7. Aktualizacja z v2 do v3 zmieniła składnię reguł oraz nazwy dostawców, a automatyczna aktualizacja latest bez wahania przeładuje konfigurację, której nie będzie już w stanie poprawnie zinterpretować. Aktualizację należy przeprowadzać świadomie: zapoznać się z notami migracyjnymi, zmienić tag, wykonać docker compose up -d traefik i monitorować logi. Jeśli nadal używany jest tag v2, przewodnik migracji Traefik z v2 do v3 szczegółowo opisuje każdą zmianę nazwy, tryb kompatybilności oraz procedurę wycofania zmian, która pozwala zachować certyfikaty.
Należy tworzyć kopie zapasowe ./letsencrypt oraz wolumenów danych każdej aplikacji. Traefik nie przechowuje żadnego innego stanu, którego nie można odtworzyć z pliku compose.
Co ulega awarii przy skalowaniu
Pierwszą barierą nie jest przepustowość, lecz pojedyncza maszyna: jedna instancja Traefik na jednym VPS stanowi pojedynczy punkt awarii dla pięciu aplikacji, a acme.json to pamięć oparta na plikach płaskich; zapisywanie do niej przez dwie instancje Traefik spowoduje uszkodzenie danych. Skalowanie poziome wymaga przeniesienia przechowywania certyfikatów poza plik lub terminacji TLS w innym miejscu.
Drugą barierą są połączenia długotrwałe. Zdarzenia przesyłane przez serwer (Server-sent events), duże przesyłanie plików i wolni klienci napotykają limity czasu odpowiedzi punktu wejścia; --entryPoints.websecure.transport.respondingTimeouts.readTimeout oraz jego odpowiedniki writeTimeout i idleTimeout to parametry służące do ich regulacji. WebSockets przechodzą bez dodatkowej konfiguracji.
Trzecią barierą jest dysk. --accesslog=true zapisuje dane do stdout, a sterownik json-file w Dockerze przechowuje je bezterminowo, o ile nie zostanie nałożony limit. Należy ustawić logging.options.max-size w usłudze Traefik lub zapisywać dziennik dostępu do pliku i stosować jego rotację.
Nic z powyższych nie wymaga orkiestratora. Wymagany jest jedynie serwer pod pełną kontrolą, z publicznym adresem IP oraz otwartymi portami 80 i 443; pojedynczy mały VPS stanowi pełną listę zależności.
FAQ
Czy nadal potrzebuję certbot, jeśli używam Traefik?
Nie. Mechanizm ACME w Traefik automatycznie żąda i odnawia certyfikaty dla każdej nazwy hosta, którą obsługuje, przechowując je w acme.json. Certbot pozostaje właściwym narzędziem, gdy nginx lub inny serwer samodzielnie terminuje TLS; uruchamianie obu narzędzi dla tych samych nazw hostów powoduje jedynie niepotrzebne zużycie limitów Let’s Encrypt.
Dlaczego mój kontener zwraca 404 przez Traefik?
Błąd 404 zwracany przez Traefik oznacza, że żaden router nie dopasował żądania. Należy sprawdzić, czy kontener posiada etykietę traefik.enable=true (wymaganą, gdy ustawiono exposedByDefault=false), czy wartość Host() zgadza się z wpisaną nazwą oraz czy nazwa routera jest identyczna we wszystkich etykietach danej aplikacji. Traefik v3 wymaga również użycia grawisów lub znaków ucieczki dla cudzysłowów wewnątrz dopasowania; pojedyncze cudzysłowy są odrzucane.
Jaka jest różnica między 404 a 502 w tym przypadku?
Błąd 404 oznacza, że routing nie nastąpił; błąd 502 oznacza, że router dopasował żądanie, ale backend odrzucił połączenie. Typowymi przyczynami błędu 502 są: brak podłączenia kontenera do sieci proxy lub wskazanie w loadbalancer.server.port portu opublikowanego zamiast portu, na którym aplikacja nasłuchuje wewnątrz kontenera. Dziennik dostępu wskazuje dokładny adres, z którym próbował połączyć się Traefik.
Czy montowanie gniazda Docker w trybie tylko do odczytu jest wystarczające?
Flaga :ro czyni plik gniazda dostępnym tylko do odczytu, ale nie ogranicza samego API; żądania POST nadal mogą być przez nie przesyłane, a dostęp do Docker API jest równoważny z uprawnieniami root na hoście. Bezpieczniejszym rozwiązaniem jest użycie kontenera docker-socket-proxy, przedstawionego powyżej, który udostępnia Traefik jedynie odczyt informacji o kontenerach i sieciach, całkowicie blokując operacje zapisu.
Czy Traefik może wystawić certyfikat typu wildcard?
Jest to możliwe tylko przy użyciu wyzwania DNS-01 oraz tokena API dostawcy DNS. Metody TLS-ALPN-01 oraz HTTP-01 weryfikują pojedynczą nazwę hosta i nie mogą wygenerować certyfikatu wildcard. Metoda DNS-01 jest również rozwiązaniem, gdy CDN, taki jak Cloudflare, terminuje TLS przed serwerem VPS, co uniemożliwia poprawne zakończenie pozostałych dwóch typów wyzwań.