tutorial: configurar GitHub Actions para testes de integração com PostgreSQL em containers efêmeros e rollback automático de migrações — guia prático
Deploy & DevOps · Por Kevin Marcondes · Publicado em · 9 min de leitura
Este passo a passo mostra como preparar um workflow que sobe PostgreSQL como service efêmero, aplica migrations (up), roda testes de integração e executa rollback (down) automaticamente, mesmo quando há falhas. Tema: tutorial: configurar GitHub Actions para testes de integração com PostgreSQL em containers efêmeros e rollback automático de migrações. Ao final, você terá um pipeline reproduzível e limpo.
1) Pré‑requisitos e arquitetura do pipeline
Você precisa de testes de integração e scripts de migração com up/down. O workflow cria um PostgreSQL efêmero por job, aplica migrations, executa testes e desfaz alterações no final. Nunca aponte para um banco compartilhado no CI.
Arquitetura: runner do GitHub Actions + service PostgreSQL + scripts de migrate/rollback. Defina DATABASE_URL e valide a conexão antes da suíte para falhar rápido.
- Repositório com diretório de migrations (.up.sql e .down.sql).
- Scripts para aplicar e desfazer migrations (bash, make ou ferramenta da sua stack).
- Test runner (pytest, npm test, go test etc.).
- Acesso a runner Ubuntu no GitHub Actions e psql instalado.
- DATABASE_URL configurada para o service local.
# Variáveis de ambiente e verificação rápida
export DATABASE_URL="postgres://postgres:postgres@localhost:5432/app_test"
pg_isready -h localhost -p 5432 -U postgres
psql "$DATABASE_URL" -c "SELECT 1;"
# Exemplos de execução (substitua pelo seu runner)
# Node: npm run test:integration
# Python: pytest -m integration
# Go: go test ./... -tags=integration
./scripts/run-tests.sh
2) Como o banco efêmero e o rollback automático funcionam?
O service PostgreSQL é descartado ao fim do job, garantindo isolamento entre execuções. Mesmo assim, o rollback (down) é útil para validar reversões e treinar a equipe para cenários reais.
Você pode combinar estratégias complementares de isolamento, rollback e sandbox transacional conforme sua stack e cobertura desejada.
| Estratégia | Quando usar | Prós | Contras |
|---|---|---|---|
| Banco efêmero por job | Sempre que possível | Isolamento total e limpo | Não valida scripts de rollback |
| Rollback de migrations (down) ao final | Validar reversões e treinar equipe | Garante que o down funciona | Exige manter scripts down confiáveis |
| Transação por teste (BEGIN/ROLLBACK) | Frameworks que suportam sandbox | Execução rápida e repetível | Não cobre alterações de schema complexas |
3) Organizar migrations e scripts de up/down
Padronize um layout previsível e crie dois scripts: migrate.sh (up) e rollback.sh (down). Scripts devem falhar no primeiro erro para não mascarar problemas.
Se precisar de critérios e práticas adicionais sobre reversões, veja também o guia de organização de migrations em PostgreSQL em migrations e rollbacks bem estruturados.
# Estrutura sugerida
# migrations/
# 001_create_users.up.sql
# 001_create_users.down.sql
# 002_add_email_index.up.sql
# 002_add_email_index.down.sql
# scripts/
# migrate.sh
# rollback.sh
# scripts/migrate.sh
#!/usr/bin/env bash
set -euo pipefail
shopt -s nullglob
: "${DATABASE_URL:?Defina DATABASE_URL}"
for f in migrations/*.up.sql; do
echo "[UP] Aplicando $f"
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -1 -f "$f"
done
echo "Migrations UP concluídas."
# scripts/rollback.sh
#!/usr/bin/env bash
set -euo pipefail
shopt -s nullglob
: "${DATABASE_URL:?Defina DATABASE_URL}"
# Reverte na ordem inversa
for f in $(ls migrations/*.down.sql | sort -r); do
echo "[DOWN] Revertendo $f"
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -1 -f "$f"
done
echo "Rollback concluído."
4) Definir o workflow do GitHub Actions com PostgreSQL service
Crie um workflow que sobe o service postgres, instala o cliente psql, aplica migrations, executa testes e sempre tenta o rollback no final. Use GitHub Secrets em vez de senhas em claro.
Para reforçar a segurança de pipelines e secrets, confira o checklist de segurança CI/CD. Se você também faz deploys com Actions, veja o passo a passo de pipeline de deploy automático.
name: ci-integration-postgres
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
ports:
- 5432:5432
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: app_test
options: >-
--health-cmd="pg_isready -U postgres"
--health-interval=5s
--health-timeout=5s
--health-retries=20
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/app_test
steps:
- uses: actions/checkout@v4
- name: Instalar cliente PostgreSQL
run: |
sudo apt-get update
sudo apt-get install -y postgresql-client
- name: Aguardar Postgres e validar conexão
run: |
for i in {1..30}; do
if pg_isready -h localhost -p 5432 -U postgres; then
break
fi
sleep 1
done
psql "$DATABASE_URL" -c "SELECT version();"
- name: Rodar migrations (UP)
run: bash scripts/migrate.sh
- name: Executar testes de integração
run: |
./scripts/run-tests.sh
- name: Rollback de migrations (DOWN) — sempre
if: always()
run: bash scripts/rollback.sh
5) Como garantir o rollback mesmo quando os testes falham?
Use if: always() no passo de rollback. Assim, o down roda independentemente do resultado dos testes. Falhas no rollback devem quebrar o job para sinalizar migrations não reversíveis.
Se preferir, crie um job de limpeza separado com needs e if: always() para executar ações adicionais após a suíte.
- Passo de rollback com if: always().
- Erros no rollback interrompem o pipeline para correção imediata.
- Job cleanup opcional para tarefas extras após testes.
# Exemplo de job de limpeza opcional
jobs:
test:
runs-on: ubuntu-latest
# ... passos do job de testes ...
cleanup:
runs-on: ubuntu-latest
needs: [test]
if: always()
steps:
- uses: actions/checkout@v4
- name: (Opcional) operações de limpeza adicionais
run: |
echo "Nada a fazer: o service Postgres é efêmero."
# Em casos especiais, notifique ou arquive artefatos.
6) Paralelismo e bancos isolados por job: como configurar?
Para paralelizar, gere um nome de banco único por job e exporte na DATABASE_URL, evitando colisões entre execuções simultâneas. A matrix permite testar múltiplas versões do PostgreSQL.
Se você usa runners self‑hosted e precisa escalar concorrência, veja o guia de auto‑escala de runners do GitHub Actions.
- Nome do banco com matrix.pg, github.run_id e github.run_attempt.
- Criação idempotente via CREATE DATABASE ou createdb.
- Rollback continua válido com múltiplas bases isoladas.
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
pg: [14, 15, 16]
services:
postgres:
image: postgres:${{ matrix.pg }}
ports: ["5432:5432"]
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd="pg_isready -U postgres"
--health-interval=5s --health-timeout=5s --health-retries=20
env:
DB_NAME: app_test_${{ matrix.pg }}_${{ github.run_id }}_${{ github.run_attempt }}
DATABASE_URL: postgres://postgres:postgres@localhost:5432/${{ env.DB_NAME }}
steps:
- uses: actions/checkout@v4
- name: Instalar cliente PostgreSQL
run: sudo apt-get update && sudo apt-get install -y postgresql-client
- name: Criar banco isolado
run: |
psql "postgres://postgres:postgres@localhost:5432/postgres" \
-v ON_ERROR_STOP=1 -c "CREATE DATABASE \"${DB_NAME}\";" || true
- name: Rodar migrations e testes
run: |
bash scripts/migrate.sh
./scripts/run-tests.sh
- name: Rollback (sempre)
if: always()
run: bash scripts/rollback.sh
7) Seeds de dados no CI sem sujeira
Seeds inserem dados mínimos necessários para os testes. Aplique após migrations e, se não houver rollback específico, confie no banco efêmero para limpeza.
Prefira seeds idempotentes (UPSERT/ON CONFLICT) para evitar duplicatas e intermitências em reexecuções.
- Seeds após migrations; antes dos testes.
- Ids fixos e UPSERTs evitam duplicatas.
- Rollback pode remover registros de seed explicitamente.
# scripts/seed.sh (exemplo)
#!/usr/bin/env bash
set -euo pipefail
: "${DATABASE_URL:?}"
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 <<'SQL'
INSERT INTO users(id, name, email)
VALUES (1, 'Test User', '[email protected]')
ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, email=EXCLUDED.email;
SQL
echo "Seeds aplicados."
8) Verificação final e solução de problemas comuns
Confirme que o schema foi aplicado, que os testes usaram a base correta e que o rollback limpou as alterações. Em falhas, foque em conexão, autenticação e ordem das migrations.
Se o erro persistir, isole um caso mínimo com uma migration e um teste para reduzir variáveis e acelerar o diagnóstico.
- Connection refused: aguarde saúde do service e instale psql.
- Autenticação: use secrets; usuário com privilégios adequados.
- Timeouts: aumente health‑retries/interval; evite consultas pesadas no setup.
- Locks: aplique migrations em transação (-1) e serialize alterações complexas.
- psql não encontrado: instale postgresql-client no job.
- Sem down: mantenha banco efêmero e escreva scripts de rollback o quanto antes.
# Checks rápidos
psql "$DATABASE_URL" -c "\\dt public.*" # Tabelas no schema público
psql "$DATABASE_URL" -c "SELECT count(*) FROM public.users;" # Tabela-chave
psql "$DATABASE_URL" -c "\\dt public.users" || true # Após rollback, deve falhar/estar vazia
Perguntas frequentes
Preciso do rollback se o banco do CI já é efêmero?
Sim, se você quer validar os scripts down. O banco efêmero garante limpeza; o rollback garante que suas migrações são reversíveis e evita travamentos em cenários reais de rollback.
Posso usar docker-compose em vez de services no GitHub Actions?
Pode, mas o services do GitHub Actions é mais simples para um único PostgreSQL. O docker-compose adiciona flexibilidade quando há múltiplos serviços e redes personalizadas no pipeline.
Como isolar builds paralelos para não conflitar dados?
Use nomes de banco únicos por job (run_id, run_attempt, matriz). Crie a base antes das migrations e exporte DATABASE_URL com o nome diferenciado para cada execução.
Onde coloco a senha do PostgreSQL no CI?
Coloque em GitHub Secrets e referencie no workflow. Em exemplos didáticos usamos valores padrão, mas em projetos reais use secrets para reduzir exposição e auditoria.
E se minhas migrations não têm scripts down?
Use o banco efêmero para garantir limpeza entre jobs e priorize escrever os scripts down. Sem down, você não consegue treinar e validar rollbacks, o que aumenta risco em produção.
Como semear dados de teste sem sujar o ambiente?
Aplique seeds idempotentes após as migrations e, se necessário, remova-os no rollback. Em banco efêmero, o container é descartado ao fim do job, garantindo limpeza automática.
Quer acelerar seu pipeline de testes com PostgreSQL?
Se precisa adaptar o exemplo à sua stack (Node, Python, Go, PHP) e garantir rollbacks confiáveis, vamos conversar sobre seu cenário.
Falar sobre seu pipeline CIVeja também
- tutorial: configurar mTLS entre Traefik e serviços em Docker Compose usando smallstep CA e certificados automáticos — passo a passo
- Tutorial: integrar Sentry ao pipeline de deploy com GitHub Actions para upload automático de source maps e criação de issues (PHP e JavaScript) — passo a passo
- Configurar retenção e compactação de logs de containers Docker com journald + logrotate + rsyslog em VPS com pouco disco
- Configurar BuildKit garbage collection e systemd timers para limpeza automática de imagens e volumes Docker em VPS: tutorial prático