Nginx com Docker Compose como gateway

Quando você roda múltiplos serviços com Docker Compose, o Nginx funciona como gateway, recebe todas as requisições na porta 80/443 e roteia para o container correto pela rede interna do Docker. Elimina a necessidade de expor portas individuais de cada serviço e centraliza SSL.

Nginx como serviço no Docker Compose

Estrutura completa com Nginx, app e banco de dados:

yaml
# docker-compose.yml
services:
  nginx:
    image: nginx:alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro
    depends_on:
      - app
      - api
    networks:
      - frontend

  app:
    build: ./app
    restart: always
    environment:
      DATABASE_URL: postgresql://user:pass@postgres:5432/db
    depends_on:
      - postgres
    networks:
      - frontend
      - backend
    # NÃO expor porta — apenas Nginx acessa internamente

  api:
    build: ./api
    restart: always
    environment:
      DATABASE_URL: postgresql://user:pass@postgres:5432/db
      REDIS_URL: redis://redis:6379
    networks:
      - frontend
      - backend

  postgres:
    image: postgres:17-alpine
    restart: always
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - backend   # apenas rede interna — não acessível pelo Nginx

  redis:
    image: redis:7-alpine
    restart: always
    networks:
      - backend

networks:
  frontend:    # Nginx ↔ app/api
  backend:     # app/api ↔ banco/redis

volumes:
  postgres_data:

Configuração Nginx para rotear por domínio

Arquivo de configuração Nginx que roteia para os containers corretos:

nginx
# nginx/conf.d/default.conf

# Redirecionar HTTP → HTTPS
server {
    listen 80;
    server_name _;
    return 301 https://$host$request_uri;
}

# Frontend (app)
server {
    listen 443 ssl http2;
    server_name seudominio.com.br www.seudominio.com.br;

    ssl_certificate     /etc/letsencrypt/live/seudominio.com.br/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/seudominio.com.br/privkey.pem;

    location / {
        # "app" é o nome do serviço no docker-compose.yml
        # Docker resolve automaticamente para o IP do container
        proxy_pass http://app:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
    }
}

# API
server {
    listen 443 ssl http2;
    server_name api.seudominio.com.br;

    ssl_certificate     /etc/letsencrypt/live/api.seudominio.com.br/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.seudominio.com.br/privkey.pem;

    location / {
        proxy_pass http://api:4000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
    }
}

Renovar certificados Let's Encrypt com Nginx em Docker

Como usar Certbot junto com Nginx containerizado:

yaml
# Opção 1: Certbot na máquina host (fora do Docker)
# O Let's Encrypt valida via porta 80 (challenge HTTP)

# Adicionar volume do Let's Encrypt ao compose:
# nginx: volumes: - /etc/letsencrypt:/etc/letsencrypt:ro

# Emitir certificado com Nginx parado temporariamente:
sudo systemctl stop nginx || true          # parar Nginx do host se existir
sudo certbot certonly --standalone -d seudominio.com.br
docker compose restart nginx               # reiniciar Nginx do Docker

# Opção 2: Certbot como container no compose
services:
  certbot:
    image: certbot/certbot
    volumes:
      - /etc/letsencrypt:/etc/letsencrypt
      - /var/www/certbot:/var/www/certbot
    command: certonly --webroot -w /var/www/certbot -d seudominio.com.br --non-interactive --agree-tos --email admin@seudominio.com.br
    profiles: ["certbot"]   # rodar manualmente: docker compose --profile certbot run certbot

# No nginx.conf — servir o webroot challenge:
location /.well-known/acme-challenge/ {
    root /var/www/certbot;
}

Traefik: alternativa ao Nginx para Docker

Traefik detecta containers automaticamente e configura rotas via labels:

yaml
# docker-compose.yml com Traefik
services:
  traefik:
    image: traefik:v3
    restart: always
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@seudominio.com.br"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik_data:/letsencrypt

  app:
    build: ./app
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host('seudominio.com.br')"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls.certresolver=letsencrypt"
      - "traefik.http.services.app.loadbalancer.server.port=3000"

volumes:
  traefik_data:

Escalar serviços com --scale e load balancing automático

Docker Compose com Nginx faz load balancing entre múltiplas réplicas:

yaml
# docker-compose.yml — sem port mapping fixo para escalar
services:
  app:
    build: ./app
    restart: always
    # SEM "ports:" — Nginx acessa pela rede interna
    environment:
      DATABASE_URL: postgresql://user:pass@postgres:5432/db
    networks:
      - frontend
      - backend

# Subir 3 réplicas do app:
docker compose up -d --scale app=3

# O Docker Compose cria um DNS interno que resolve "app" para todos os IPs
# O Nginx com upstream faz round-robin automaticamente:

# nginx/conf.d/app.conf:
upstream app_cluster {
    server app:3000;    # DNS round-robin automático do Docker
    keepalive 32;
}

server {
    location / {
        proxy_pass http://app_cluster;
    }
}

# Verificar os containers rodando:
docker compose ps
# app-1, app-2, app-3 todas UP

Não quer configurar manualmente?

Implante o VPS em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

Nginx no host: mais simples para começar, acesso direto ao sistema de arquivos e certificados, não precisa gerenciar um container a mais. Nginx no Docker: isolamento completo, configuração como código (versionada no repositório), ambiente consistente entre dev/prod. Para VPS com múltiplos serviços Docker: Nginx no Docker faz mais sentido. Para VPS com apps mistas (Docker + non-Docker): Nginx no host é mais flexível.

Separação de redes é defesa em profundidade: mesmo que o Nginx seja comprometido, ele não consegue acessar o banco diretamente. A aplicação está nas duas redes (frontend + backend) porque precisa receber tráfego do Nginx E acessar o banco. O banco só precisa ser acessado pela aplicação, nunca pela internet ou pelo gateway.

Traefik: configuração zero via labels Docker, SSL automático com Let's Encrypt sem configuração extra, dashboard built-in, detecta containers dinamicamente. Nginx: mais maduro e flexível, melhor performance e documentação mais ampla, mais controle. Para projetos puramente Docker com muitos serviços: Traefik é mais produtivo. Para configurações avançadas ou ambientes mistos: Nginx.

docker compose exec nginx nslookup app, deve retornar o IP do container da aplicação. Se falhar: verifique que ambos os serviços estão na mesma network no docker-compose.yml. O Docker tem um DNS embutido que resolve nomes de serviços para IPs dos containers na mesma rede.

Não. O Docker DNS resolve o nome do serviço dinamicamente, quando o container app reinicia com novo IP, o Nginx resolve o novo IP na próxima requisição. O upstream com keepalive pode ter conexões stale por um curto período, mas o Nginx detecta a falha e reabre a conexão automaticamente.