Poupe até 53% em Servidores VPS, escolha agora. Oferta limitada.

Otimizar Docker Compose: portas só para serviços internos

16 min de leitura  ·  Guia técnico

Otimizar Docker Compose para liberar portas só para serviços internos significa publicar serviços sensíveis com bind em 127.0.0.1 ou usar expose na rede interna, em vez de abrir portas em todas as interfaces do host. Assim, bancos de dados, caches e backends continuam acessíveis entre containers ou localmente no servidor, sem ficarem expostos à internet.

  1. Identifique quais serviços precisam de acesso externo e quais devem ficar internos.
  2. Troque PORTA:PORTA por 127.0.0.1:PORTA:PORTA nos serviços locais ao host.
  3. Use expose para comunicação entre containers na mesma rede Docker.
  4. Valide a stack com docker compose config e confira os binds com ss -tlnp.
  5. Recrie os containers com docker compose up -d e teste o acesso externo.

Pré-requisitos para otimizar o Docker Compose com portas restritas

  • Docker Engine instalado (versão 24 ou superior recomendada) e Docker Compose v2 disponível via docker compose (plugin integrado).
  • Acesso root ou usuário com permissão no grupo docker no servidor.
  • Um arquivo docker-compose.yml funcional com ao menos dois serviços definidos.
  • Conhecimento básico de redes Docker (bridge, host, overlay).
  • Servidor Linux — os exemplos foram testados no Debian 12 (Bookworm), Ubuntu 24.04 LTS e Rocky Linux 9, mas o conceito de otimizar o Docker Compose dessa forma também se aplica a versões mais novas em 2026.
  • Opcional: UFW ou nftables configurado para entender a interação com o Docker.

Por que o Docker Compose expõe portas para a internet por padrão

Quando você usa a diretiva ports no formato simples "8080:80", o daemon do Docker cria uma regra no iptables que vincula a porta 8080 a todas as interfaces de rede do host — incluindo a interface pública. Esse comportamento é independente do UFW: o Docker insere suas regras na chain DOCKER antes das chains gerenciadas pelo UFW, contornando qualquer bloqueio configurado nele.

O resultado prático é que um banco de dados MySQL rodando na porta 3306 com a diretiva "3306:3306" fica acessível para qualquer IP na internet, mesmo que você tenha uma regra ufw deny 3306 ativa. Esse é um dos erros de segurança mais comuns em stacks Docker em produção.

Para entender melhor como o Docker se integra a um servidor VPS Linux, consulte o artigo Dicas de Otimização de Servidores Linux, que cobre ajustes de kernel e rede relevantes para esse cenário.

Como otimizar o Docker Compose com bind de host 127.0.0.1

A forma mais direta de impedir que uma porta seja exposta publicamente é vincular o bind de host à interface de loopback. Isso garante que somente processos rodando no próprio servidor consigam acessar aquela porta — containers, serviços locais como Nginx atuando como proxy reverso, ou conexões SSH tuneladas.

Veja um exemplo completo de docker-compose.yml com uma aplicação web (Node.js), um banco de dados (MySQL 8.0) e um cache (Redis):

services:
  app:
    image: node:20-alpine
    ports:
      - "0.0.0.0:3000:3000"
    networks:
      - interna
    depends_on:
      - db
      - cache

  db:
    image: mysql:8.0
    ports:
      - "127.0.0.1:3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: senha_segura
      MYSQL_DATABASE: minha_app
    networks:
      - interna

  cache:
    image: redis:7-alpine
    ports:
      - "127.0.0.1:6379:6379"
    networks:
      - interna

networks:
  interna:
    driver: bridge

Neste exemplo:

  • O serviço app está acessível externamente na porta 3000 (bind em 0.0.0.0).
  • O MySQL está vinculado apenas ao loopback — acessível via 127.0.0.1:3306 no host, mas invisível para a internet.
  • O Redis segue o mesmo padrão: porta 6379 disponível apenas localmente.
  • Todos os serviços compartilham a rede interna interna, permitindo comunicação entre containers pelo nome do serviço.

Após salvar o arquivo, aplique a configuração:

