Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 19

Mapping Container Ports with `-p`

Recommended resources

Olá! Bem-vindo de volta ao nosso curso.

Na lição anterior, você aprendeu a criar redes bridge personalizadas para isolar serviços e permitir que eles se comuniquem de forma segura usando a resolução de nomes por DNS. Isso nos deixou com uma questão fundamental: se nossos contêineres estão agora isolados do mundo exterior, como fazemos para que um usuário ou um serviço externo (como um navegador ou Postman) possa interagir com nossa aplicação?

Esta lição responde exatamente a essa pergunta. Nosso objetivo é aprender a publicar a porta de um contêiner na máquina hospedeira (host) usando a flag -p. Este mecanismo é a ponte entre a rede isolada do seu contêiner e a rede do seu host, tornando suas aplicações acessíveis. Ao final desta lição, você será capaz de executar uma API .NET em um contêiner e acessá-la a partir do seu navegador.

O Conceito de Mapeamento de Portas

Quando você executa um processo dentro de um contêiner, ele escuta em uma porta dentro do ambiente de rede isolado do contêiner. Por exemplo, um servidor web Nginx escuta por padrão na porta 80, e uma API ASP.NET Core 8 escuta na porta 8080. Essas portas não são acessíveis diretamente do seu localhost.

Para expor uma porta do contêiner, você precisa mapeá-la para uma porta na máquina host. A flag para isso é --publish ou sua abreviação -p.

A sintaxe é simples e crucial de memorizar:

docker run -p <PORTA_DO_HOST>:<PORTA_DO_CONTÊINER> <IMAGEM>

Pense nisso como um redirecionamento. Qualquer tráfego que chega na <PORTA_DO_HOST> será encaminhado pelo Docker para a <PORTA_DO_CONTÊINER> do contêiner específico.

Docker Port Mapping Explained
Este diagrama mostra o fluxo: uma requisição de um cliente (navegador, cliente de banco de dados) chega a uma porta específica no host Docker. O Docker então redireciona essa requisição para a porta correspondente dentro do contêiner correto.

Vamos a um exemplo prático. Rode o seguinte comando para iniciar um servidor web Nginx:

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

Aqui, estamos mapeando a porta 8080 do seu host para a porta 80 dentro do contêiner Nginx. Agora, abra seu navegador e acesse http://localhost:8080. Você verá a página de boas-vindas do Nginx!

Uma grande vantagem disso é que você pode rodar múltiplas instâncias do mesmo serviço, desde que as mapeie para portas diferentes no host. Tente rodar este segundo comando:

docker run -d --name webserver2 -p 8081:80 nginx

Agora, se você acessar http://localhost:8081, verá outra instância do Nginx. A porta dentro do contêiner é a mesma (80), mas no host elas estão em portas diferentes, evitando conflitos.


Como o Mapeamento de Portas Funciona "Por Debaixo dos Panos"?

Dado seu background em redes, você sabe que não há mágica. O Docker modifica a pilha de rede do host para realizar esse redirecionamento. Em sistemas Linux (que também é a base do Docker Desktop para Mac e Windows), isso é feito principalmente através da manipulação de regras de firewall com iptables.

Quando você usa a flag -p, o Docker adiciona uma regra na tabela nat (Network Address Translation). Especificamente, ele cria uma regra de DNAT (Destination NAT).

Docker Container Port Publishing Mechanism
Este diagrama ilustra o processo de DNAT. Uma requisição externa (1) chega à interface de rede do host na porta publicada (8080). A regra de iptables (2) reescreve o endereço IP de destino para o IP privado do contêiner (ex: 172.17.0.5) e a porta de destino para a porta interna do contêiner (80). O kernel do host então roteia o pacote modificado para o contêiner (3).

Essa automação é uma das grandes conveniências do Docker, abstraindo a complexidade da configuração de iptables em um simples comando.


Aplicação Prática: Publicando uma API .NET Core

Agora, vamos trazer isso para o seu dia a dia como desenvolvedor .NET. O processo é o mesmo, mas há um detalhe crucial sobre as versões recentes do .NET.

Um artigo da Medium, "Containerization of the .NET Core 7 Web API using Docker", demonstra o comando típico para uma aplicação .NET 7:

Containerization of the .NET Core 7 Web API using Docker

