SSD Nodes Learn
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-07-24

Traefik v3: 5 aplikacji w jednym Docker Compose

Konfiguracja Traefik v3 dla 5 aplikacji na jednym IP. Dowiedz się, jak uniknąć błędu acme.json i automatycznie obsługiwać certyfikaty Let's Encrypt.

Jeden adres IP, pięć aplikacji, jeden port 443

Serwer VPS posiada jeden publiczny adres IPv4 oraz jeden port TCP 443. Wymagane jest uruchomienie Gitea, kopii stagingowej aplikacji, wewnętrznego panelu dashboard, strony statusu oraz odbiorcy webhooków — pięć nazw hostów na jednej maszynie. Reverse proxy to proces obsługujący porty :80 i :443, który odczytuje nagłówek Host w każdym zapytaniu i przekazuje go do odpowiedniego kontenera. Traefik realizuje to zadanie, automatycznie pozyskując i odnawiając certyfikaty dla każdej nazwy hosta bez konieczności ręcznego uruchamiania certbot.

Główną różnicą między Traefik a blokiem nginx server {} jest sposób definiowania konfiguracji. W nginx wymagana jest edycja pliku i przeładowanie usługi, natomiast zarządzanie cyklem życia certyfikatów stanowi oddzielny proces — jest to workflow stosowany podczas wydawania certyfikatów Let's Encrypt za pomocą certbot na nginx, gdzie mechanizm odnawiania działa całkowicie poza serwerem WWW. Docker provider w Traefik monitoruje strumień zdarzeń Docker i odczytuje labels z kontenerów: uruchomienie kontenera z etykietą reguły Host() umożliwia rutowanie w ciągu sekundy; zatrzymanie kontenera powoduje usunięcie trasy. Stanowi to również ryzyko. Konfiguracja zawarta w etykietach jest rozproszona w wielu miejscach, a błędna etykieta nie generuje błędu — kontener po prostu nie jest rutowany, a Traefik nie wyświetla żadnego komunikatu.

Cztery rzeczowniki

  • Entrypoints to nasłuchujące gniazda (sockets). Należy zdefiniować dwa: web na :80 oraz websecure na :443.
  • Routers dopasowują żądanie (Host(...)) i przypisują je do usługi. Certyfikaty są pobierane dla każdego routera za pomocą tls.certresolver.
  • Services to backend — kontener oraz port, na którym nasłuchuje on wewnątrz sieci Docker.
  • Middlewares znajdują się pomiędzy routerem a usługą: uwierzytelnianie podstawowe (basic auth), listy zezwolonych adresów IP, modyfikacja nagłówków, przekierowania.

Konfiguracja statyczna (entrypoints, providers, ACME) jest przekazywana w linii komend Traefik lub w traefik.yml; jej zmiana wymaga restartu procesu Traefik. Konfiguracja dynamiczna (routers, services, middlewares) pochodzi z etykiet (labels) kontenerów i jest przeładowywana na bieżąco (hot-reload). Pomylenie tych dwóch typów konfiguracji jest najczęstszą przyczyną problemów typu "flaga nie działa".

Plik compose

Podstawą jest jedna współdzielona sieć Docker o nazwie proxy. Traefik nawiązuje połączenie z kontenerem tylko wtedy, gdy oba znajdują się w tej samej sieci.

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=3000

Najpierw docker compose up -d, następnie docker compose logs -f traefik. Każda kolejna aplikacja stanowi kopię bloku gitea z własną nazwą routera, własnym Host() oraz własnym portem wewnętrznym. Instalacja Nextcloud w Dockerze z TLS i kopiami zapasowymi działa w ten sam sposób — należy usunąć publikowane porty, podłączyć kontener do proxy, a etykiety routera obsłużą nazwę hosta oraz certyfikat.

Istotnych jest pięć szczegółów.

exposedByDefault=false sprawia, że kontener jest niewidoczny dla Traefik, dopóki nie posiada etykiety traefik.enable=true. Brak tej etykiety powoduje generowanie trasy dla każdego uruchomionego kontenera — w tym dla tymczasowego kontenera postgres używanego do testów.

providers.docker.network=proxy określa, której sieci ma użyć Traefik, gdy kontener jest podłączony do wielu sieci jednocześnie. Brak tej konfiguracji może spowodować wybór błędnego adresu IP kontenera, co skutkuje błędem 502, który wygląda jak błąd aplikacji.

loadbalancer.server.port=3000 to port wewnątrz kontenera; w tym przypadku Gitea nasłuchuje na porcie 3000. Należy zauważyć, że żaden kontener aplikacji nie publikuje portów — robi to wyłącznie Traefik.

