1

Entendendo redes na prática com Docker: bridges, containers e roteamento no macOS

Recentemente, comecei a estudar o curso Conceitos Básicos de Rede. Durante as aulas, passei por assuntos como endereçamento IP, interfaces de rede, switches, bridges, domínios de broadcast e roteamento.

Em determinado momento, decidi fazer uma pausa no conteúdo teórico e tentar enxergar esses conceitos em algo que já faz parte do meu dia a dia como desenvolvedor: Docker.

A ideia era simples: em vez de estudar uma bridge apenas como um dispositivo de camada 2, por que não investigar como o Docker utiliza bridges para permitir a comunicação entre containers?

O que começou como um experimento simples acabou levantando várias dúvidas sobre redes virtuais, endereçamento IP, máquinas virtuais e a diferença entre executar Docker no Linux e no macOS.

Neste post, compartilho o que aprendi durante essa investigação.

1. Como o Docker organiza a rede dos containers?

Quando instalamos o Docker Engine em um ambiente Linux, normalmente temos uma rede padrão chamada bridge.

Podemos visualizar as redes disponíveis com:

docker network ls

Uma saída típica seria:

NETWORK ID     NAME      DRIVER
xxxxxxxxxxxx   bridge    bridge
xxxxxxxxxxxx   host      host
xxxxxxxxxxxx   none      null

A rede bridge utiliza uma bridge virtual Linux, normalmente chamada docker0.

Essa interface funciona de maneira semelhante a um switch Ethernet virtual, permitindo que containers conectados à mesma bridge se comuniquem pela camada 2.

Imagine dois containers:

flowchart TB
    subgraph ubuntu["VM Ubuntu — host do Docker Engine"]
        app["Navegador ou curl no Ubuntu"]
        stack["Pilha de rede Linux / roteamento"]
        bridge["Bridge docker0 — 172.17.0.1/16"]
        a["Container A — eth0: 172.17.0.2"]
        b["Container B — eth0: 172.17.0.3"]
        app --> stack --> bridge
        bridge --- a
        bridge --- b
    end

No Ubuntu: a interface docker0 faz parte do próprio host Linux, que possui uma rota para a rede dos containers.

Nesse exemplo, os dois containers pertencem à rede 172.17.0.0/16.

Cada container possui seu próprio ambiente de rede, incluindo interfaces, endereços IP e tabela de roteamento.

No Linux, a conexão entre um container e a bridge geralmente é realizada por meio de um par de interfaces virtuais chamado veth pair.

Uma extremidade fica no namespace de rede do container, normalmente identificada como eth0, enquanto a outra fica conectada à bridge no host.

Isso permite que os containers troquem quadros Ethernet, de maneira semelhante a computadores conectados ao mesmo switch físico.

2. Mas como o host consegue acessar os containers?

Aqui surgiu uma das primeiras dúvidas.

Se o container possui o IP 172.17.0.2, por que um computador Linux executando Docker consegue acessá-lo diretamente?

A resposta está na interface docker0.

Em uma configuração típica, ela possui o endereço:

172.17.0.1/16

Como o host Linux possui uma interface conectada à rede 172.17.0.0/16, ele também possui uma rota para essa rede.

Podemos verificar isso utilizando:

ip route

Uma das entradas pode ser semelhante a:

172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1

Isso indica que o Linux sabe como alcançar os endereços dessa sub-rede.

Portanto, se um container estiver executando um servidor HTTP na porta 80, o host Linux normalmente poderá acessá-lo diretamente:

curl http://172.17.0.2:80

Sem precisar publicar a porta do container no host.

Foi nesse ponto que surgiu uma diferença interessante entre o ambiente utilizado no curso e o meu computador.

3. Por que isso não funciona da mesma forma no macOS?

O professor utilizava uma máquina virtual Ubuntu para executar Docker.

Eu estava utilizando Docker Desktop diretamente no meu MacBook.

Ao tentar acessar o IP de um container pelo navegador do macOS, percebi que o comportamento era diferente.

A explicação está na arquitetura do Docker Desktop.

Os containers Linux dependem de funcionalidades do kernel Linux, como namespaces e cgroups.

Como o macOS utiliza outro kernel, o Docker Desktop executa os containers dentro de uma máquina virtual Linux.

A arquitetura fica aproximadamente assim:

flowchart TB
    subgraph mac["Host físico — macOS"]
        browser["Navegador no Mac"]
        desktop["Docker Desktop — integração e portas publicadas"]
        browser --> desktop
    end
    subgraph vm["VM Linux — host do Docker Engine"]
        engine["Docker Engine / rede da VM"]
        bridge["Bridge docker0 — 172.17.0.1/16"]
        a["Container A — 172.17.0.2"]
        b["Container B — 172.17.0.3"]
        engine --- bridge
        bridge --- a
        bridge --- b
    end
    desktop -. "Encaminhamento de portas, quando publicadas" .-> engine

No macOS: docker0 pertence à VM Linux. A linha tracejada representa a integração do Docker Desktop, não uma bridge de camada 2 direta entre o Mac e docker0.

A interface docker0 não pertence diretamente à pilha de rede do macOS. Ela existe dentro da VM Linux.

Consequentemente, o macOS não possui, por padrão, uma rota direta para os endereços IP dos containers nessa rede.

Isso explica por que acessar 172.17.0.2 diretamente funciona normalmente no host Linux de uma instalação tradicional, mas não no macOS com Docker Desktop.

O ponto principal é que o host do Docker Engine e o sistema operacional físico nem sempre são o mesmo ambiente de rede.

4. Então por que localhost funciona?

Mesmo sem conseguir acessar diretamente o IP do container, eu conseguia acessar aplicações executadas nele utilizando localhost.

