Programa
Ao trabalhar com contêineres do Docker, um dos aspectos mais cruciais que encontrei é permitir a comunicação de rede entre os contêineres e o mundo externo. Sem a configuração adequada da porta, os aplicativos em contêineres permanecem isolados e inacessíveis. Neste artigo, mostrarei a você o processo de exposição das portas do Docker, uma habilidade fundamental para quem trabalha com aplicativos em contêineres.
Este guia foi criado especificamente para desenvolvedores de software, engenheiros de DevOps e profissionais de TI que precisam tornar seus aplicativos em contêineres acessíveis a usuários, outros serviços ou sistemas externos. Se você estiver implantando um servidor da Web, uma API ou um banco de dados em um contêiner, entender a exposição da porta é essencial para criar sistemas funcionais baseados no Docker.
Ao final deste artigo, você terá uma compreensão clara de como funciona a rede do Docker, os diferentes métodos de exposição de portas e as técnicas práticas para implementar esses conceitos em seus próprios projetos.
Se você é novo no Docker, considere fazer um de nossos cursos, como Introdução ao Docker, Containerização e Virtualização com Docker e Kubernetes, Docker Intermediário ou Conceitos de Containerização e Virtualização.
O que é expor uma porta no Docker?
Antes de mergulhar nos detalhes técnicos, é importante que você entenda o que significa exposição de porta no ecossistema do Docker.
Princípios de isolamento da rede de contêineres
Os contêineres do Docker são projetados tendo o isolamento como um princípio fundamental. Esse isolamento é obtido por meio de recursos do kernel do Linux, como namespaces e cgroups. Os namespaces fornecem isolamento de processos, garantindo que os processos em um contêiner não possam ver ou interagir com processos em outros contêineres ou no sistema host. Os Cgroups, por outro lado, controlam a alocação de recursos, limitando a quantidade de CPU, memória e outros recursos que um contêiner pode consumir.
Do ponto de vista da rede, cada contêiner recebe seu próprio namespace de rede, completo com uma interface Ethernet virtual (par veth). Essa interface se conecta a uma rede de ponte do Docker por padrão, permitindo a comunicação entre contêineres e mantendo o isolamento da rede do host. Pense nisso como se cada contêiner tivesse seu próprio endereço de rede privado, invisível para o mundo externo.
Em sua configuração padrão, um contêiner do Docker é totalmente protegido por sandbox. Os serviços executados dentro do contêiner podem se comunicar entre si, mas não podem ser acessados de fora do contêiner, nem mesmo da máquina host. É nesse ponto que a exposição à porta se torna necessária.
Expor vs. publicar: distinção semântica
Ao trabalhar com a configuração de portas do Docker, você encontrará dois conceitos relacionados, mas distintos: expor e publicar portas.
A exposição das portas é principalmente um recurso de documentação. Quando você expõe uma porta no Docker, está basicamente adicionando metadados à sua imagem de contêiner, indicando que o aplicativo em contêiner escuta em portas específicas.
No entanto, a exposição de uma porta não a torna realmente acessível de fora do contêiner. Ele serve como documentação para os usuários da sua imagem.
A publicação de portas é o que realmente torna seus serviços em contêineres disponíveis para o mundo externo. A publicação cria um mapeamento entre uma porta na máquina host e uma porta dentro do contêiner. Quando você publica uma porta, o Docker configura a rede do host para encaminhar o tráfego da porta do host especificada para a porta do contêiner correspondente.
Veja a seguir quando você deve usar cada abordagem:
- Use expor quando você estiver criando imagens destinadas a serem usadas por outros usuários do Docker, para documentar quais portas o seu aplicativo usa.
- Usar publicar quando você precisar que sistemas externos (inclusive o host) acessem os serviços em execução no seu contêiner.
- Use os dois juntos para obter documentação e funcionalidade completas, especialmente em ambientes de produção.
Como expor as portas do Docker
Agora que entendemos os conceitos, vamos examinar os aspectos práticos da exposição das portas do Docker em diferentes contextos.
Estratégias de declaração de Dockerfile
A maneira mais básica de expor uma porta é usar a instrução EXPOSE
em seu Dockerfile. Isso declara as portas que o contêiner foi projetado para usar.
FROM my-image:latest
EXPOSE 80
EXPOSE 443
Neste exemplo, especifiquei que o contêiner usará as portas 80
e 443
, que são padrão para o tráfego HTTP (Hypertext Transfer Protocol) e HTTPS (Hypertext Transfer Protocol Secure). Você também pode combiná-los em uma única instrução:
EXPOSE 80 443
Ao especificar as portas, é recomendável incluir o protocolo, TCP (Transmission Control Protocol) ou UDP (User Datagram Protocol), se o seu aplicativo usar um protocolo específico:
EXPOSE 53/udp
EXPOSE 80/tcp
Se nenhum protocolo for especificado, o TCP será assumido por padrão. Isso é adequado para a maioria dos aplicativos da Web, mas serviços como DNS (Domain Name System) ou servidores de jogos geralmente exigem UDP.
Do ponto de vista da segurança, recomendo que você exponha apenas as portas de que seu aplicativo realmente precisa. Cada porta exposta representa um possível vetor de ataque, portanto, a minimização do número de portas expostas segue o princípio do menor privilégio.
Gerenciamento de portas em tempo de execução
Embora a instrução EXPOSE
documente quais portas um contêiner usa, para tornar essas portas acessíveis a partir do host ou de outras máquinas, você precisa publicá-las ao executar o contêiner.
Veja como vincular explicitamente uma porta de contêiner a uma porta de host específica:
docker run -p 8081:80 my-image
O sinalizador -p
(ou --publish
) permite que você vincule uma única porta ou um intervalo de portas no contêiner ao host.
Esse comando mapeia a porta 80 dentro do contêiner para a porta 8081 no host. Depois de executar esse comando, você pode acessar o servidor da Web navegando para http://localhost:8081
no navegador.
Para maior conveniência durante o desenvolvimento, o Docker oferece o sinalizador -P
, P maiúsculo ou --publish-all
, que publica automaticamente todas as portas expostas em portas aleatórias com números altos no host:
docker run -P my-image
Para saber quais portas de host foram atribuídas, posso usar o comando docker ps
:
docker ps
Isso mostrará resultados como:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a7c53d9413bf image "/docker-.…" 10 s ago Up 9 s 0.0.0.0:49153->80/tcp, :::49153->80/tcp wizardl
Aqui, a porta 80 dentro do contêiner é mapeada para a porta 49153 no host.
Ao trabalhar com serviços que usam TCP e UDP na mesma porta (como servidores DNS), você precisa especificar o protocolo ao publicar:
docker run -p 53:53/tcp -p 53:53/udp dns-server
Orquestração do Docker Compose
Para aplicativos com vários contêineres, o Docker Compose oferece uma maneira mais gerenciável de configurar redes e portas.
Em um arquivo YAML do Docker Compose, você pode especificar mapeamentos de porta na chave ports
para cada serviço:
services:
web:
image: my-image
ports:
- "8081:80"
- "443:443"
api:
build: ./api
ports:
- "3000:3000"
O Docker Compose faz uma distinção importante entre ports
e expose
. A seção ports
cria mapeamentos de portas publicadas acessíveis de fora, enquanto expose
só disponibiliza portas para serviços vinculados dentro da mesma rede do Compose:
services:
web:
image: my-image
ports:
- "8081:80"
database:
image: postgres
expose:
- "5432"
Neste exemplo, o serviço web
pode ser acessado pelo host na porta 8081, mas o banco de dados PostgreSQL só pode ser acessado por outros serviços dentro do arquivo Compose, não diretamente pelo host.
Para configurações flexíveis, especialmente em ambientes diferentes, posso usar variáveis de ambiente em mapeamentos de portas:
services:
web:
image: my-image
ports:
- "${WEB_PORT:-8081}:80"
Essa sintaxe permite que eu especifique a porta do host por meio de uma variável de ambiente (WEB_PORT
), que tem como padrão 8081 se não for definida.
Expondo portas do Docker - exemplo prático
Vamos examinar um exemplo completo de exposição de portas do Docker para um aplicativo da Web com um backend de banco de dados.
Imagine que estamos criando um aplicativo da Web simples que consiste em um serviço da API Python Flask e um banco de dados PostgreSQL. O serviço de API escuta na porta 5000
, e o PostgreSQL usa sua porta padrão 5432
.
Aqui está a aparência do nosso aplicativo Flask simples (app.py
). Esse aplicativo criará um banco de dados myapp
e buscará os dados da tabela items
, assim que essa tabela for criada no banco de dados.
from flask import Flask, jsonify
import os
import psycopg2
app = Flask(__name__)
# Database connection parameters from environment variables
DB_HOST = os.environ.get('DB_HOST', 'db')
DB_NAME = os.environ.get('DB_NAME', 'myapp')
DB_USER = os.environ.get('DB_USER', 'postgres')
DB_PASS = os.environ.get('DB_PASSWORD', 'postgres')
@app.route('/')
def index():
return jsonify({'message': 'API is running'})
@app.route('/items')
def get_items():
# Connect to the PostgreSQL database
conn = psycopg2.connect(
host=DB_HOST,
database=DB_NAME,
user=DB_USER,
password=DB_PASS
)
# Create a cursor and execute a query
cur = conn.cursor()
cur.execute('SELECT id, name FROM items')
# Fetch results and format as list of dictionaries
items = [{'id': row[0], 'name': row[1]} for row in cur.fetchall()]
# Close connections
cur.close()
conn.close()
return jsonify(items)
if __name__ == '__main__':
# Run the Flask app, binding to all interfaces (important for Docker)
app.run(host='0.0.0.0', port=5000)
Em seguida, criarei nosso arquivo requirements.txt
:
flask
psycopg2-binary
Agora, preciso criar um Dockerfile para nosso serviço de API Python:
FROM python:3.9-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Expose the port Flask runs on
EXPOSE 5000
# Command to run the application
CMD ["python", "app.py"]
Por fim, criarei um arquivo Docker Compose para orquestrar os dois serviços:
services:
api:
build:
context: .
dockerfile: Dockerfile
tags:
- "my-custom-image:latest"
container_name: my-custom-api
ports:
- "5000:5000"
environment:
- DB_HOST=db
- DB_NAME=myapp
- DB_USER=postgres
- DB_PASSWORD=postgres
depends_on:
- db
db:
container_name: my-custom-db
image: postgres:13
expose:
- "5432"
environment:
- POSTGRES_DB=myapp
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Nessa configuração:
- O serviço de API é criado a partir do nosso Dockerfile e publica a porta
5000
, tornando-o acessível a partir do computador host. - O serviço PostgreSQL expõe a porta
5432
, mas não a publica, tornando-a acessível somente a outros serviços da rede Compose. - O serviço de API pode acessar o banco de dados usando o nome do host db (que é o nome do serviço) e a porta
5432
.
Para executar esse aplicativo, use:
docker-compose up
Agora, posso acessar a API em http://localhost:5000
, mas a instância do PostgreSQL não pode ser acessada diretamente de fora da rede do Docker, o que é uma boa prática de segurança para serviços de banco de dados. Mas você pode acessar o PostgreSQL a partir do contêiner em execução:
docker compose exec db psql -U postgres -d myapp
Esse comando acessará a CLI do PostgreSQL, onde você poderá visualizar o banco de dados e criar uma tabela items
. Uma vez criado, posso visualizar os dados em http://localhost:5000/items
Como expor várias portas do Docker
Se meu aplicativo Python precisar expor várias portas, posso especificar todas elas no Dockerfile:
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000 8081 9090
CMD ["python", "app.py"]
Esse Dockerfile expõe:
- Porta
5000
para o aplicativo principal do Flask (atualmente ativo) - Porta
8081
para um possível painel de monitoramento - Porta
9090
para uma futura interface de depuração
É importante observar que, embora eu tenha exposto as portas 8081
e 9090
no Dockerfile como documentação, meu aplicativo Flask atual só está configurado para escutar na porta 5000
. As portas adicionais expostas indicam onde a funcionalidade futura pode ser implementada.
Como publicar portas expostas
Para publicar portas que estão expostas em um Dockerfile, uso o sinalizador -p
ou -P
ao executar o contêiner:
docker run -p 8081:5000 my-custom-image
Isso mapeia a porta 5000
no contêiner (que é exposta no Dockerfile e onde o Flask está escutando) para a porta 8081
no host. Depois de executar esse comando, posso acessar meu aplicativo Flask navegando para http://localhost:8081
no meu navegador.
Também posso usar o sinalizador -P para publicar automaticamente todas as portas expostas em portas aleatórias no host:
docker run -P my-custom-image
Para verificar quais portas estão publicadas, posso usar:
docker ps
Para obter informações mais detalhadas, você pode usar:
docker port [container_id]
5000/tcp -> 0.0.0.0:32768
8081/tcp -> 0.0.0.0:32769
9090/tcp -> 0.0.0.0:32770
Esse comando mostra todos os mapeamentos de porta para um contêiner específico. Por exemplo, se meu aplicativo Flask estiver escutando apenas na porta 5000
, mas eu tiver exposto várias portas no Dockerfile, o Docker port mostrará exatamente quais portas do host estão mapeadas para quais portas do contêiner. Portanto, se a porta 5000
tiver sido mapeada para 32768, poderei acessar meu aplicativo Flask em http://localhost:32768
Técnicas de diagnóstico e solução de problemas
Mesmo com uma configuração cuidadosa, podem surgir problemas com o mapeamento de portas do Docker. Aqui estão algumas técnicas que uso para diagnosticar e resolver problemas comuns.
Inspeção de mapeamento de portas
A primeira etapa do diagnóstico de problemas relacionados a portas é verificar se os mapeamentos estão configurados corretamente. Eu uso esses comandos:
Liste todos os contêineres em execução com mapeamentos de porta:
docker ps
Obtenha informações detalhadas sobre um contêiner específico:
docker inspect [container_id]
Verifique os mapeamentos de porta para um contêiner específico:
docker port [container_id]
O comando docker inspect fornece informações detalhadas, incluindo configurações de rede. Para se concentrar em detalhes relacionados à rede, use:
docker inspect --format='{{json .NetworkSettings.Ports}}' [container_id] | jq
Se você precisar fazer uma análise mais profunda do tráfego, ferramentas como tcpdump
e netstat
podem ser muito úteis. Primeiro, verifique se eles estão instalados dentro do contêiner. Se o contêiner ainda não incluir essas ferramentas, você poderá instalá-las executando o seguinte comando:
docker exec -u root -it f5c6cac71492 bash -c "apt-get update && apt-get install -y net-tools tcpdump"
Para ver em quais portas o aplicativo está escutando dentro do contêiner:
docker exec -it [container_id] netstat -tuln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:5000 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.11:42265 0.0.0.0:* LISTEN
udp 0 0 127.0.0.11:34267 0.0.0.0:*
Isso mostra, por exemplo, que o aplicativo Flask está escutando em todas as interfaces na porta 5000
.
Para monitorar o tráfego de entrada e saída na interface de rede principal do contêiner (geralmente eth0):
docker exec -it [container_id] tcpdump -i eth0
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
Esse comando permite que você rastreie o tráfego e identifique se as solicitações estão chegando ao contêiner ou se os pacotes estão sendo descartados ou mal roteados.
Análise de regras do Iptables
O Docker utiliza o site iptables
para gerenciar o Network Address Translation (NAT) e controlar o fluxo de tráfego entre o host e os contêineres. Ele insere automaticamente regras para encaminhar portas de host para os IPs e portas de contêineres apropriados, permitindo o acesso contínuo a serviços em contêineres.
Em hosts Linux nativos, você pode inspecionar essas regras usando comandos como:
sudo iptables -t nat -L DOCKER -n -v
Isso mostra como o Docker mapeia o tráfego de entrada em portas de host específicas para endpoints de contêineres. Com base no arquivo Docker Compose de antes, você verá as portas encaminhadas:
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
50 3000 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
100 6000 RETURN all -- br-xxxxxxx * 0.0.0.0/0 0.0.0.0/0
500 30000 DNAT tcp -- !br-xxxxxxx * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5000 to:172.18.0.2:5000
400 24000 DNAT tcp -- !br-xxxxxxx * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8081 to:172.18.0.2:5000
300 18000 DNAT tcp -- !br-xxxxxxx * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9090 to:172.18.0.2:5000
Aqui:
DNAT
redirecionam o tráfego do host para o contêiner.RETURN
garantem o roteamento adequado do tráfego interno da rede do Docker.
No entanto, dependendo do seu ambiente (por exemplo, Docker Desktop no Windows ou WSL2), o Docker pode lidar com o encaminhamento de porta de forma diferente e as regras do iptables
podem não ser visíveis ou modificáveis de dentro do sistema. Nesse caso, você pode verificar o encaminhamento de porta com o seguinte comando:
sudo ss -tulnp
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
tcp LISTEN 0 4096 *:5000 *:*
tcp LISTEN 0 4096 *:8081 *:*
tcp LISTEN 0 4096 *:9090 *:*
Dicas de solução de problemas:
- Confirme os mapeamentos de porta com
docker ps
edocker port [container]
. - Verifique se o firewall do sistema host ou o software de segurança está bloqueando as portas.
- Use logs de contêineres e ferramentas de rede como
netstat
etcpdump
para verificar o comportamento da rede do seu aplicativo.
Conclusão
A exposição das portas do Docker é uma habilidade fundamental que preenche a lacuna entre o isolamento em contêineres e a usabilidade prática. Ao longo deste artigo, expliquei como funciona o modelo de rede do Docker, a diferença entre expor e publicar portas e forneci exemplos práticos para vários cenários.
Tenha em mente estes pontos-chave:
- Use
EXPOSE
em Dockerfiles para documentar quais portas seu aplicativo usa - Use
-p
ou-P
ao executar contêineres para tornar essas portas acessíveis de fora - Aproveite o Docker Compose para gerenciar aplicativos complexos com vários contêineres
- Usar ferramentas de diagnóstico para solucionar problemas quando eles surgirem
O domínio desses conceitos e técnicas permitirá que você crie aplicativos em contêineres mais robustos que se comuniquem de forma eficaz tanto internamente quanto com o mundo externo. Não importa se você está desenvolvendo um aplicativo da Web simples ou uma arquitetura de microsserviços complexa, o gerenciamento adequado de portas é essencial para o sucesso.
Como os contêineres continuam a dominar as estratégias modernas de implantação de aplicativos, a capacidade de gerenciar com confiança as redes e portas do Docker continuará sendo uma habilidade essencial no kit de ferramentas de todos os desenvolvedores.
Para continuar aprendendo, não deixe de conferir os seguintes recursos:
Perguntas frequentes sobre a exposição de uma porta do Docker
O que significa expor uma porta no Docker?
Expor uma porta no Docker é uma forma de documentar em quais portas o seu aplicativo em contêiner escuta, mas isso não torna a porta acessível fora do contêiner.
Como faço para tornar a porta de um contêiner do Docker acessível a partir da minha máquina host?
Você publica uma porta usando o sinalizador -p
com docker run
ou a seção ports
no Docker Compose, que mapeia uma porta de host para a porta do contêiner.
Qual é a diferença entre `EXPOSE` e `-p` no Docker?
EXPOSE
é usado no Dockerfile para declarar portas para documentação, enquanto o -p
publica e mapeia essas portas para acesso externo.
Posso expor várias portas para um único contêiner do Docker?
Sim, você pode expor várias portas usando várias instruções EXPOSE
no Dockerfile ou especificando várias portas na seção ports
do Docker Compose.
Como faço para solucionar problemas de mapeamento de portas do Docker?
Use comandos como docker ps
, docker port
, docker inspect
e ferramentas de rede dentro do contêiner (por exemplo, netstat
, tcpdump
) para verificar o mapeamento de portas e o fluxo de tráfego.
Como fundador da Martin Data Solutions e cientista de dados freelancer, engenheiro de ML e IA, tenho um portfólio diversificado em regressão, classificação, PNL, LLM, RAG, redes neurais, métodos de conjunto e visão computacional.
- Desenvolveu com sucesso vários projetos de ML de ponta a ponta, incluindo limpeza de dados, análise, modelagem e implantação no AWS e no GCP, fornecendo soluções impactantes e dimensionáveis.
- Criou aplicativos da Web interativos e dimensionáveis usando Streamlit e Gradio para diversos casos de uso do setor.
- Ensinou e orientou alunos em ciência e análise de dados, promovendo seu crescimento profissional por meio de abordagens de aprendizagem personalizadas.
- Projetou o conteúdo do curso para aplicativos RAG (retrieval-augmented generation) adaptados aos requisitos da empresa.
- Criou blogs técnicos de IA e ML de alto impacto, abordando tópicos como MLOps, bancos de dados vetoriais e LLMs, obtendo um envolvimento significativo.
Em cada projeto que assumo, certifico-me de aplicar práticas atualizadas em engenharia de software e DevOps, como CI/CD, code linting, formatação, monitoramento de modelos, rastreamento de experimentos e tratamento robusto de erros. Tenho o compromisso de fornecer soluções completas, transformando insights de dados em estratégias práticas que ajudam as empresas a crescer e tirar o máximo proveito da ciência de dados, do machine learning e da IA.