Przekierowanie na entrypoint web zamienia żądania HTTP na przekierowanie 308 do HTTPS. Port 80 pozostaje otwarty: jest wymagany do wyzwania ACME HTTP oraz dla użytkowników wpisujących samą nazwę hosta.

Podwójne wystąpienie $$ w hashu basic-auth wynika z mechanizmu escaping w Compose, a nie jest błędem. Hasz należy wygenerować za pomocą htpasswd -nbB admin 'your-password' (pakiet apache2-utils), a następnie podwoić każdy znak $.

Certyfikat oraz pułapka acme.json

tlschallenge=true wybiera TLS-ALPN-01: Let's Encrypt łączy się z serwerem na porcie 443, a Traefik odpowiada na wyzwanie (challenge) wewnątrz handshake TLS. Alternatywą jest HTTP-01 na porcie 80 — należy zamienić linię tlschallenge w liście command: Traefik na te dwie:

      - --certificatesresolvers.le.acme.httpchallenge=true
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web

Obie metody są poprawne. Obie wymagają, aby publiczne DNS dla nazwy hosta wskazywało już na VPS — organ certyfikujący rozwiązuje nazwę i łączy się z zewnątrz. Najpierw należy utworzyć rekordy A (oraz AAAA), potwierdzić je za pomocą dig +short git.example.com, a następnie uruchomić Traefik.

Poniżej opisano pułapkę, która powoduje błędy podczas konfiguracji. Traefik przechowuje klucz konta ACME oraz każdy wystawiony certyfikat w jednym pliku acme.json. Jeśli plik ten jest czytelny dla grupy lub wszystkich użytkowników, Traefik wyświetla komunikat zbliżony do tego i przerywa działanie:

error: unable to get ACME account: permissions 644 for /letsencrypt/acme.json are too open, please use 600

Poprawnym rozwiązaniem jest metoda opisana powyżej: należy użyć bind-mount dla katalogu, aby Traefik mógł samodzielnie utworzyć plik z odpowiednimi uprawnieniami. Jeśli plik acme.json został utworzony za pomocą touch, umask ustawia uprawnienia na 644. Należy naprawić uprawnienia na hoście:

chmod 600 ./letsencrypt/acme.json
docker compose restart traefik

Należy wykonać kopię zapasową tego katalogu wraz z wolumenami aplikacji. Utrata pliku nie jest krytyczna, ponieważ certyfikaty można wystawić ponownie, jednak ponowne wystawianie pięciu nazw hostów jednocześnie może spowodować przekroczenie limitów (rate limits).

Podczas testów należy używać staging CA. Należy odkomentować linię caserver, skonfigurować wszystkie trasy, a następnie ponownie ją zakomentować i usunąć acme.json, aby poprosić o nowe certyfikaty produkcyjne. Produkcyjne Let's Encrypt pozwala na wystawienie pięciu duplikatów certyfikatów tygodniowo dla identycznego zestawu nazw hostów oraz nakłada limity na powtarzające się nieudane walidacje dla tej samej nazwy. Staging wystawia certyfikaty niewiarygodne — przeglądarka wyświetla ostrzeżenie (jest to sygnał, że proces działa poprawnie) — przy znacznie luźniejszych limitach.

Dashboard to interfejs sterujący, a nie wersja demonstracyjna

Większość instrukcji typu quickstart ustawia --api.insecure=true, co powoduje udostępnienie dashboardu na porcie 8080 bez uwierzytelniania. W przypadku serwera z publicznym adresem IP, każda osoba skanująca sieć uzyska dostęp do topologii routingu, nazw hostów, nazw middleware oraz portów backendowych.

Alternatywą są etykiety w usłudze traefik powyżej: dashboard jest rutowany jak każda inna aplikacja, przy użyciu rzeczywistej nazwy hosta, przez TLS, za pośrednictwem basicauth. service=api@internal odpowiada za połączenie routera z wbudowanym API Traefik. Można dodatkowo zwiększyć bezpieczeństwo, stosując listę zezwolonych adresów IP (IP allow-list) od lewej do prawej. Jeśli adres biurowy jest dynamiczny, należy ustawić zakres na podsieć przydzielaną przez WireGuard VPN hostowany na tym samym VPS i uzyskiwać 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-auth

Gniazdo Docker posiada uprawnienia root

/var/run/docker.sock to API, które umożliwia utworzenie kontenera montującego / z hosta. Dostęp do tego API jest równoważny z uprawnieniami root na maszynie, a Traefik wymaga go do odczytywania etykiet (labels).

Należy zachować :ro w montowaniu, ale należy mieć świadomość skutków: czyni to plik gniazda (socket file) tylko do odczytu. Nie powstrzymuje to POST żądań do Docker API przesyłanych przez to gniazdo. Skuteczną metodą ograniczania ryzyka jest nieudzielanie Traefik dostępu do gniazda i umieszczenie mię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:
      - proxy

