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

Risco de perder o acesso: mudanças de firewall podem derrubar sua sessão SSH. Mantenha uma sessão aberta enquanto testa e saiba acessar o console pelo iLO/IPMI. Se ficar sem acesso, entre pelo console e rode ufw disable ou iptables -F DOCKER-USER para desfazer.
Não desative o iptables do Docker ("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:

  1. Leia o README do projeto e o bloco de regras proposto antes de aplicar.
  2. Faça backup: sudo cp /etc/ufw/after.rules /etc/ufw/after.rules.bak.
  3. 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
  4. 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-numbers mostra suas regras e os contadores de pacotes subindo.
  • sudo ufw status verbose lista as regras de route (opção 4).

Problemas comuns

  • Containers perderam acesso à internet: faltou a regra de ESTABLISHED,RELATED antes 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

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.

Esta resposta lhe foi útil? 0 Usuários acharam útil (0 Votos)

Leia também