Alcançar destino IPv6-only sem mexer no host

O problema

Alguns serviços gerenciados só publicam AAAA (IPv6), sem registro A. O caso clássico é o Supabase no free tier: db.<ref>.supabase.co resolve só pra IPv6. Como as redes bridge do Docker nascem IPv4-only, o container não consegue abrir a conexão — e o deploy fica em restart loop com um erro genérico de driver de banco (Unable to determine Dialect without JDBC metadata, connection refused, etc.), que não parece um problema de rede.

Por que a solução "óbvia" mexe no host

O caminho intuitivo é criar uma rede dual-stack (docker network create --ipv6 --subnet fd00:.../64 ...) e listá-la no networks: do app. Isso funciona, mas tem um custo: o container passa a fazer o egress IPv6 ele mesmo, e pra isso o kernel precisa rotear os pacotes da bridge pra NIC física — o que exige net.ipv6.conf.all.forwarding=1.

Esse sysctl é um parâmetro global do host, não da app, e ligá-lo tem um efeito colateral perigoso: o kernel para de aceitar Router Advertisements (RA). Num host que pega o IPv6 via SLAAC/RA (comum em cloud), isso derruba o IPv6 do próprio host, a menos que você paree com accept_ra=2 na interface. Ou seja: uma config de app acaba precisando de uma mutação de kernel que pode tirar o servidor do ar.

Em Docker 28+ o docker network create --ipv6 já liga o forwarding sozinho ao subir a bridge. O ponto continua valendo: é o host sendo alterado, com o risco de RA acima.

A solução: relay IPv4→IPv6 no host

Em vez do container fazer o egress IPv6, um relay no namespace do host recebe a conexão em IPv4 e a repassa em IPv6. O container fica na bridge normal, 100% IPv4, e o Traefik ingress continua intacto.

app container (bridge normal, IPv4)
   │  conecta em 172.17.0.1:5432  (gateway do host — IPv4, SEM forwarding)
   ▼
relay no host namespace  (socat / haproxy)
   │  abre conexão pro [db.<ref>.supabase.co]:5432 via IPv6
   ▼  (é o HOST originando o tráfego)
destino IPv6-only

Por que não precisa de forwarding: o sysctl governa rotear pacotes de terceiros entre interfaces. Os dois saltos aqui são isentos disso — o container falando com seu próprio gateway é comunicação local com a bridge; e o relay, por estar no host, faz o host originar a conexão IPv6, que usa a stack do host direto (tráfego local não passa pela lógica de forwarding).

Prova (validada em Docker 29, host OVH com `forwarding=0`)

Teste Resultado
Host originando IPv6 (forwarding=0) ✅ alcança — tráfego local dispensa forwarding
Container v6 direto (sem rede ipv6) ❌ falha — sem rota v6
Container → relay → endpoint v6-only ✅ o destino enxerga o IPv6 público do host

O net.ipv6.conf.all.forwarding permaneceu 0 o tempo todo, e o container tinha zero endereços IPv6.

Como configurar

1. O relay (systemd, resiliente)

# /etc/systemd/system/relay-supabase.service
[Unit]
Description=Relay IPv4->IPv6 para Supabase (db)
After=network-online.target docker.service

[Service]
# socat: escuta na bridge do host em IPv4, repassa pro Supabase em IPv6
ExecStart=/usr/bin/socat TCP4-LISTEN:5432,fork,reuseaddr,bind=172.17.0.1 TCP6:db.<ref>.supabase.co:5432
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now relay-supabase

Alternativa em container (sem instalar socat no host):

# serviço de relay, no host namespace
services:
  relay-supabase:
    image: alpine/socat
    network_mode: host
    restart: always
    command: TCP4-LISTEN:5432,fork,reuseaddr,bind=172.17.0.1 TCP6:db.<ref>.supabase.co:5432

2. O app aponta pro relay (não pro hostname Supabase)

# .deploy.yml
environment:
  DB_HOST: "172.17.0.1"   # gateway/host, IPv4 puro
  DB_PORT: "5432"

O app fica na bridge normal, com Traefik ingress e isolamento preservados. Nada de --ipv6, nada de forwarding, nada de rede custom.

Ressalvas

  • TLS/Postgres: o Postgres negocia TLS dentro da conexão (não por SNI no handshake TCP), então o relay TCP cru funciona e o certificado do Supabase valida pelo hostname normalmente.
  • HTTPS com SNI: pra alvos HTTPS o relay tem que ser TCP puro (passthrough) e o cliente precisa mandar o SNI/Host certo — não é o caso de banco, mas atente se for relay pra uma API HTTPS.
  • Um relay por destino: limpo pra 1–2 endpoints (banco, storage). Pra egress IPv6 arbitrário não escala — aí a conversa é outra (ver abaixo).
  • Resiliência: use systemd com Restart=always (ou haproxy com health check). socat solto num terminal morre fácil.

Quando o relay não é o ideal

  • Some com o problema na fonte: o Supabase oferece IPv4 via o pooler (Supavisor) e via add-on de IPv4 dedicado. Se disponível, trocar a connection string elimina a necessidade de egress IPv6 — sem relay, sem sysctl.
  • Egress IPv6 amplo (não um endpoint só): aí cabe macvlan/ipvlan com um prefixo /64 roteado (cada container ganha um IPv6 público próprio, sem forwarding) — mas exige espaço de endereço real do provedor e sai da bridge public, quebrando o Traefik-por-nome. Bom pra worker, não pra app com ingress.

Relacionado

By Borlot.com.br on 17/07/2026