Database migrations no CI/CD: Flyway ou Liquibase automatizados
1. Introdução ao Problema: Migrações de Banco de Dados em Ambientes Contêineres
Em pipelines DevOps modernos, a automação de migrações de banco de dados é um dos maiores desafios técnicos. Enquanto o código da aplicação evolui rapidamente através de containers efêmeros, os bancos de dados mantêm estado persistente — e essa assimetria gera conflitos frequentes.
No contexto de Docker e Kubernetes, o problema se agrava: pods são criados e destruídos constantemente, mas o schema do banco precisa evoluir de forma controlada e atômica. Uma migração mal executada pode derrubar toda a aplicação ou, pior, corromper dados em produção.
O fluxo ideal funciona assim: um desenvolvedor altera um arquivo SQL → o CI detecta a mudança → executa a migração no banco de staging → após validação, promove para produção → só então o deploy da nova versão da aplicação é liberado. Este artigo mostra como implementar esse fluxo com Flyway e Liquibase, duas ferramentas maduras de versionamento de banco de dados.
2. Flyway vs. Liquibase: Escolha da Ferramenta
Flyway adota uma abordagem minimalista: você escreve scripts SQL puros (V1__create_table.sql, V2__add_column.sql) e a ferramenta gerencia o versionamento automaticamente. É ideal para equipes pequenas que preferem simplicidade e já dominam SQL.
Liquibase oferece mais controle: os changesets podem ser escritos em XML, YAML ou JSON, com suporte nativo a rollback e pré-condições. É a escolha certa para ambientes regulados (bancos, fintechs) onde cada alteração precisa ser auditada e reversível.
Ambas as ferramentas possuem imagens Docker oficiais:
# Flyway
docker run --rm flyway/flyway:latest migrate -url=jdbc:postgresql://host:5432/db -user=admin -password=secret
# Liquibase
docker run --rm liquibase/liquibase:latest update --url=jdbc:postgresql://host:5432/db --username=admin --password=secret
3. Estrutura de Pipeline CI/CD para Migrações
A chave para pipelines robustos é separar a etapa de migração do build da aplicação. Exemplo de pipeline GitLab CI:
stages:
- build
- migrate
- deploy
migrate-staging:
stage: migrate
image: flyway/flyway:latest
script:
- flyway -url=$STAGING_DB_URL -user=$DB_USER -password=$DB_PASSWORD migrate
only:
- main
deploy-staging:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl set image deployment/app app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
needs:
- migrate-staging
No GitHub Actions, o mesmo princípio se aplica:
jobs:
migrate:
runs-on: ubuntu-latest
container:
image: liquibase/liquibase:latest
steps:
- name: Run migration
run: |
liquibase update \
--url=${{ secrets.DB_URL }} \
--username=${{ secrets.DB_USER }} \
--password=${{ secrets.DB_PASS }}
A regra de ouro: nunca faça deploy antes da migração. O pipeline deve bloquear a etapa de deploy se a migração falhar.
4. Execução Segura em Kubernetes com Init Containers
Em Kubernetes, o padrão mais seguro é usar init containers para executar migrações antes do container principal da aplicação subir:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
spec:
initContainers:
- name: flyway-migration
image: flyway/flyway:latest
command: ["flyway", "migrate"]
env:
- name: FLYWAY_URL
value: jdbc:postgresql://db-service:5432/mydb
- name: FLYWAY_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: FLYWAY_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
volumeMounts:
- name: migrations
mountPath: /flyway/sql
containers:
- name: myapp
image: myapp:latest
Para evitar concorrência entre réplicas, configure locks de banco:
# Flyway usa locks nativos do banco
# Liquibase requer configuração adicional:
liquibase.command.lockWaitTime: 60000
liquibase.command.lockPollRate: 1000
5. Versionamento e Rollback Automatizados
Versionamento: Flyway usa números sequenciais (V1, V2, V3) enquanto Liquibase sugere timestamps (20240101120000). Ambos funcionam, mas timestamps evitam conflitos em branches paralelos.
Rollback via CI/CD: Liquibase tem suporte nativo a rollback:
# Rollback de uma changeset específica
liquibase rollback --tag=v1.0
# Rollback automático em caso de falha
liquibase update --rollback-on-error=true
Flyway requer a versão Teams/Enterprise para rollback automático. Na versão Community, use scripts manuais:
# Criar script de rollback manual
# U1__revert_create_table.sql
DROP TABLE IF EXISTS users CASCADE;
Feature flags: isole migrações problemáticas com variáveis de ambiente:
- Se a migração falhar, ative a flag DISABLE_NEW_FEATURE=true
- A aplicação lê a flag e desativa a funcionalidade que depende da nova tabela
6. Gerenciamento de Segredos e Conexões
Nunca hardcode credenciais. Use Kubernetes Secrets:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
username: <base64-encoded>
password: <base64-encoded>
Referencie no pod:
envFrom:
- secretRef:
name: db-credentials
Para rotation de senhas sem downtime, use o padrão dual credentials: mantenha duas senhas ativas no banco, troque a senha no secret e remova a antiga após validação.
7. Monitoramento e Validação Pós-Migração
Configure health checks no Kubernetes que aguardam a migração:
livenessProbe:
exec:
command:
- /bin/sh
- -c
- "curl -f http://localhost:8080/health/db || exit 1"
initialDelaySeconds: 10
periodSeconds: 30
O endpoint /health/db deve verificar se a versão esperada do schema está ativa. Exemplo em Node.js:
app.get('/health/db', async (req, res) => {
const version = await db.query('SELECT version FROM schema_version ORDER BY version DESC LIMIT 1');
if (version === process.env.EXPECTED_SCHEMA_VERSION) {
res.status(200).send('OK');
} else {
res.status(503).send('Migration pending');
}
});
No pipeline, adicione alertas:
# GitLab CI - notificação no Slack
after_script:
- |
if [ $CI_JOB_STATUS == "failed" ]; then
curl -X POST -H 'Content-type: application/json' \
--data '{"text":"Migration failed in $CI_ENVIRONMENT_NAME"}' \
$SLACK_WEBHOOK_URL
fi
8. Boas Práticas e Próximos Passos
- Teste em staging com dados anonimizados: clone a base de produção, anonimize dados sensíveis e execute as migrações primeiro ali.
- Canary releases: faça rollout gradual (10% → 50% → 100%) e monitore erros de banco antes de liberar para todos.
- Migrações declarativas com operadores Kubernetes: ferramentas como StackGres e KubeDB gerenciam bancos inteiros via CRDs, incluindo migrações automáticas.
Checklist final para produção:
- [ ] Migração executada em init container (nunca no container principal)
- [ ] Locks de banco configurados para evitar concorrência
- [ ] Rollback testado e documentado
- [ ] Secrets injetados via Kubernetes Secrets
- [ ] Health checks verificam versão do schema
- [ ] Pipeline bloqueia deploy em caso de falha
Com essas práticas, suas migrações de banco de dados se tornam tão confiáveis quanto o deploy de código — mesmo em ambientes Kubernetes dinâmicos e efêmeros.
Referências
- Flyway Documentation — Documentação oficial completa do Flyway, incluindo exemplos de integração com Docker e Kubernetes.
- Liquibase Official Docs — Guia oficial do Liquibase com changelogs, rollback e integração CI/CD.
- Kubernetes Init Containers — Documentação oficial sobre init containers, padrão recomendado para migrações.
- GitLab CI/CD Database Migrations — Tutorial prático do GitLab sobre como executar migrações no pipeline CI/CD.
- StackGres Operator — Operador Kubernetes para PostgreSQL que gerencia migrações e backups de forma declarativa.
- KubeDB by AppsCode — Operador Kubernetes para múltiplos bancos de dados, com suporte a migrações automáticas via CRDs.