SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-24

Traefik no Docker Compose: 5 apps em um IP

Configure 5 apps com Traefik v3 e Docker Compose usando regras Host e Let's Encrypt. Evite o erro de inicialização causado por permissões incorretas no acme.json.

Um IP, cinco aplicações, uma porta 443

O seu VPS tem um único endereço IPv4 público e uma única porta TCP 443. Você quer alojar nele o Gitea, uma cópia de staging da sua aplicação, um dashboard interno, uma página de estado e um recetor de webhooks: cinco nomes de host, uma máquina. Um reverse proxy é o processo que controla as portas :80 e :443, lê o cabeçalho Host em cada pedido e encaminha-o para o contentor correto. O Traefik faz isso e obtém e renova um certificado para cada nome de host sem que você precise de executar o certbot manualmente. O Nginx e o Caddy também terminariam corretamente o TLS para os mesmos cinco nomes de host. Por isso, se a escolha ainda estiver em aberto, vale a pena comparar os três proxies quanto à gestão de certificados e ao custo de configuração por aplicação antes de ligar tudo a um deles.

O que distingue o Traefik de um bloco server {} do nginx é a origem da configuração. Com o nginx, você edita um ficheiro e recarrega o serviço. O ciclo de vida dos certificados continua a ser uma tarefa separada. Esse é o fluxo que você segue quando emite certificados Let's Encrypt com certbot no nginx, com um temporizador de renovação completamente fora do servidor Web. O provider Docker do Traefik monitoriza o fluxo de eventos do Docker e lê labels dos seus contentores: inicie um contentor com uma label de regra Host() e ele ficará acessível em menos de um segundo; pare-o e a rota desaparece. Esse também é o risco. A configuração armazenada em labels fica distribuída por cinco locais ao mesmo tempo. Uma label incorreta falha silenciosamente: o contentor simplesmente não é encaminhado e o Traefik não informa o problema.

Os quatro substantivos

  • Entrypoints são sockets de escuta. Irá definir dois: web em :80 e websecure em :443.
  • Routers associam um pedido (Host(...)) a um serviço. Os certificados são pedidos por router, através de tls.certresolver.
  • Services são o backend: um contentor e a porta onde este escuta dentro da rede Docker.
  • Middlewares ficam entre o router e o serviço: autenticação básica, listas de permissões de IP, reescrita de cabeçalhos e redirecionamentos.

Estes quatro substantivos são os nomes que o Traefik dá a tarefas que, de outro modo, teria de escrever manualmente: o router é um server_name, o serviço é um destino proxy_pass e os middlewares são as diretivas de cabeçalhos e autenticação que definiria ao criar linha a linha um bloco server de reverse proxy nginx.

A configuração estática (entrypoints, providers e ACME) é transmitida ao Traefik na linha de comandos ou em traefik.yml. Alterá-la implica reiniciar o Traefik. A configuração dinâmica (routers, services e middlewares) chega através das labels dos contentores e é recarregada sem interrupção. Confundir as duas é a causa habitual de a "minha flag não fazer nada".

O ficheiro Compose

Uma rede Docker partilhada, chamada proxy, é a base da configuração. O Traefik só consegue alcançar um contentor se ambos estiverem nessa rede.

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

docker compose up -d e, depois, docker compose logs -f traefik. Cada aplicação adicional é uma cópia do bloco gitea, com o seu próprio nome de router, o seu próprio Host() e a sua própria porta interna. Uma instalação do Nextcloud em Docker com TLS e cópias de segurança é integrada da mesma forma: remova as portas publicadas, ligue-a a proxy e deixe os labels do router tratarem do hostname e do certificado.

Cinco detalhes desta configuração são importantes.

exposedByDefault=false torna um contentor invisível para o Traefik até este ter traefik.enable=true. Se o omitir, todos os contentores iniciados posteriormente, incluindo o contentor temporário postgres que executou para verificar algo, passam a ter uma rota gerada.

