WSL2 e Docker sem Docker Desktop: configurando NAT e acesso localhost manualmente

Há um tempo eu venho tentando contruir um SaaS como forma de praticar meus estudos. Durante várias ideias e tentativas, eu me deparei com um problema relacionado ao docker: por algum motivo, o Docker Desktop não queria funcionar com Windows.

O que foi estranho e pareceu ser um problema simples de resolver, acabou se tornando uma dor de cabeça tremenda e me levou a buscar novas alternativas. O uso do WSL2 se mostrou como caminho, não só para resolver um problema, mas como um tópico de estudo para além da programação.

O WSL2 não é uma simples compatibilidade de comandos UNIX dentro do Windows. Ele roda um kernel Linux completo em uma máquina virtual leve baseada em Hyper-V. Quando o Docker Desktop é configurado para usar o WSL2 como backend, todos os containers são criados dentro desse kernel Linux, e não diretamente no Windows.

A comunicação entre o Windows e o ambiente Linux do WSL2 é feita via uma interface virtual criada pelo Windows: vEthernet (WSL). Ela é, na verdade, um Switch Virtual criado automaticamente pela plataforma de virtualização do Windows (chamada Virtual Machine Platform, um componente do Hyper-V) quando o WSL2 é iniciado pela primeira vez. Essa interface atua como única ponte de rede entre dois ambientes isolados:

  • O sistema operacional Windows (Host).
  • O kernel Linux/VM do WSL2 (Guest).

O principal mecanismo que ela gerencia é o NAT (Network Address Translation). O Linux (WSL2 VM) recebe um IP privado (por exemplo, 172.27.x.x ou 192.168.x.x) que muda a cada reinicialização do Windows. Esse IP funciona como Gateway Padrão e Servidor DNS dentro do ambiente Linux, permitindo que o tráfego de saída seja traduzido para a rede do host. No lado do Windows, a interface vEthernet (WSL) recebe o primeiro endereço desse bloco privado (por exemplo, 172.27.x.1) e atua como o roteador/NAT responsável por gerenciar todo o tráfego entre o Windows e a VM Linux.

Teoricamente, o Docker Desktop deveria configurar o proxy automático de portas por meio da interface vEthernet (WSL), permitindo que minha aplicação no Windows acessasse o container através do endereço localhost:1433. No entanto, como optei por rodar o Docker Engine nativo diretamente na minha distribuição WSL2, eu não contava com o serviço de encaminhamento automático de portas que o Docker Desktop injeta no host Windows.

Assim, minha aplicação .NET, executando no Windows, tentava se conectar a localhost:1433. Como não havia o serviço de port forwarding do Docker Desktop, o Windows não sabia que o tráfego dessa porta deveria ser redirecionado para o endereço IP dinâmico da VM WSL2 (geralmente algo como 172.x.x.x). Por isso, a conexão falhava.

Uma alternativa para contornar esse problema foi criar uma rede personalizada no Docker e expor a porta do container do SQL Server para o host Windows. Essa configuração permitiu que o tráfego de localhost:1433 fosse corretamente roteado até o container, mesmo sem o proxy de portas do Docker Desktop.

O primeiro passo foi criar uma rede bridge isolada com suporte a DNS interno:

docker network create mynetwork

Essa rede cria uma bridge virtual (por exemplo, br-2c7e9a5d7b45) com uma sub-rede dedicada (172.18.0.0/16) e um gateway interno (172.18.0.1).
Containers conectados a ela podem se comunicar diretamente por nome, sem precisar de IP fixo.

Em seguida, subi o container do SQL Server conectado a essa rede e expus a porta 1433:

docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=YourStrong!Passw0rd" 
  --name sqlserver 
  --network mynetwork 
  -p 1433:1433 
  -d mcr.microsoft.com/mssql/server:2022-latest

O parâmetro -p 1433:1433 faz o mapeamento de porta entre o host (Windows) e o container, permitindo que a aplicação fora do WSL2 acesse o serviço pelo localhost:1433.

O –network mynetwork garante que, se eu decidir rodar outros containers (por exemplo, uma API .NET) dentro do WSL2, eles poderão se conectar via DNS interno — usando apenas sqlserver como host.

Para confirmar a conectividade, testei o acesso diretamente do Windows:

Test-NetConnection localhost -Port 1433

E também validei de dentro da aplicação .NET, configurando a connection string da seguinte forma:

"Server=localhost,1433;Database=MyAppDb;User Id=sa;Password=YourStrong!Passw0rd;TrustServerCertificate=True;"

Após isso, a aplicação passou a se conectar normalmente ao SQL Server rodando dentro do container, mesmo com o Docker Engine isolado dentro da VM WSL2.

Mas, por que essa solução funciona?

Ao expor a porta (-p 1433:1433), o Docker criou uma regra de DNAT (Destination NAT) na tabela iptables do Linux interno do WSL2. Essa regra instrui o kernel a redirecionar todo o tráfego recebido na porta 1433 do host (interface eth0) para o IP interno do container SQL Server.

Quando o Windows acessa localhost:1433, o pacote é enviado à interface vEthernet (WSL) → traduzido pelo NAT → entregue à bridge br-xxxxx → e finalmente roteado ao container. Com isso, o comportamento esperado do Docker Desktop foi restaurado manualmente, mas de forma controlada e previsível.

Leave a Reply