Należy usunąć wolumen gniazda z konfiguracji Traefik i skierować dostawcę (provider) na proxy:

--providers.docker.endpoint=tcp://dockerproxy:2375

Traefik zachowuje uprawnienia do odczytu kontenerów oraz sieci, tracąc jednocześnie możliwość tworzenia jakichkolwiek zasobów.

Firewall, ports i błąd w regule popełniany przez wszystkich

Dwa otwarte porty oraz SSH:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Publikowane porty Docker omijają ufw. Docker wstawia własne reguły iptables, które są sprawdzane przed łańcuchami ufw. Kontener uruchomiony z flagą ports: ["3000:3000"] pozostaje dostępny z internetu, mimo ustawienia reguły deny w ufw. Problem wynika ze struktury działania, a nie z konfiguracji firewalla: należy publikować porty wyłącznie w Traefik, a pozostałym kontenerom nadawać networks: [proxy] i nic więcej. Jeśli dana usługa musi komunikować się z hostem, należy przypisać ją do interfejsu loopback — "127.0.0.1:3000:3000".

Troubleshooting: błędy, które wystąpią w rzeczywistości

404 page not found, serwowane przez Traefik. Brak dopasowania routera. Najczęstsze przyczyny: kontener nie posiada traefik.enable=true (przy ustawionym exposedByDefault=false); reguła Host() nie pasuje do wpisanej nazwy; nazwa routera w jednym labelu różni się od nazwy w innym (routers.gitea.rule i routers.gitea.entrypoints muszą być identyczne); lub hostname został umieszczony w cudzysłowie zamiast w backticksach. Traefik v3 wymaga użycia backticksów wewnątrz matcherów.

502 Bad Gateway. Router dopasował regułę, ale backend jest nieosiągalny. Zazwyczaj kontener nie znajduje się w sieci proxy — należy sprawdzić docker inspect -f '{{json .NetworkSettings.Networks}}' gitea. Inną przyczyną jest błędny loadbalancer.server.port: podano port opublikowany zamiast wewnętrznego lub aplikacja nasłuchuje na innym porcie. Logi wskazują na próbę połączenia: dial tcp 172.18.0.5:8080: connect: connection refused.

Przeglądarka wyświetla ostrzeżenie, a certyfikat zostaje wystawiony dla TRAEFIK DEFAULT CERT. Brak certyfikatu dla danej nazwy hosta spowodował, że Traefik serwuje własny certyfikat self-signed. Należy sprawdzić linie ACME:

unable to obtain ACME certificate for domains "git.example.com" ...
acme: error: 400 ... DNS problem: NXDOMAIN looking up A for git.example.com

DNS nie wskazuje jeszcze na serwer. Należy poprawić rekord, odczekać na wygaśnięcie TTL i zrestartować Traefik.

Invalid response from http://git.example.com/.well-known/acme-challenge/... podczas wyzwania HTTP: port 80 nie jest osiągalny przez Traefik z zewnątrz — zazwyczaj jest to firewall na poziomie dostawcy VPS, a nie ufw.

Brak wystawienia certyfikatów przy aktywnym "orange cloud" w Cloudflare. Cloudflare kończy sesję TLS na brzegu sieci, co uniemożliwia ukończenie wyzwania TLS-ALPN-01. Należy ustawić rekord na DNS-only podczas wystawiania certyfikatu lub przełączyć się na wyzwanie DNS-01 przy użyciu tokenu API. DNS-01 jest jedynym wyzwaniem umożliwiającym wystawienie certyfikatów wildcard.

Redirect loop. Element przed Traefik kończy sesję TLS i przekazuje tekst jawny (plaintext) na port :80; przekierowanie w entrypoint zwraca żądanie na HTTPS. Należy usunąć jedno z dwóch przekierowań.

Keeping it running

Jednostka Docker musi mieć włączone uruchamianie przy starcie systemu (systemctl is-enabled docker), a restart: unless-stopped przywraca stos po ponownym uruchomieniu. Aby uzyskać pełną kontrolę, mała jednostka systemd uruchamiająca docker compose -f /srv/edge/compose.yml up -d z flagą RemainAfterExit=yes zapewnia systemctl status edge oraz kontrolę kolejności uruchamiania.

Należy przypiąć konkretną wersję tagu Traefik (traefik:v3.5, nigdy latest). Aktualizacja z v2 do v3 zmieniła składnię reguł oraz nazwy dostawców. Nieplanowana latest spowoduje przeładowanie konfiguracji, której system nie będzie już rozumiał. Należy przeprowadzać aktualizację świadomie: należy przeczytać notatki migracyjne, zmienić tag na docker compose up -d traefik i monitorować logi. Jeśli nadal korzystasz z tagu v2, przewodnik migracji Traefik v2 do v3 opisuje każdą zmianę nazwy, tryb kompatybilności oraz procedurę wycofania zmian (rollback), która zachowuje certyfikaty.

