tutorial: configurar mTLS entre Traefik e serviços em Docker Compose usando smallstep CA e certificados automáticos — passo a passo
Docker & Containers · Por Kevin Marcondes · Publicado em · 12 min de leitura
Este é um tutorial: configurar mTLS entre Traefik e serviços em Docker Compose usando smallstep CA e certificados automáticos. Você verá arquitetura, arquivos, comandos e verificação ponta a ponta. O Traefik autenticará no backend via certificado de cliente, e os serviços exigirão mTLS para aceitar conexões, com emissão e renovação automatizadas pela step-ca.
Arquitetura: como o mTLS entre Traefik e serviços funciona
O Traefik atuará como proxy reverso e se conectará aos serviços de backend por TLS, apresentando um certificado de cliente e validando o certificado do servidor. O backend exigirá um certificado cliente válido para aceitar a conexão.
Usaremos uma Autoridade Certificadora local (smallstep step-ca) para emitir: (1) um certificado de cliente para o Traefik e (2) um certificado de servidor para cada serviço. A renovação é automática com o step CLI.
Esta abordagem protege a conexão interna, reduz ataques de man-in-the-middle e autentica ambos os lados da conexão. Se estiver avaliando sua PKI, compare alternativas em smallstep vs HashiCorp Vault para mTLS em Docker Compose.
- Traefik valida o certificado do servidor (CA raiz confiável).
- Serviço valida o certificado de cliente do Traefik (mTLS obrigatório).
- Certificados emitidos e renovados pela step-ca via step CLI.
- Roteamento HTTP externo continua simples; mTLS é aplicado apenas no upstream Traefik→serviços.
| Componente | Certificados/Arquivos | Finalidade |
|---|---|---|
| step-ca | root_ca.crt, intermediária, ca.json | Emitir e renovar certificados |
| Traefik | traefik-client.crt, traefik-client.key, root_ca.crt | mTLS como cliente; confiar no backend |
| Serviço (ex.: Nginx) | server.crt, server.key, root_ca.crt | TLS do servidor e exigência de cliente |
Pré-requisitos e escopo do ambiente
Você precisa de Docker e Docker Compose instalados, acesso shell e portas livres 80 e 9000. Vamos criar uma rede Docker única, com três serviços: step-ca, Traefik e um backend Nginx como exemplo.
Os domínios usados são locais (app.localhost) e nomes internos (app.internal). Adapte para sua realidade. Não exponha a CA na internet; mantenha-a acessível apenas na rede interna do Compose.
- Docker e Compose atualizados.
- Portas 80 (Traefik) e 9000 (step-ca) livres.
- Editor de texto para criar arquivos.
- Conhecimento básico de YAML e TLS (SAN, CA).
Como iniciar uma smallstep CA no Docker e obter o fingerprint?
A smallstep CA (step-ca) será nossa PKI interna. Vamos inicializar o repositório da CA em um volume Docker, gerar root/intermediária e guardar a senha. O fingerprint da raiz será usado para bootstrap seguro pelos clientes.
Os comandos abaixo criam o volume, inicializam a CA e imprimem o fingerprint da raiz.
# 1) Crie o volume de dados da CA
docker volume create step-ca
# 2) Gere uma senha e inicialize a estrutura de diretórios
# (a senha protege as chaves privadas da CA)
docker run --rm -v step-ca:/home/step alpine sh -c '
set -e; mkdir -p /home/step/secrets;
head -c 16 /dev/urandom | base64 > /home/step/secrets/password
'
# 3) Inicialize a CA (nome, DNS interno e provisionador de login)
docker run --rm -it -v step-ca:/home/step smallstep/step-ca \
step ca init \
--deployment-type standalone \
--name "Local Dev CA" \
--dns "step-ca" \
--address ":9000" \
--provisioner "[email protected]" \
--password-file /home/step/secrets/password
# 4) Descubra o fingerprint da raiz (copie para usar no Compose)
docker run --rm -v step-ca:/home/step smallstep/step-ca \
step certificate fingerprint /home/step/certs/root_ca.crt
# Guarde o fingerprint impresso (ex.: 7f:ab:...): vamos usar como STEP_FINGERPRINT.
Como emitir e renovar certificados automáticos no serviço backend (Nginx)?
O backend precisa de dois itens: (a) um certificado de servidor emitido pela CA e (b) a configuração do Nginx para exigir certificado de cliente. Usaremos uma imagem customizada com o step CLI para emitir e renovar o certificado, recarregando o Nginx quando o arquivo mudar.
Crie os arquivos abaixo no diretório ./app. A renovação roda em segundo plano e dispara reload do Nginx sem interromper conexões ativas.
# file: app/Dockerfile
FROM nginx:alpine
RUN apk add --no-cache curl tar ca-certificates bash \
&& update-ca-certificates
# Instala o step CLI (verifique a URL mais recente conforme sua política interna)
RUN curl -fsSL https://dl.smallstep.com/cli/releases/latest/step_linux_amd64.tar.gz \
| tar -xz -C /usr/local/bin --strip-components=1 'step_*/bin/step'
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
# file: app/nginx.conf
server {
listen 443 ssl http2;
server_name app.internal;
ssl_certificate /etc/nginx/certs/server.crt;
ssl_certificate_key /etc/nginx/certs/server.key;
# Exigir certificado de cliente (mTLS)
ssl_client_certificate /step/certs/root_ca.crt; # CA raiz confiável
ssl_verify_client on;
# End-point exemplo
location / {
add_header Content-Type text/plain;
return 200 'ok from app (mTLS required)\n';
}
}
# file: app/entrypoint.sh
#!/usr/bin/env bash
set -euo pipefail
: "${STEP_FINGERPRINT:?Defina STEP_FINGERPRINT no compose}"
: "${STEP_PROVISIONER:?Defina STEP_PROVISIONER no compose}"
# Bootstrap confiança na CA interna (via DNS step-ca na rede Docker)
step ca bootstrap --ca-url https://step-ca:9000 --fingerprint "$STEP_FINGERPRINT"
# Emite o certificado de servidor (SAN: app.internal)
mkdir -p /etc/nginx/certs
step ca certificate "app.internal" \
/etc/nginx/certs/server.crt /etc/nginx/certs/server.key \
--provisioner "$STEP_PROVISIONER" --no-password --san "app.internal"
# Renova automaticamente e recarrega o Nginx quando necessário
step ca renew /etc/nginx/certs/server.crt /etc/nginx/certs/server.key \
--daemon --exec "nginx -s reload"
# Inicia o Nginx em primeiro plano
exec nginx -g 'daemon off;'
Como configurar o Traefik para mTLS com os backends?
O Traefik precisa de: (a) um certificado de cliente para apresentar ao backend e (b) confiar na CA raiz para validar o certificado do servidor. Usaremos uma sidecar com step CLI para emitir/renovar o certificado de cliente e um arquivo dinâmico do Traefik para definir o serversTransport com mTLS.
Para estratégias de roteamento graduais, veja exemplos de canário em Traefik com Canary releases no Docker Compose.
- O Traefik usa serversTransport para mTLS no upstream.
- O router expõe app.localhost via HTTP e encaminha por HTTPS+mTLS ao backend.
- A sidecar mantém o cert de cliente renovado; reiniciar o Traefik aplica o novo cert.
# file: traefik/dynamic.yml
http:
serversTransports:
mtls-to-app:
serverName: app.internal
rootCAs:
- /step/certs/root_ca.crt
certificates:
- certFile: /certs/traefik-client.crt
keyFile: /certs/traefik-client.key
routers:
app:
rule: Host(`app.localhost`)
entryPoints: ["web"]
service: app
services:
app:
loadBalancer:
servers:
- url: https://app:443
serversTransport: mtls-to-app
Subir tudo com Docker Compose (Traefik, step-ca e backend)
O docker-compose.yml integra a CA, o Traefik, a sidecar que gerencia o certificado de cliente e o backend Nginx. Antes de subir, substitua o valor de STEP_FINGERPRINT pelo fingerprint obtido na inicialização da CA.
No primeiro uso, a sidecar e o backend emitem certificados; a renovação ocorre em background. Para aplicar certificados renovados no Traefik, reinicie o container quando desejar.
# file: docker-compose.yml
version: "3.9"
services:
step-ca:
image: smallstep/step-ca:latest
command: ["step-ca", "/home/step/config/ca.json"]
volumes:
- step-ca:/home/step
ports:
- "9000:9000" # somente para laboratório; evite expor em produção
networks: [internal]
traefik-client:
image: smallstep/step-ca:latest # contém o step CLI
depends_on:
- step-ca
environment:
STEP_FINGERPRINT: "COLE_AQUI_O_FINGERPRINT_DA_RAIZ"
STEP_PROVISIONER: "[email protected]"
volumes:
- step-ca:/step:ro
- traefik-certs:/certs
entrypoint: ["/bin/sh","-c"]
command: >-
step ca bootstrap --ca-url https://step-ca:9000 --fingerprint $STEP_FINGERPRINT &&
step ca certificate traefik-client /certs/traefik-client.crt /certs/traefik-client.key \
--provisioner $STEP_PROVISIONER --no-password --kty RSA &&
step ca renew /certs/traefik-client.crt /certs/traefik-client.key --daemon
networks: [internal]
traefik:
image: traefik:v2.10
depends_on:
- traefik-client
command:
- "--providers.docker=true"
- "--providers.file.directory=/etc/traefik/dynamic"
- "--providers.file.watch=true"
- "--entrypoints.web.address=:80"
ports:
- "80:80"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik/dynamic.yml:/etc/traefik/dynamic/dynamic.yml:ro
- traefik-certs:/certs:ro
- step-ca:/step:ro
networks: [internal]
app:
build: ./app
depends_on:
- step-ca
environment:
STEP_FINGERPRINT: "COLE_AQUI_O_FINGERPRINT_DA_RAIZ"
STEP_PROVISIONER: "[email protected]"
volumes:
- step-ca:/step:ro # para confiar na raiz da CA
networks: [internal]
volumes:
step-ca:
traefik-certs:
networks:
internal:
driver: bridge
Como confirmar que funcionou? Testes rápidos de verificação
Verifique que o Traefik responde em HTTP e encaminha para o backend por HTTPS+mTLS. Depois, tente acessar o backend diretamente sem cliente mTLS para ver a falha esperada.
Se algo falhar, confira logs dos containers e valide nomes (serverName, SAN app.internal) e o fingerprint.
- Teste via Traefik deve retornar 200 com a mensagem do backend.
- Acesso direto ao backend sem cliente mTLS deve falhar (handshake).
- Logs do Nginx indicam ssl client verify successful ao passar pelo Traefik.
# 1) Suba o ambiente
docker compose up -d --build
# 2) Acesse via Traefik (host header para roteamento)
curl -sS -H 'Host: app.localhost' http://127.0.0.1/
# Esperado: ok from app (mTLS required)
# 3) Teste o backend sem cliente mTLS (deve falhar)
docker compose run --rm alpine sh -lc "apk add --no-cache curl >/dev/null && curl -ksS https://app:443 || echo 'falha esperada (mTLS)'"
# 4) Opcional: veja logs
docker compose logs -f app traefik
Dicas de produção e manutenção de certificados
Em produção, mantenha a step-ca acessível apenas em rede privada, com backups das chaves e rotação planejada. Ajuste lifetimes curtos e renovações frequentes para reduzir risco.
Para aplicar certificados renovados no Traefik, agende um reload ou reinício controlado. O Nginx já recarrega automático no hook de renovação. Testes de renovação devem ser feitos fora do horário crítico. Para gestão de logs de containers, veja práticas em retenção e compressão de logs com journald + logrotate.
- Isolar a CA (rede privada, restrições de firewall).
- SANs coerentes com DNS/Service Discovery.
- Clock sincronizado (NTP) para evitar invalidez por tempo.
- Automatize reload do Traefik em janela segura.
- Monitore expiração de certificados (alertas).
Quais erros de mTLS aparecem e como resolver?
Falhas comuns incluem SAN incorreto, fingerprint errado no bootstrap, cadeia de certificados incompleta e problemas de DNS. Use logs e testes diretos para isolar a causa.
Se o handshake falha, verifique também se o Traefik está usando o serversTransport correto e se o SNI (serverName) bate com o SAN do certificado do backend.
- SAN/SNI não batem: ajuste serverName e reemita o cert do backend.
- Fingerprint incorreto: refaça o bootstrap com o fingerprint certo.
- Cadeia incompleta: garanta uso da raiz correta no rootCAs/ssl_client_certificate.
- DNS/Service name errado: confirme app.internal na rede Docker.
- Tempo (NTP) fora: sincronize relógios para evitar NotBefore/NotAfter.
Onde se aprofunda em alternativas e variações?
Se você precisa comparar motores de emissão de certificados ou ver uma variação com Nginx direto na borda, confira estes materiais relacionados. Para estratégias de roteamento avançadas no Traefik, veja canary e regras de peso.
Gerenciamento de logs e retenção em VPS pequenos também pode ajudar no dia a dia de operação.
- Leia o comparativo smallstep vs HashiCorp Vault para mTLS em Docker Compose.
- Veja a variação com Nginx na borda: mTLS com Nginx e smallstep em Docker Compose.
- Aprofunde roteamento no Traefik com Canary releases no Docker Compose.
- Otimize retenção/rotação de logs de containers com journald + logrotate + rsyslog.
Perguntas frequentes
Preciso expor a porta da CA (9000) na internet?
Não, mantenha a step-ca acessível apenas na rede interna do Docker ou VPN. Expor a CA aumenta a superfície de ataque. Os serviços emitem e renovam certificados via DNS interno (step-ca) sem necessidade de acesso externo.
Como o Traefik apresenta o certificado de cliente ao backend?
O Traefik usa um serversTransport com certificados de cliente (certFile/keyFile) e a raiz confiável (rootCAs). Ele se conecta por HTTPS ao backend, apresenta o certificado e valida o certificado do servidor antes de encaminhar o tráfego.
Posso usar vários serviços de backend com mTLS?
Sim. Emita um certificado de servidor para cada serviço (ex.: api.internal, auth.internal) e crie serversTransports específicos quando precisar de SNI diferente. Você pode reutilizar o mesmo certificado cliente do Traefik ou emitir clientes distintos por serviço.
Como renovar certificados sem downtime?
O step CLI renova em background e pode executar um reload suave no serviço (Nginx). Para o Traefik, programe um reload/restart controlado do container quando os arquivos de certificado forem atualizados, evitando janelas críticas e mantendo instâncias redundantes quando possível.
É possível usar domínios reais (externos) com esta CA?
É possível, desde que o DNS aponte para os serviços corretos e você gerencie a confiança da CA nos clientes. Em geral, use a CA interna para tráfego interno (Traefik→serviços) e outra solução para TLS externo público, mantendo escopos separados.
Como diagnosticar falhas de handshake TLS entre containers?
Verifique logs do Traefik e do backend, confirme SAN/SNI e confianças de CA. Tente conexões de teste a partir de um container temporário e valide datas de validade. Erros comuns incluem SAN errado e fingerprint incorreto no bootstrap dos clientes.
Quer levar mTLS ao seu ambiente Docker com segurança?
Se este passo a passo ajudou, você já tem a base para rodar mTLS entre Traefik e serviços com certificados automáticos. Precisa adaptar para múltiplos clusters, rotação e observabilidade? Vamos conversar.
Falar sobre mTLS em DockerVeja também
- Tutorial: integrar Sentry ao pipeline de deploy com GitHub Actions para upload automático de source maps e criação de issues (PHP e JavaScript) — passo a passo
- Configurar retenção e compactação de logs de containers Docker com journald + logrotate + rsyslog em VPS com pouco disco
- tutorial: configurar GitHub Actions para testes de integração com PostgreSQL em containers efêmeros e rollback automático de migrações — guia prático
- Configurar BuildKit garbage collection e systemd timers para limpeza automática de imagens e volumes Docker em VPS: tutorial prático