providers.docker.network=proxy indica ao Traefik qual rede deve usar quando um contentor está ligado a várias redes. Se o omitir, o Traefik pode escolher o IP errado do contentor. O resultado é um erro 502 que parece ser causado pela aplicação.

loadbalancer.server.port=3000 é a porta dentro do contentor. O Gitea escuta nessa porta 3000. Repare que nenhum contentor de aplicação publica uma porta. Apenas o Traefik o faz.

O redirecionamento no entrypoint web converte os pedidos em texto simples num redirecionamento 308 para HTTPS. A porta 80 continua aberta: o desafio HTTP do ACME precisa dela, assim como as pessoas que introduzem um hostname sem protocolo.

O $$ duplicado no hash de basic auth é um mecanismo de escape do Compose, não um erro de escrita. Gere-o com htpasswd -nbB admin 'your-password' (pacote apache2-utils) e duplique cada $.

O certificado e a armadilha do acme.json

tlschallenge=true seleciona TLS-ALPN-01: o Let's Encrypt liga-se ao seu servidor na porta 443 e o Traefik responde ao desafio durante o handshake TLS. A alternativa é HTTP-01, na porta 80. Substitua a linha tlschallenge na lista command: do Traefik por estas duas:

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

Ambas funcionam. As duas exigem que o DNS público do hostname já aponte para o seu VPS e que a autoridade certificadora consiga resolver o nome e ligar-se a partir do exterior. Crie primeiro o registo A (e AAAA), confirme com dig +short git.example.com e, em seguida, inicie o Traefik.

Agora, a armadilha que pode consumir uma noite de trabalho. O Traefik mantém a chave da conta ACME e todos os certificados emitidos num único acme.json. Se esse ficheiro puder ser lido pelo grupo ou por qualquer utilizador, o Traefik imprime uma linha muito semelhante a esta e para:

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

A correção adequada é a indicada acima: faça bind mount do diretório e deixe o Traefik criar o ficheiro com as permissões corretas. Se criou acme.json com touch, a sua umask definiu as permissões como 644. Corrija no host:

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

Inclua esse diretório nas cópias de segurança juntamente com os volumes da aplicação. Se o perder, é possível recuperar: os certificados serão emitidos novamente. No entanto, emitir novamente cinco hostnames ao mesmo tempo pode atingir os limites de taxa.

Use a CA de staging enquanto faz iterações. Remova o comentário da linha caserver, faça todas as rotas funcionarem, depois volte a comentá-la e elimine acme.json para que sejam solicitados novos certificados de produção. O Let's Encrypt de produção permite cinco certificados duplicados por semana para um conjunto idêntico de hostnames e limita as validações falhadas repetidas para o mesmo nome. O staging emite certificados não confiáveis. O seu browser apresenta um aviso, e esse aviso confirma que o processo funcionou, com limites muito mais permissivos.

O dashboard é uma superfície de controlo, não uma demonstração

A maioria dos quickstarts define --api.insecure=true, que disponibiliza o dashboard na porta 8080 sem autenticação. Num servidor com um IP público, isso expõe a topologia de encaminhamento, os nomes dos hosts, os nomes dos middlewares e as portas dos backends a qualquer pessoa que faça uma varredura.

Os labels do serviço traefik acima são a alternativa: o dashboard é encaminhado como qualquer outra aplicação, num hostname real, através de TLS e protegido por basicauth. service=api@internal liga o router à API integrada do Traefik. Pode reforçar a proteção encadeando uma lista de permissões de IP, aplicada da esquerda para a direita. Se o endereço do escritório for dinâmico, defina o intervalo para a sub-rede atribuída por uma VPN WireGuard alojada por si no mesmo VPS e aceda ao dashboard apenas através do túnel:

- traefik.http.middlewares.office.ipallowlist.sourcerange=10.0.0.7/32
- traefik.http.routers.dashboard.middlewares=office,dashboard-auth

Uma única password basicauth partilhada deixa de ser defensável quando cinco aplicações precisam das suas próprias contas. O mesmo slot de middleware aceita um forwardauth que transfere a decisão para o Authentik, um servidor self-hosted de single sign-on, para que o dashboard e todas as rotas adjacentes fiquem protegidos por um único login que pode revogar num só local.