Należy wykonać kopię zapasową ./letsencrypt oraz wolumenów danych każdej aplikacji. Traefik nie przechowuje żadnych innych danych stanu, których nie można odtworzyć z pliku compose.

Co powoduje awarie przy dużej skali

Pierwszym ograniczeniem nie jest przepustowość, lecz pojedyncza jednostka: jedna instancja Traefik na jednym VPS stanowi pojedynczy punkt awarii (single point of failure) dla pięciu aplikacji, a acme.json to pamięć typu flat-file — dwie instancje Traefik zapisujące do niej spowodują uszkodzenie danych. Skalowanie w poziomie wymaga przeniesienia przechowywania certyfikatów poza pliki lub terminacji TLS w innym miejscu.

Drugim ograniczeniem są długotrwałe połączenia. Server-sent events, duże przesyłanie plików oraz wolni klienci napotykają limity czasu odpowiedzi (timeouts) punktu wejścia; rozwiązaniem są --entryPoints.websecure.transport.respondingTimeouts.readTimeout oraz jej odpowiedniki writeTimeout i idleTimeout. Połączenia WebSockets są przekazywane bez dodatkowej konfiguracji.

Trzecim ograniczeniem jest dysk. --accesslog=true zapisuje dane do stdout, a sterownik json-file w Dockerze przechowuje te dane na stałe, jeśli nie zostanie ustawiony limit. Należy ustawić logging.options.max-size dla usługi Traefik lub zapisywać logi dostępu do pliku i stosować rotację.

Żaden z powyższych elementów nie wymaga orkiestratora. Wymagany jest jednak serwer pod pełną kontrolą, z publicznym adresem IP oraz otwartymi portami 80 i 443 — jedyną niezbędną zależnością jest jeden mały VPS.

FAQ

Czy nadal potrzebuję certbot, jeśli używam Traefik?

Nie. Resolver ACME w Traefik żąda i odnawia certyfikat dla każdej obsługiwanej nazwy hosta, a następnie przechowuje je w acme.json. Certbot jest właściwym narzędziem, gdy nginx lub inny serwer samodzielnie kończy sesję TLS; uruchomienie obu rozwiązań dla tych samych nazw hostów powoduje przekroczenie limitów (rate limits) Let's Encrypt.

Dlaczego mój kontener zwraca błąd 404 przez Traefik?

Błąd 404 serwowany przez Traefik oznacza, że żądanie nie zostało dopasowane do żadnego routera. Należy sprawdzić, czy kontener posiada traefik.enable=true (wymagane po ustawieniu exposedByDefault=false), czy wartość Host() jest zgodna z wpisaną nazwą oraz czy nazwa routera jest identyczna we wszystkich etykietach (labels) dla danej aplikacji. W Traefik v3 wewnątrz matcherów należy używać apostrofów (backticks), a nie cudzysłowów.

Jaka jest różnica między 404 a 502 w tym przypadku?

Błąd 404 oznacza brak dopasowania routingu; błąd 502 oznacza, że router dopasował żądanie, ale backend odrzucił połączenie. Typowymi przyczynami błędu 502 są: kontener niepodłączony do sieci proxy lub loadbalancer.server.port wskazujący na port opublikowany zamiast portu, na którym aplikacja nasłuchuje wewnątrz kontenera. Logi dostępu (access log) zawierają dokładny adres, pod który próbował połączyć się Traefik.

Czy zamontowanie Docker socket w trybie read-only jest wystarczające?

Flaga :ro czyni plik socket tylko do odczytu, ale nie dotyczy to API, z którego korzysta — żądania POST nadal są przesyłane przez niego, a dostęp do Docker API jest równoważny uprawnieniom root na hoście. Bezpieczniejszym rozwiązaniem jest kontener docker-socket-proxy przedstawiony powyżej, który udostępnia Traefik wyłącznie uprawnienia do odczytu kontenerów i sieci, całkowicie blokując operacje zapisu.

Czy Traefik może wystawić certyfikat wildcard?

Tak, wyłącznie przy użyciu wyzwania DNS-01 i tokenu API dostawcy usług DNS. Metody TLS-ALPN-01 oraz HTTP-01 służą do weryfikacji pojedynczej nazwy hosta i nie pozwalają na uzyskanie certyfikatu wildcard. DNS-01 jest również rozwiązaniem w sytuacjach, gdy CDN taki jak Cloudflare kończy sesję TLS przed VPS i pozostałe dwa typy wyzwań nie mogą zostać ukończone.