Caddy como reverse proxy no Docker: configuração com Compose e HTTPS automático
Rode o Caddy como reverse proxy no Docker: uma stack compose, uma rede compartilhada, reverse_proxy para os nomes dos serviços e HTTPS automático, além de quando as labels do caddy-docker-proxy ajudam.
Resposta curta: rode o Caddy num container próprio que publica as portas 80 e 443, conecte-o e os containers dos seus apps a uma rede Docker compartilhada e aponte `reverse_proxy` para o nome de cada serviço e sua porta interna (por exemplo `reverse_proxy app:3000`). O Caddy resolve o nome pelo DNS embutido do Docker e obtém automaticamente um certificado TLS para cada endereço de site.
Este guia mostra primeiro a abordagem com Caddyfile simples, depois a variante caddy-docker-proxy baseada em labels, e explica quando cada uma faz sentido num único VPS. Se você roda o Caddy direto no host em vez de no Docker, veja o guia básico de reverse proxy com Caddy.
Como as peças se encaixam
Só o container do Caddy publica portas para o mundo externo. Todo container de backend entra numa rede bridge definida pelo usuário (aqui chamada `web`) e expõe sua porta apenas nessa rede. O servidor DNS embutido do Docker, disponível em redes definidas pelo usuário, permite que o Caddy alcance `app:3000` pelo nome do serviço ou do container, então você nunca precisa fixar no código IPs de containers que mudam a cada recriação.
Manter os backends sem portas publicadas importa mais do que parece: o Docker escreve as próprias regras de iptables, então uma porta publicada com `ports:` pode ficar acessível pela internet mesmo quando o ufw diz que ela está bloqueada. Use `expose:` ou nenhum mapeamento de porta para tudo o que deve ficar atrás do proxy.
| Componente | Função | Publicado no host? |
|---|---|---|
| container caddy | Terminação TLS, roteamento por hostname, redirecionamento de HTTP para HTTPS | Sim: 80/tcp, 443/tcp, 443/udp |
| rede web | Rede bridge compartilhada com DNS embutido para nomes de serviço | Não |
| containers dos apps | Seus serviços (apps web, APIs, dashboards) | Não, acessíveis só via Caddy |
| volume caddy_data | Certificados, chaves privadas, conta ACME | Não, mas precisa persistir |
Passo a passo: Caddyfile simples com docker compose
- 01.Aponte um registro A (e um AAAA, se você usa IPv6) de cada hostname para o VPS antes de iniciar o Caddy, ou a emissão do certificado vai falhar.
- 02.Crie a rede compartilhada uma vez: `docker network create web`.
- 03.Crie `/opt/proxy/compose.yaml` e `/opt/proxy/Caddyfile` como mostrado abaixo e rode `docker compose up -d` em `/opt/proxy`.
- 04.Conecte cada stack de app à rede externa `web` e inicie-a com `docker compose up -d`.
- 05.Acompanhe a emissão do primeiro certificado com `docker compose logs -f caddy` em `/opt/proxy`.
- 06.Depois de editar o Caddyfile, recarregue sem downtime: `docker compose exec -w /etc/caddy caddy caddy reload`.
# /opt/proxy/compose.yaml
services:
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data # certificates and ACME account keys
- caddy_config:/config
networks:
- web
volumes:
caddy_data:
caddy_config:
networks:
web:
external: trueConecte um app e escreva o Caddyfile
Cada stack de aplicação declara a mesma rede externa. O nome do serviço vira o hostname que o Caddy chama. O volume `caddy_data` é o que você nunca pode perder: apagá-lo obriga o Caddy a pedir todos os certificados de novo, o que pode esbarrar nos rate limits do ACME.
# /opt/app/compose.yaml
services:
app:
image: ghcr.io/example/app:latest
restart: unless-stopped
expose:
- "3000" # reachable on the network, NOT published on the host
networks:
- web
networks:
web:
external: true
# /opt/proxy/Caddyfile
{
email [email protected]
}
app.example.com {
reverse_proxy app:3000
}
grafana.example.com {
reverse_proxy grafana:3000
}
api.example.com {
reverse_proxy api-1:8080 api-2:8080 {
lb_policy round_robin
health_uri /healthz
health_interval 10s
}
}Backends no Caddy: upstreams, balanceamento de carga e health checks
Na terminologia do Caddy, um backend é um upstream de `reverse_proxy`. Você pode listar vários upstreams numa só diretiva, escolher uma `lb_policy` como `round_robin`, `least_conn` ou `ip_hash` e adicionar health checks ativos com `health_uri` para que containers com falha saiam da rotação. Checks passivos (`fail_duration`, `max_fails`) também são úteis quando o app não tem endpoint de saúde.
Por padrão, o Caddy define `X-Forwarded-For`, `X-Forwarded-Proto` e `X-Forwarded-Host` nas requisições encaminhadas. Configure seu app para confiar na faixa de rede do proxy, para que ele veja o IP real do cliente e gere links `https://`. WebSockets e gRPC funcionam pelo `reverse_proxy` sem configuração extra; para gRPC em texto claro, use `reverse_proxy h2c://service:50051`.
Labels em vez de Caddyfile: caddy-docker-proxy
Quem procura um "Caddy proxy manager" geralmente quer o que o Nginx Proxy Manager oferece: adicionar um container e ganhar um hostname e um certificado sem editar um arquivo central. O Caddy não tem GUI oficial, mas o projeto comunitário caddy-docker-proxy (github.com/lucaslorentz/caddy-docker-proxy) gera o Caddyfile a partir das labels dos containers e recarrega o Caddy quando containers iniciam ou param.
Ele precisa de acesso de leitura ao socket do Docker, o que na prática equivale a root no host. Monte-o como somente leitura, mantenha a imagem do proxy atualizada e prefira o Caddyfile simples se você roda apenas alguns serviços.
services:
caddy:
image: lucaslorentz/caddy-docker-proxy:ci-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
environment:
- CADDY_INGRESS_NETWORKS=web
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- caddy_data:/data
networks:
- web
whoami:
image: traefik/whoami
networks:
- web
labels:
caddy: whoami.example.com
caddy.reverse_proxy: "{{upstreams 80}}"
volumes:
caddy_data:
networks:
web:
external: trueCaddyfile simples vs caddy-docker-proxy
| Caddyfile simples | Labels do caddy-docker-proxy | |
|---|---|---|
| A configuração fica em | Um único arquivo versionado no git | Labels espalhadas pelos arquivos compose |
| Acesso ao socket do Docker | Não é necessário | Obrigatório (sensível em segurança) |
| Novo serviço | Editar o Caddyfile, rodar `caddy reload` | Adicionar labels, iniciar o container |
| Imagem | `caddy:2` oficial | Imagem da comunidade baseada no Caddy |
| Depuração | Ler o arquivo diretamente | Inspecionar a configuração gerada nos logs |
| Ideal para | Setups de VPS pequenos e estáveis | Muitos serviços efêmeros ou alterados com frequência |
Rodando num VPS
Num VPS KVM da NoctisNode você tem root completo e um IPv4 dedicado, então publicar 80 e 443 a partir de um container Caddy e esconder todo o resto é simples. Um Wisp (1 vCPU, 1 GB de RAM) basta para o Caddy e alguns containers leves; stacks mais pesadas cabem no Pulse ou no Umbra. Os detalhes da emissão de certificados e os modos de falha estão em como funciona o HTTPS automático do Caddy, e o encaminhamento de TCP bruto ou SSH está em proxy TCP e SSH com Caddy. Proteja o SSH do host com o guia de hardening de SSH.
Perguntas frequentes
QComo faço o Caddy servir de reverse proxy para um container Docker?
AColoque o Caddy e o container na mesma rede Docker definida pelo usuário e use `reverse_proxy service-name:port` no Caddyfile. Não use `localhost`, porque dentro do container do Caddy ele se refere ao próprio Caddy.
QPor que o Caddy retorna 502 Bad Gateway para o meu container?
AGeralmente os containers não estão numa rede compartilhada, a porta está errada (use a porta interna do container, não uma porta publicada no host) ou o app escuta só em 127.0.0.1 dentro do container. Faça o app escutar em 0.0.0.0 e confira `docker network inspect web`.
QO caddy-docker-proxy é seguro?
AFunciona bem, mas exige o socket do Docker, que concede controle equivalente a root sobre o host. Monte-o como somente leitura, mantenha a imagem atualizada e evite expor o endpoint de administração do Caddy.
QOnde o Caddy guarda os certificados no Docker?
AEm `/data` dentro da imagem oficial, então monte um volume nomeado ali. Perdê-lo significa reemitir todos os certificados e possivelmente atingir os rate limits do ACME.
QExiste um Caddy proxy manager com interface web?
AO Caddy em si não tem interface web oficial; ele é configurado com Caddyfile, JSON ou pela API de administração. Ferramentas baseadas em labels, como o caddy-docker-proxy, oferecem um fluxo parecido de adicionar um container e pronto.