Por exemplo:

docker run -d --name meu-nginx -p 8080:80 nginx

Esse comando inicia um container com Nginx e publica sua porta 80 na porta 8080 do host.

O mapeamento é:

8080:80
 |   |
 |   |--- Porta do container
 |
 |------- Porta do host

Quando acessamos:

http://localhost:8080

O Docker Desktop encaminha a conexão recebida no Mac para a porta 80 do container.

O fluxo simplificado é:

Navegador no macOS
    |
    | localhost:8080
    |
Docker Desktop
    |
    | Encaminhamento de portas
    |
VM Linux
    |
    | Rede Docker
    |
Container Nginx
172.17.0.2:80

Esse comportamento é possível graças ao mecanismo de publicação de portas do Docker.

É importante observar que localhost representa o próprio ambiente de rede de quem realiza a conexão.

Ou seja, localhost no macOS não é a mesma coisa que localhost dentro de um container.

Cada container possui seu próprio namespace de rede e, consequentemente, sua própria interface de loopback.

5. Bridge não é a mesma coisa que roteador

Outro conceito que ficou mais claro durante os experimentos foi a diferença entre bridge e roteamento.

Uma bridge opera na camada 2 do modelo OSI, encaminhando quadros Ethernet entre interfaces conectadas.

Já um roteador opera na camada 3, encaminhando pacotes IP entre redes diferentes.

Na rede padrão do Docker, a interface docker0 exerce um papel interessante:

  • Como bridge, conecta as interfaces virtuais dos containers.
  • Como interface IP do host Linux, normalmente possui o endereço 172.17.0.1, utilizado como gateway pelos containers.

Quando o Container A (172.17.0.2) precisa se comunicar com o Container B (172.17.0.3) na mesma rede, a comunicação pode ocorrer pela bridge.

Entretanto, quando um container precisa acessar um endereço externo, como 8.8.8.8, o pacote é enviado ao gateway.

O Linux então pode realizar o roteamento e aplicar NAT para permitir que o container acesse redes externas.

Assim, temos duas responsabilidades diferentes:

Bridge: comunicação entre dispositivos de uma mesma rede de camada 2.

Roteamento: comunicação entre redes IP diferentes.

Essa distinção ajudou a entender por que a existência de uma bridge não significa automaticamente que qualquer dispositivo externo conseguirá acessar os containers.

6. Um experimento para reproduzir

Uma forma interessante de visualizar esses conceitos é criar dois containers na mesma rede.

Primeiro, criamos uma rede bridge própria:

docker network create minha-rede

Depois, iniciamos um servidor Nginx:

docker run -d \
  --name servidor \
  --network minha-rede \
  nginx

Observe que não publicamos nenhuma porta com -p.

Agora podemos iniciar outro container na mesma rede e realizar uma requisição HTTP:

docker run --rm \
  --network minha-rede \
  curlimages/curl \
  http://servidor

O segundo container consegue acessar o Nginx utilizando o nome servidor, graças à resolução de nomes fornecida pelo Docker em redes bridge definidas pelo usuário.

Nesse cenário, não precisamos utilizar o IP do Mac nem publicar a porta do Nginx.

Os dois containers se comunicam pela rede virtual criada pelo Docker.

Também podemos inspecionar essa rede:

docker network inspect minha-rede

O resultado apresenta informações como a sub-rede utilizada, o gateway e os containers conectados.

É um experimento simples, mas que ajuda a visualizar vários conceitos de redes funcionando juntos.

7. O que aprendi com essa investigação?

Antes, eu enxergava Docker principalmente como uma ferramenta para empacotar aplicações e padronizar ambientes de desenvolvimento.

Depois dessa investigação, comecei a perceber como ele também é uma excelente ferramenta para estudar redes de computadores.

Consegui relacionar conceitos que estava estudando no curso com situações reais:

  • Endereçamento IP: cada container pode possuir seu próprio endereço em uma rede virtual.
  • Interfaces de rede: containers possuem interfaces virtuais, como eth0.
  • Bridges: permitem a comunicação de camada 2 entre containers.
  • Domínios de broadcast: uma bridge conecta interfaces dentro de um mesmo domínio de broadcast.
  • Roteamento: permite a comunicação entre a rede dos containers e outras redes.
  • NAT e publicação de portas: ajudam a disponibilizar serviços dos containers para outros ambientes.
  • Virtualização: explica as diferenças de comportamento entre Docker no Linux e Docker Desktop no macOS.

Uma das principais lições foi perceber que conhecer o endereço IP de um dispositivo não significa necessariamente conseguir acessá-lo.

Para que a comunicação aconteça, precisamos considerar a topologia da rede, as rotas disponíveis e os mecanismos de encaminhamento envolvidos.

Conclusão

Minha intenção inicial era apenas fazer uma pausa no curso de Conceitos Básicos de Rede e experimentar alguns assuntos na prática.

Acabei encontrando no Docker uma maneira de conectar o conteúdo teórico com ferramentas que já utilizo durante o desenvolvimento de software.

O mais interessante foi perceber que conceitos como bridges, interfaces, sub-redes e roteamento não são apenas abstrações apresentadas em diagramas de redes. Eles estão presentes em ferramentas que usamos diariamente.

Ainda tenho bastante coisa para explorar, especialmente namespaces de rede, interfaces veth, tabelas de roteamento e regras de NAT no Linux.

Mas essa experiência reforçou uma abordagem de estudo que pretendo continuar utilizando: aprender a teoria e, sempre que possível, procurar onde ela aparece nas ferramentas que já fazem parte da minha rotina.

E vocês, já utilizaram Docker ou outras ferramentas de desenvolvimento para estudar conceitos de redes na prática?

Carregando publicação patrocinada...