docker compose up -d

Verifique quais portas estão abertas e em quais interfaces:

ss -tlnp | grep -E '3000|3306|6379'
LISTEN  0  128  0.0.0.0:3000   0.0.0.0:*   users:(("docker-proxy",pid=12345,fd=4))
LISTEN  0  128  127.0.0.1:3306 0.0.0.0:*   users:(("docker-proxy",pid=12346,fd=4))
LISTEN  0  128  127.0.0.1:6379 0.0.0.0:*   users:(("docker-proxy",pid=12347,fd=4))

O output confirma que 3306 e 6379 estão vinculados apenas ao loopback, enquanto 3000 está acessível em todas as interfaces.

Como usar expose para otimizar o Docker Compose internamente

A diretiva expose é a alternativa mais segura quando dois containers precisam se comunicar, mas nenhuma porta deve ser publicada no host. Diferente de ports, o expose apenas documenta e disponibiliza a porta dentro da rede Docker interna — sem criar nenhuma regra no iptables do host.

Exemplo de stack onde o PHP-FPM se comunica com o MySQL sem expor nada externamente:

services:
  php:
    image: php:8.3-fpm
    expose:
      - "9000"
    networks:
      - backend

  db:
    image: mariadb:11.4
    expose:
      - "3306"
    environment:
      MARIADB_ROOT_PASSWORD: senha_forte
      MARIADB_DATABASE: producao
    networks:
      - backend

  nginx:
    image: nginx:1.26-alpine
    ports:
      - "0.0.0.0:80:80"
      - "0.0.0.0:443:443"
    networks:
      - backend

networks:
  backend:
    driver: bridge

Nesta configuração, o Nginx recebe requisições externas nas portas 80 e 443, repassa para o PHP-FPM via socket na porta 9000, e o PHP-FPM acessa o MariaDB pelo hostname db na porta 3306 — tudo dentro da rede backend, sem nenhuma dessas portas internas aparecer no host.

Para verificar que nenhuma porta interna foi publicada:

docker compose ps
NAME        IMAGE              COMMAND                  SERVICE   STATUS    PORTS
php         php:8.3-fpm        "docker-php-entrypoi…"   php       running   9000/tcp
db          mariadb:11.4       "docker-entrypoint.s…"   db        running   3306/tcp
nginx       nginx:1.26-alpine  "/docker-entrypoint.…"   nginx     running   0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp

Observe que 9000/tcp e 3306/tcp aparecem sem o prefixo 0.0.0.0:, confirmando que são apenas portas internas da rede Docker.

Como liberar uma porta do Docker apenas para um IP específico

Em cenários onde um serviço precisa ser acessível por um IP externo específico — como um servidor de monitoramento ou um IP de escritório — o Docker Compose permite especificar o IP de destino diretamente na diretiva ports.

A sintaxe é: "IP_ESPECIFICO:PORTA_HOST:PORTA_CONTAINER"

services:
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "203.0.113.10:9090:9090"
    networks:
      - monitoramento

  grafana:
    image: grafana/grafana:latest
    ports:
      - "203.0.113.10:3000:3000"
    networks:
      - monitoramento

networks:
  monitoramento:
    driver: bridge

Atenção: o IP 203.0.113.10 no exemplo acima é o IP do host de destino (a interface do servidor que receberá as conexões), não o IP de origem do cliente. Para filtrar por IP de origem, você ainda precisará de regras adicionais no firewall. Essa configuração restringe em qual interface do servidor a porta será vinculada.

Valide a configuração antes de aplicar:

docker compose config

O comando exibe o arquivo docker-compose.yml após interpolação de variáveis e validação de sintaxe, permitindo revisar tudo antes de subir os containers.

Usando redes Docker isoladas para segmentar serviços

Uma estratégia avançada de isolamento de portas no Docker Compose é criar múltiplas redes com propósitos distintos: uma rede pública para os serviços que precisam de acesso externo e uma rede privada para a comunicação entre backends. Containers em redes diferentes não conseguem se comunicar diretamente, mesmo que estejam na mesma stack.

