Destino Dev

Blog

Ambientes de preview em PRs com GitHub Actions e Docker Compose — passo a passo

Por John Pires · 19 de março de 2026

Ambientes de preview em PRs com GitHub Actions e Docker Compose — passo a passo

1) Por que aprender isso em 2026

Contexto atual e motivação

Em 2026, equipes distribuídas e integração contínua são padrão. Review de código não é só revisar diffs: é validar comportamento do sistema. Ambientes de preview (deploy efêmero por PR) encurtam o ciclo de feedback, aumentam a confiança em mudanças e reduzem bugs em produção. Para quem está começando, entender como montar um preview com Docker Compose e GitHub Actions entrega uma habilidade prática e repetível.

Analogia simples

Pense em um preview como uma cópia temporária do seu playground. Em vez de testar mudanças no mesmo parque onde as outras pessoas brincam, você cria uma caixa isolada e descarta depois. Docker Compose é a caixa, GitHub Actions é o encarregado que monta a caixa automaticamente quando você abre um PR.

Antes de partir para exemplos, dois pontos importantes: 1) previews devem ser rápidos e descartáveis; 2) não exponha dados sensíveis. Vamos trabalhar com serviços simples (API e banco em memória ou sqlite) para manter segurança e velocidade.

Regra prática: ambientes de preview servem para validação funcional e visual, não como ambiente de staging permanente.

2) Configuracao do ambiente

Ferramentas necessárias

Você precisa do Docker instalado (engine + CLI), docker-compose v2 ou superior (ou plugin compose), e uma conta no GitHub com repositório. Localmente, teste seus compose files; no CI, o runner do GitHub Actions já tem Docker disponível. Tenha também o Git configurado e, se precisar, acesso para criar secrets no repositório.

Estrutura mínima do repositório

Uma estrutura simples para começar:

  • app/ - código da aplicação (ex: Node.js/Flask)
  • docker/Dockerfile - imagem da app
  • docker/docker-compose.yml - composição para desenvolvimento
  • .github/workflows/preview.yml - workflow que cria o preview

Essa estrutura isola infra de código. O nome das pastas aqui é sugestão; o importante é manter consistência entre compose e workflow.

Segurança e secrets

Não commitse credenciais. No GitHub Actions, configure secrets via Settings > Secrets. Use variáveis de ambiente no workflow e injete apenas valores necessários. Para bancos de teste, prefira dados sintéticos ou bancos em memória.

3) Hello World progressivo

Primeiro erro: fazer preview com imagem pública e dados reais

Um erro comum de iniciantes é rodar em preview a mesma imagem usada em produção apontando para serviços reais. Isso pode causar efeitos colaterais e vazamento de dados. Expondo serviços internos ou utilizando credenciais reais, você transforma um preview em risco.

Jeito errado: workflow que chama deploy para produção

Exemplo de um erro prático em workflow: executar um script de deploy que usa credenciais de produção diretamente no PR. Veja o trecho problemático (não execute):

# Jeito errado: deploy direto para o ambiente de producao (exemplo nao funcional de objetivo didatico)
# Este arquivo demonstra o erro: ele chama um script de deploy com SECRET_PROD presente
name: preview-bad
on:
  pull_request:
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: ./scripts/deploy-to-production.sh

O que faz: chama um deploy que pode usar variáveis de produção. O que acontece: quando aberto um PR, alterações acidentais podem ser promovidas, e revisores não conseguem isolar impacto.

Jeito certo: criar preview isolado com Docker Compose

Melhor abordagem: construir imagem da branch e orquestrar serviços via Docker Compose apenas no runner, usando variáveis seguras e portas dinâmicas. Workflow cria um compose file temporário e expõe um endpoint de verificação, sem tocar produção.

Abaixo um Dockerfile simples para uma API Node.js (exemplo real funcional básico). Contexto: builda a imagem usada no preview. Depois do bloco eu explico o que ocorre.

# Dockerfile: construi uma imagem minima para a app de exemplo
FROM node:18-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

O que faz: cria uma imagem leve baseada em node:18-alpine, instala dependências e define comando de execução. O que acontece ao buildar: a imagem está pronta para rodar em um container que serve a API na porta 3000.

Compose de preview: jeito certo e isolado

Exemplo de docker-compose específico para preview. Contexto: ele monta a app e um banco sqlite em volume temporário. Depois explico comportamento.

# docker/docker-compose.preview.yml
version: '3.8'
services:
  app:
    build:
      context: ..
      dockerfile: docker/Dockerfile
    ports:
      - "0:3000" # porta dinamica no host do runner
    environment:
      - NODE_ENV=preview
      - DATABASE_URL=sqlite:/data/db.sqlite
    volumes:
      - preview_data:/data
volumes:
  preview_data:
    driver: local

O que faz: define serviço app com build a partir da branch atual e monta um volume temporário. A porta host usa '0:3000' que no runner permite mapear porta aleatória. O que acontece: ao subir containers, runner atribui uma porta de host e sua aplicação fica acessível localmente no runner, usada apenas para checks automáticos no workflow.

4) Mini-projeto pratico

Objetivo do mini-projeto

Vamos montar um fluxo mínimo que cria um ambiente de preview para cada Pull Request, executa testes de smoke (curl para health) e registra logs. Tudo isso usando apenas GitHub Actions + Docker Compose, sem necessidade de cloud externo.

Workflow GitHub Actions: explicação e arquivo