Este artigo mostra um exemplo completo de como containerizar uma API .NET 7. Vamos focar em como o autor executa o contêiner e publica a porta.

Leia apenas o 'Step 3' e sua explicação. Observe o comando docker run -d -p 5001:80 ... e a explicação de cada flag. Note que para uma aplicação .NET 7, a porta interna usada é a 80.

Como você viu, o comando docker run -p 5001:80 mapeia a porta 5001 do host para a porta 80 do contêiner. Isso funcionava perfeitamente para imagens baseadas em .NET 7.

Atenção: Mudança Importante no .NET 8

Com o .NET 8, a equipe da Microsoft fez uma alteração importante. Para melhorar a segurança (permitindo que os contêineres rodem como um usuário não-root por padrão), a porta padrão para aplicações ASP.NET Core dentro de contêineres mudou da porta privilegiada 80 para a porta não-privilegiada 8080.

Esta mudança pode pegar muitos desenvolvedores de surpresa. A documentação da Microsoft explica isso em detalhes.

Default ASP.NET Core port changed from 80 to 8080

Este documento oficial da Microsoft é uma leitura essencial. Ele detalha a mudança da porta padrão do ASP.NET Core no .NET 8, o motivo por trás dela e como adaptar seus comandos docker run.

Leia o artigo inteiro. Preste atenção especial na seção 'New behavior', que mostra o 'caso de falha' e o comando correto. Entenda também a 'Reason for change', pois ela se conecta a uma futura lição sobre segurança e o princípio do menor privilégio.

Resumindo a leitura: se você pegar uma imagem de API .NET 8 e tentar rodá-la com -p 8000:80, a conexão falhará. O comando correto é:

# Para uma API .NET 8+
docker run -d -p 8000:8080 minha-api-dotnet8

EXPOSE vs. -p

Você pode ver a instrução EXPOSE em um Dockerfile. Por exemplo, a imagem base do ASP.NET 8 tem EXPOSE 8080. É fundamental entender que EXPOSE não publica a porta. Ela funciona como documentação, um metadado que informa ao desenvolvedor qual porta a aplicação pretende usar. A publicação real e o mapeamento sempre acontecem em tempo de execução com a flag -p.

Test your understanding!

Você construiu uma imagem de uma API .NET 8 chamada minha-api:1.0. A aplicação dentro do contêiner escuta tráfego HTTP. Você precisa rodar duas instâncias desta API na sua máquina local para testes de integração. Uma deve estar acessível em http://localhost:9000 e a outra em http://localhost:9001. Escreva os dois comandos docker run necessários para realizar essa tarefa.

Show answer

Os comandos corretos seriam:

# Inicia a primeira instância na porta 9000 do host
docker run -d --name api1 -p 9000:8080 minha-api:1.0

# Inicia a segunda instância na porta 9001 do host
docker run -d --name api2 -p 9001:8080 minha-api:1.0

Ambos os comandos mapeiam para a porta 8080 dentro do contêiner (o padrão do .NET 8), mas usam portas diferentes no host (9000 e 9001) para evitar conflitos.


Conclusão

Parabéns! Você agora domina um dos conceitos mais fundamentais do Docker. Expor seus serviços para o mundo exterior é um passo essencial na conteinerização de qualquer aplicação.

Principais pontos a reter:

  • A flag -p (ou --publish) mapeia uma porta do host para uma porta do contêiner com a sintaxe -p <HOST_PORT>:<CONTAINER_PORT>.
  • Este mecanismo é o que torna as aplicações dentro de contêineres isolados acessíveis externamente.
  • Por baixo dos panos, o Docker usa regras de iptables (DNAT) para rotear o tráfego do host para o contêiner.
  • Para aplicações .NET 8 ou mais recentes, a porta padrão do contêiner mudou para 8080 por razões de segurança.
  • A instrução EXPOSE em um Dockerfile serve como documentação e não publica a porta; a publicação é feita em tempo de execução com -p.

Na última lição, estabelecemos a comunicação entre contêineres. Nesta, abrimos a comunicação do mundo exterior para os contêineres. A próxima peça do quebra-cabeça é: o que acontece com os dados? Se um contêiner de banco de dados for parado, todos os dados são perdidos. Na próxima lição, vamos resolver esse problema explorando volumes Docker e armazenamento persistente.