services:
  nginx:
    image: nginx:1.26-alpine
    ports:
      - "0.0.0.0:80:80"
      - "0.0.0.0:443:443"
    networks:
      - publica
      - privada

  app:
    image: python:3.12-slim
    expose:
      - "8000"
    networks:
      - privada

  db:
    image: postgres:16-alpine
    expose:
      - "5432"
    networks:
      - privada

networks:
  publica:
    driver: bridge
  privada:
    driver: bridge
    internal: true

O parâmetro internal: true na rede privada é crucial: ele impede que containers nessa rede façam requisições para fora do host, criando um isolamento bidirecional. O Nginx está em ambas as redes — recebe tráfego externo pela rede publica e repassa para a aplicação pela rede privada. O banco de dados PostgreSQL está apenas na rede privada e não tem nenhuma rota para a internet.

Para listar as redes criadas e verificar quais containers estão em cada uma:

docker network ls
docker network inspect nome_da_stack_privada

Problemas comuns e como resolver

Sintoma: porta ainda aparece acessível externamente após configurar 127.0.0.1

Causa: O container foi iniciado antes da alteração no docker-compose.yml, ou há um container antigo ainda rodando com a configuração anterior.
Solução: Pare completamente a stack e suba novamente, forçando a recriação dos containers:

docker compose down
docker compose up -d

Após reiniciar, confirme com ss -tlnp que o bind está correto. Se o problema persistir, verifique se há containers órfãos com docker ps -a e remova-os com docker rm -f CONTAINER_ID.

Sintoma: containers na mesma rede não conseguem se comunicar pelo nome do serviço

Causa: Os containers estão em redes diferentes, ou a resolução DNS interna do Docker não está funcionando corretamente. Isso ocorre com frequência quando se usa a rede padrão default sem definir redes explícitas.
Solução: Defina redes explícitas no docker-compose.yml e certifique-se de que ambos os serviços estão na mesma rede. Teste a resolução de nomes de dentro de um container:

docker compose exec app ping db

Se o ping falhar, verifique com docker network inspect se ambos os containers aparecem na mesma rede.

Sintoma: UFW bloqueou a porta, mas ela ainda está acessível externamente

Causa: O Docker manipula o iptables diretamente e insere regras na chain DOCKER, que tem precedência sobre as regras do UFW. Isso é um comportamento documentado do Docker e não um bug.
Solução: Não confie apenas no UFW para bloquear portas publicadas pelo Docker. Use o bind de host 127.0.0.1 no docker-compose.yml como primeira linha de defesa. Se precisar de controle granular via firewall, configure as regras diretamente no iptables na chain DOCKER-USER, que é respeitada pelo Docker:

iptables -I DOCKER-USER -p tcp --dport 3306 -j DROP
iptables -I DOCKER-USER -s 203.0.113.10 -p tcp --dport 3306 -j ACCEPT

Atenção: regras inseridas diretamente no iptables não persistem após reboot sem configuração adicional. Use o pacote iptables-persistent no Debian/Ubuntu para salvar as regras.

Sintoma: erro "bind: address already in use" ao subir a stack

Causa: Outro processo no host já está usando a porta especificada — pode ser um container anterior, um serviço do sistema (como MySQL nativo) ou outra instância do Docker Compose.
Solução: Identifique o processo que está usando a porta:

ss -tlnp | grep 3306

Se for um serviço do sistema, pare-o antes de subir o container: systemctl stop mysql. Se for outro container, remova-o com docker rm -f CONTAINER_ID.

Perguntas frequentes sobre Docker Compose e portas internas

Como impedir que o Docker Compose exponha uma porta para a internet?

No arquivo docker-compose.yml, use a sintaxe longa de ports com o bind de host 127.0.0.1:PORTA:PORTA em vez de apenas PORTA:PORTA. Isso faz o daemon do Docker vincular a porta somente à interface de loopback, tornando-a inacessível externamente mesmo que o firewall UFW esteja ativo. Essa é a abordagem mais confiável e não depende de regras de firewall externas para funcionar.

Qual a diferença entre ports e expose no Docker Compose?