O socket do Docker dá acesso a root

/var/run/docker.sock é uma API que pode criar um contentor que monta / a partir do host. O acesso a essa API equivale a root na máquina, e o Traefik precisa dele para ler labels.

Mantenha :ro no mount, mas deixe claro o que essa opção garante: torna o ficheiro do socket apenas de leitura. Não impede que pedidos POST à API do Docker passem por ele. A mitigação real consiste em nunca fornecer o socket ao Traefik e colocar um proxy de filtragem entre ambos:

  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

Remova o volume do socket do Traefik e aponte o provider para o proxy:

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

O Traefik mantém acesso de leitura aos contentores e às redes, mas deixa de poder criar qualquer recurso.

Firewall, portas e a regra que toda a gente interpreta mal

Duas portas abertas, além do SSH:

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

As portas publicadas pelo Docker ignoram o ufw. O Docker insere as suas próprias regras do iptables, que são avaliadas antes das cadeias do ufw. Por isso, um contentor iniciado com ports: ["3000:3000"] fica acessível a partir da Internet, mesmo que exista uma regra ufw deny para essa porta. A defesa é estrutural, não depende da configuração da firewall: publique portas apenas a partir do Traefik e dê a todos os outros contentores networks: [proxy], sem mais nada. Se algo tiver realmente de aceder ao host, associe-o ao loopback, "127.0.0.1:3000:3000".

Solução de problemas: erros que você realmente verá

404 page not found, servido pelo Traefik. Nenhum router correspondeu. Por ordem de probabilidade: o contentor não tem traefik.enable=true (com exposedByDefault=false definido); a regra Host() não corresponde ao nome introduzido; o nome do router num label é diferente do nome noutro (routers.gitea.rule e routers.gitea.entrypoints têm de ser a mesma palavra); ou o hostname foi colocado entre aspas em vez de crases. O Traefik v3 exige crases dentro dos matchers.

502 Bad Gateway. Um router correspondeu, mas o backend estava inacessível. Quase sempre, o contentor não está na rede proxy. Verifique docker inspect -f '{{json .NetworkSettings.Networks}}' gitea. A outra possibilidade é um loadbalancer.server.port incorreto: foi indicada uma porta publicada ou a aplicação escuta noutra porta. O log identifica a tentativa: dial tcp 172.18.0.5:8080: connect: connection refused.

O navegador apresenta um aviso e o certificado foi emitido para TRAEFIK DEFAULT CERT. Não existe um certificado para esse hostname e o Traefik serviu o seu certificado autoassinado de substituição. Leia as linhas do ACME:

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

O DNS ainda não aponta para o servidor. Corrija o registo, aguarde o TTL e reinicie o Traefik.

Invalid response from http://git.example.com/.well-known/acme-challenge/... no desafio HTTP: a porta 80 não chega ao Traefik a partir do exterior. Normalmente, existe uma firewall ao nível do fornecedor à frente do VPS, e não o ufw.

Os certificados nunca são emitidos e o DNS está no Cloudflare com a nuvem laranja ativa. O Cloudflare termina o TLS na sua edge, e o TLS-ALPN-01 não consegue ser concluído através dela. Defina o registo como DNS-only durante a emissão ou mude para o desafio DNS-01 com um token de API. O DNS-01 também é o único desafio que emite wildcards.

Loop de redirecionamento. Algo à frente do Traefik já termina o TLS e encaminha texto simples para :80; o redirecionamento do entrypoint envia-o novamente para HTTPS. Remova um dos dois redirecionamentos.

Manter o serviço em execução

A unidade do Docker deve estar ativada para arrancar no boot (systemctl is-enabled docker), e restart: unless-stopped repõe a stack depois de um reboot. Para um controlo explícito, uma unidade systemd pequena que execute docker compose -f /srv/edge/compose.yml up -d com RemainAfterExit=yes fornece systemctl status edge e controlo da ordem de arranque.

