Entenda por que o Docker ignora o UFW e expõe portas na internet, e veja 4 formas de corrigir: localhost, DOCKER-USER e ufw-docker.
Por que o Docker ignora o UFW
Uma surpresa comum: você ativa o UFW, libera só a porta 22, sobe um container com -p 3306:3306 e o MySQL fica acessível para a internet inteira. Isso acontece porque o Docker ignora o UFW, e não é bug do UFW. O Docker cria regras de NAT próprias no iptables, e o tráfego destinado a uma porta publicada é redirecionado para o container antes de passar pelas cadeias INPUT que o UFW controla. A própria documentação da Docker afirma que Docker e UFW são incompatíveis nesse ponto.
Este artigo mostra quatro formas de resolver, da mais simples para a mais completa. Para o básico do UFW, veja também o artigo «UFW no Ubuntu e Debian: como configurar o firewall passo a passo». Em servidores dedicados com IP público direto, resolver isso é obrigatório.
Pré-requisitos
- Docker instalado pelo repositório oficial (veja o artigo «Como instalar Docker no Ubuntu e no Debian (com Docker Compose, pelo repositório oficial)»).
- Acesso ao console IPMI/iLO do servidor, caso uma regra bloqueie o seu SSH.
ufw disable ou iptables -F DOCKER-USER para desfazer."iptables": false no daemon.json) como "solução". Os containers perdem acesso à rede e você passa a manter tudo na mão.Primeiro, descubra o que está exposto
docker ps --format 'table {{.Names}}\t{{.Ports}}'
sudo ss -tlnp
Qualquer porta que apareça como 0.0.0.0:PORTA ou [::]:PORTA está aberta para o mundo, independentemente do UFW. Teste de fora (de outro servidor ou do seu computador) com nc -zv IP_DO_SERVIDOR PORTA.
Opção 1: publique só no localhost (a mais simples)
Se o serviço é acessado por um proxy reverso (Nginx, Caddy, Traefik) que roda no próprio servidor, ou se você só precisa acessar por túnel SSH/VPN, publique a porta apenas no 127.0.0.1:
# docker run
docker run -d -p 127.0.0.1:8080:80 nginx
# compose.yaml
ports:
- "127.0.0.1:8080:80"
Bancos de dados, Redis, painéis administrativos e APIs internas quase nunca precisam estar em 0.0.0.0. Containers no mesmo projeto Compose conversam pela rede interna usando o nome do serviço, sem publicar porta nenhuma.
Opção 2: mude o padrão do Docker para localhost
Para evitar que um -p 5432:5432 esquecido exponha algo, faça o Docker publicar no localhost por padrão. Em /etc/docker/daemon.json:
{
"ip": "127.0.0.1"
}
sudo systemctl restart docker
docker compose up -d --force-recreate # em cada projeto
Com isso, só fica público o que você publicar explicitamente com o IP, por exemplo -p 203.0.113.10:443:443. Essa opção vale para a rede bridge padrão; em redes criadas pelo Compose, confira o resultado com docker ps e, se necessário, informe o IP em cada porta.
Opção 3: filtrar na cadeia DOCKER-USER
O Docker reserva a cadeia DOCKER-USER para regras do administrador, avaliadas antes das regras do próprio Docker. Exemplo: permitir acesso às portas publicadas apenas a partir do IP do seu escritório (troque eno1 pela sua interface pública, vista em ip -br a, ou vmbr0 se for uma VM):
sudo iptables -I DOCKER-USER -i eno1 -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
sudo iptables -I DOCKER-USER 2 -i eno1 ! -s 198.51.100.20 -j DROP
A primeira regra mantém funcionando as respostas de conexões que os próprios containers iniciaram (atualizações, APIs externas). Para liberar uma porta específica a todos, insira antes do DROP uma regra baseada na porta original:
sudo iptables -I DOCKER-USER 2 -i eno1 -p tcp -m conntrack --ctorigdstport 443 -j RETURN
Essas regras somem no reboot. Para persistir, use o pacote iptables-persistent com cuidado (ele salva também as regras do Docker) ou, mais limpo, a opção 4. Repita a lógica com ip6tables se usa IPv6 nos containers.
Opção 4: integrar o UFW ao Docker (ufw-docker)
O projeto open source ufw-docker adiciona ao /etc/ufw/after.rules um bloco que faz a cadeia DOCKER-USER consultar as regras de encaminhamento do UFW. Depois disso, as portas publicadas ficam fechadas para a internet por padrão e você as libera com ufw route:
- Leia o README do projeto e o bloco de regras proposto antes de aplicar.
- Faça backup:
sudo cp /etc/ufw/after.rules /etc/ufw/after.rules.bak. - Instale o script e aplique as regras:
sudo wget -O /usr/local/bin/ufw-docker https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker sudo chmod +x /usr/local/bin/ufw-docker sudo ufw-docker install sudo systemctl restart ufw - Libere o que deve ser público, usando a porta interna do container (não a do host):
sudo ufw route allow proto tcp from any to any port 443 # ou apenas de um IP de origem sudo ufw route allow proto tcp from 198.51.100.20 to any port 5432
Note a diferença: se o container foi publicado com -p 8443:443, a regra usa port 443.
Como saber se funcionou
- De uma máquina externa,
nc -zv IP_DO_SERVIDOR 3306(ou a porta testada) deve dar timeout ou recusa. - No servidor,
sudo iptables -L DOCKER-USER -n -v --line-numbersmostra suas regras e os contadores de pacotes subindo. sudo ufw status verboselista as regras de route (opção 4).
Problemas comuns
- Containers perderam acesso à internet: faltou a regra de
ESTABLISHED,RELATEDantes do DROP, ou você bloqueou a interface errada. - Regra não tem efeito: no ufw-docker, você usou a porta do host em vez da do container. Na DOCKER-USER, verifique a interface (
-i). - Firewall do Proxmox: se o Docker roda numa VM, o firewall do Proxmox (nível datacenter/VM) também filtra o tráfego antes de chegar à VM e é uma camada extra útil.
- nftables: versões recentes do Docker oferecem um backend nftables experimental (
"firewall-backend": "nftables"). Nele o comportamento muda; não use em produção sem testar.
Perguntas frequentes
Por que a porta do container abre mesmo com o UFW bloqueando?
Porque o Docker cria regras de NAT no iptables que redirecionam o tráfego para o container antes das cadeias INPUT controladas pelo UFW. Por isso o ufw deny não tem efeito sobre portas publicadas.
Posso desativar o iptables do Docker para resolver?
Não é recomendado. Com "iptables": false os containers perdem acesso à rede e você precisa manter todas as regras manualmente. Prefira publicar no localhost, usar a cadeia DOCKER-USER ou o ufw-docker.
O problema também acontece com firewalld ou nftables?
Sim, qualquer firewall que filtre só a cadeia INPUT é contornado pelas portas publicadas. A regra é a mesma: filtre na DOCKER-USER ou não publique a porta em 0.0.0.0. Veja também o artigo «Como configurar o nftables no Debian 12 e 13: regras base de firewall comentadas».
Preciso publicar a porta do banco de dados?
Na maioria dos casos, não. Containers do mesmo projeto Compose se comunicam pela rede interna pelo nome do serviço. Veja também o artigo «Portas abertas no servidor: quais nunca devem ficar expostas na internet (e como liberar só para o seu IP)».
Leitura complementar
- Docker: filtragem de pacotes e firewalls
- Docker com iptables (cadeia DOCKER-USER)
- Projeto ufw-docker
Precisa de ajuda? Abra um ticket ou fale com o suporte pelo WhatsApp. Se ficou sem acesso SSH, o suporte pode orientar o acesso ao console iLO/IPMI.