A diretiva ports publica a porta no host, tornando-a acessível de fora do container — e potencialmente da internet, dependendo do bind configurado. A diretiva expose apenas documenta e disponibiliza a porta entre containers na mesma rede Docker interna, sem publicar nada no host. Para comunicação exclusivamente entre serviços da mesma stack, expose é a opção mais segura e semanticamente correta.

O Docker Compose ignora as regras do UFW?

Sim, por padrão o Docker manipula diretamente as regras do iptables e pode contornar o UFW ao publicar portas. O Docker insere suas regras na chain DOCKER, que tem precedência sobre as chains gerenciadas pelo UFW. Por isso, vincular portas a 127.0.0.1 no docker-compose.yml é a forma mais confiável de restringir o acesso, independentemente das regras do UFW. Para controle via firewall, use a chain DOCKER-USER.

Como dois containers se comunicam sem expor portas para o host?

Basta colocar ambos os containers na mesma rede Docker definida no docker-compose.yml e usar o nome do serviço como hostname. Por exemplo, um container PHP pode acessar o MySQL pelo hostname db na porta 3306 sem que essa porta seja publicada no host. O Docker possui um servidor DNS interno que resolve automaticamente os nomes de serviço para os IPs dos containers na mesma rede.

É possível liberar uma porta do Docker apenas para um IP específico?

Sim. Na diretiva ports do docker-compose.yml, use o formato IP_ESPECIFICO:PORTA_HOST:PORTA_CONTAINER, por exemplo 203.0.113.10:8080:80. Isso vincula a porta à interface do host que possui aquele IP, restringindo em qual interface as conexões serão aceitas. Para filtrar por IP de origem do cliente, combine essa configuração com regras na chain DOCKER-USER do iptables.

Conclusão

Controlar quais portas o Docker Compose expõe é uma das práticas de segurança mais importantes para stacks em produção. Ao aplicar as técnicas descritas neste guia para otimizar o Docker Compose, você garante que apenas os serviços que realmente precisam de acesso externo fiquem visíveis na internet.

  • Use 127.0.0.1:PORTA:PORTA para qualquer serviço que deva ser acessível apenas localmente no host (bancos de dados, caches, filas de mensagens).
  • Prefira expose a ports para comunicação entre containers na mesma stack — é mais seguro e não cria regras no iptables do host.
  • Crie redes Docker separadas com internal: true para isolar completamente backends sensíveis, impedindo que containers façam conexões de saída para a internet.

Para aprofundar o conhecimento em segurança de servidores Linux com Docker, veja também o artigo Acessando servidores VPS Linux da AviraHost, que cobre boas práticas de acesso SSH ao ambiente onde sua stack Docker roda.

Leia também

Precisa de ajuda com Docker e infraestrutura de containers?

Configurar stacks Docker com segurança exige um servidor com recursos adequados e suporte técnico disponível quando você precisar. Na AviraHost, os planos de VPS Linux oferecem acesso root completo, IPs dedicados e suporte para você rodar suas aplicações em containers com controle total sobre a rede e as portas expostas.

Conheça os planos de VPS Linux da AviraHost

  • 0 Os usuários acharam isso útil
  • Docker Compose, containers, redes Docker, segurança, AviraHost
Esta resposta foi útil?

Artigos Relacionados

Instalando painel de gerenciamento de hospedagem VirtualMin.

O virtualmin é um painel de gerenciamento de hospedagem de sites gratuito, que é suportado por...

Como usar a ferramenta oficial de acesso remoto do Windows no PC e celular

1. Pelo menu Iniciar, acesse os “Acessórios do Windows” e abra o “Conexão de Área de Trabalho...

Como acessar o painel de gerenciamento dos meus Serviços.

Para acessar o painel de gerenciamento do seu serviço basta seguir o passo á passo abaixo.   1....

Compreendendo o Servidor VPS: O que é e Como Funciona!

Um servidor VPS (Virtual Private Server) é uma solução de hospedagem na qual um servidor físico é...

Como trocar a senha do usuário root do servidor VPS ou Dedicado.

Para trocar a senha do usuário root em um servidor VPS da AviraHost, você pode seguir os...