Fixe a tag do Traefik (traefik:v3.5, nunca latest). A atualização da v2 para a v3 alterou a sintaxe das regras e os nomes dos providers, e um latest não supervisionado irá recarregar sem hesitar uma configuração que já não compreende. Faça a atualização de forma controlada: leia as notas de migração, altere a tag, docker compose up -d traefik e monitorize o log. Se ainda estiver numa tag v2, o guia de migração do Traefik v2 para v3 descreve cada renomeação, o modo de compatibilidade e um rollback que preserva os seus certificados.

Faça backup de ./letsencrypt e do volume de dados de cada aplicação. O Traefik não mantém outro estado que não possa reconstruir a partir do ficheiro compose.

O que falha em escala

O primeiro limite não é o throughput, mas o servidor único: um Traefik num único VPS é um ponto único de falha para cinco aplicações, e acme.json é armazenamento em ficheiro simples; duas instâncias do Traefik a escrever nesse ficheiro vão corrompê-lo. Escalar horizontalmente significa retirar o armazenamento dos certificados de um ficheiro ou terminar o TLS noutro local.

O segundo limite são as ligações de longa duração. Server-sent events, uploads grandes e clientes lentos atingem os timeouts de resposta do entrypoint; --entryPoints.websecure.transport.respondingTimeouts.readTimeout e os seus equivalentes writeTimeout e idleTimeout são os parâmetros de controlo. WebSockets passam sem configuração adicional.

O terceiro limite é o disco. --accesslog=true escreve em stdout, e o driver json-file do Docker mantém esses dados indefinidamente, a menos que exista um limite. Defina logging.options.max-size no serviço Traefik ou escreva o access log num ficheiro e faça a sua rotação.

Nada disto exige um orquestrador. É necessário um servidor sob o seu controlo, com um IP real e as portas 80 e 443 abertas para a Internet. Um VPS pequeno é toda a lista de dependências.

FAQ

Ainda preciso do certbot se eu executar o Traefik?

Não. O resolvedor ACME do Traefik solicita e renova o certificado para cada nome de host que encaminha e armazena tudo em acme.json. O Certbot continua a ser a ferramenta adequada quando o nginx ou outro servidor termina o TLS diretamente; executar ambos para os mesmos nomes de host apenas consome os limites de taxa do Let's Encrypt.

Por que o meu contentor devolve 404 através do Traefik?

Um 404 servido pelo Traefik significa que nenhum router correspondeu ao pedido. Verifique se o contentor tem traefik.enable=true (obrigatório quando exposedByDefault=false está definido), se o valor de Host() corresponde ao nome que introduziu e se o nome do router é idêntico em todas as labels dessa aplicação. O Traefik v3 também exige crases dentro do matcher, não aspas.

Qual é a diferença entre 404 e 502 neste caso?

Um 404 significa que o encaminhamento não ocorreu; um 502 significa que um router correspondeu, mas o backend recusou a ligação. As causas habituais de um 502 são um contentor que não está ligado à rede proxy e um loadbalancer.server.port que aponta para uma porta publicada, em vez da porta em que a aplicação escuta dentro do contentor. O log de acesso indica o endereço exato para o qual o Traefik tentou ligar-se.

Montar o socket do Docker como somente leitura é suficiente?

A flag :ro torna o ficheiro do socket somente leitura, mas não a API disponibilizada por esse socket; os pedidos POST continuam a passar por ele, e o acesso à API do Docker equivale a root no host. A configuração mais segura é o contentor docker-socket-proxy apresentado acima, que expõe ao Traefik apenas operações de leitura sobre contentores e redes e bloqueia diretamente as operações de escrita.

O Traefik pode emitir um certificado wildcard?

Apenas através do desafio DNS-01, com um token de API do seu fornecedor de DNS. O TLS-ALPN-01 e o HTTP-01 validam cada um um único nome de host e não podem produzir um wildcard. O DNS-01 também é a solução quando uma CDN como a Cloudflare termina o TLS à frente do seu VPS e os outros dois desafios nunca são concluídos.