A seguir, o workflow que faz checkout, constrói imagem, executa docker compose, descobre a porta e faz uma verificação. Contexto: esse arquivo roda apenas em PRs e não empurra nem modifica ambientes externos.

# .github/workflows/preview.yml
name: preview
on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      - name: Build image for preview
        run: |
          docker build -f docker/Dockerfile -t preview:${{ github.sha }} .

      - name: Run preview compose
        run: |
          mkdir -p /tmp/preview-${{ github.run_id }}
          cp docker/docker-compose.preview.yml /tmp/preview-${{ github.run_id }}/compose.yml
          cd /tmp/preview-${{ github.run_id }}
          docker compose -f compose.yml up -d

      - name: Find mapped port
        id: port
        run: |
          CONTAINER_ID=$(docker ps --filter "ancestor=preview:${{ github.sha }}" --format "{{.ID}}" | head -n1)
          PORT=$(docker port $CONTAINER_ID 3000/tcp | sed 's/.*://')
          echo "port=$PORT" >> $GITHUB_OUTPUT

      - name: Smoke test health
        run: |
          curl -sSf "http://localhost:${{ steps.port.outputs.port }}/health" | jq .

      - name: Tear down
        if: always()
        run: |
          cd /tmp/preview-${{ github.run_id }}
          docker compose -f compose.yml down -v

O que faz: este workflow builda a imagem, sobe o compose em background, detecta a porta exposta, faz um curl para /health e derruba containers. O que acontece: cada PR executa esse pipeline isolado no runner; se o health falhar, o job falhará e o revisor verá erro no PR.

Script de wait e fallback (exemplo real funcional)

Em ambientes reais, o serviço pode demorar para subir. Um script de espera robusto evita falsos negativos. Contexto: crie scripts/wait-for-health.sh e use no workflow antes do curl.

# scripts/wait-for-health.sh
#!/usr/bin/env bash
set -euo pipefail
base_url="$1"
retries=${2:-10}
interval=${3:-2}

for i in $(seq 1 $retries); do
  if curl -sSf "$base_url/health" >/dev/null 2>&1; then
    echo "ok"
    exit 0
  fi
  echo "waiting... ($i/$retries)"
  sleep $interval
done

echo "service did not become healthy" >&2
exit 1

O que faz: tenta acessar /health até o limite de retries. O que acontece: evita falhas imediatas se a app demorar para inicializar; o workflow deve chamar esse script antes de executar testes mais complexos.

Erros comuns e armadilhas

Listo aqui problemas frequentes e soluções práticas. Esses itens são baseados em code reviews feitos em projetos reais.

  • Expor portas fixas: usar portas fixas (3000:3000) causa colisão se jobs paralelos rodarem. Use porta dinâmica ou namespace por run.
  • Credenciais no repositório: nunca commitse arquivos com secrets. Use GitHub Secrets e variáveis de ambiente no workflow.
  • Volumes persistentes entre runs: limpar volumes com docker compose down -v para evitar dados antigos interferindo.
  • Logs insuficientes: capture logs do container (docker logs) no passo de Tear down para depuração quando algo falhar.
Dica: sempre inclua passo final que coleta docker logs e artifacts (se aplicável). Logs salvam code reviews longos e evitam reproduzir o erro localmente.

5) Recursos para continuar estudando

Leitura e documentação

Documentação oficial é ponto de partida: Docker Docs, Docker Compose reference e GitHub Actions docs. Além disso, artigos sobre runner self-hosted e ambientes efêmeros ampliam horizontes. Procure por exemplos de preview environments em frameworks que você usa (Next.js, Rails, Django).

Ferramentas complementares

Ferramentas que podem facilitar previews em equipes: ngrok (túnel), external-dns + ingress controllers (para previews em k8s), e plataformas como Okteto ou StackBlitz para previews mais integrados. Cada uma tem trade-offs: custo, complexidade, segurança.

Para projetos pequenos e aprendizado, Docker Compose + GitHub Actions é o caminho mais simples. Você entende cada passo e não depende de terceiros.

Conclusão prática

Resumo direto

Workflow eficiente para previews: construir a imagem da branch, subir containers isolados com docker compose, executar checks automatizados e derrubar recursos. Evite deploys diretos para produção, não exponha secrets e automatize coleta de logs em falhas.

Checklist para aplicar agora

  1. Adicionar Dockerfile que construa sua aplicação de forma reprodutível.
  2. Criar docker-compose.preview.yml com porta dinâmica e volumes temporários.
  3. Implementar workflow GitHub Actions que builda, sobe compose, espera health e derruba recursos.
  4. Configurar secrets no GitHub e evitar qualquer credencial em commits.
  5. Adicionar coleta de logs e artifacts para facilitar code review.

Próximo passo prático: copie os arquivos mostrados, adapte caminhos e nomes, e abra um PR para ver o preview rodando no runner. Se algo falhar, confira logs e revise as portas e volumes.

Este artigo demonstrou o jeito errado (deploy para produção em PR) e o jeito certo (preview efêmero e isolado). Aplique o padrão, automatize e mantenha os previews rápidos e seguros.

Se ficou com dúvidas sobre scripts específicos do seu framework (Rails, Django, Node), adapte os comandos de build e health; o fluxo geral se mantém. Bons previews aceleram reviews e reduzem surpresas em produção.

Obrigado por levar isso até o fim: aprender a automatizar previews é um ganho real em produtividade e qualidade de código.

Assuntos: DevOps, github-actions, docker-compose, ci-cd, preview-environment

Leia também