---
title: "SQLx Migrations em Rust: PostgreSQL, CI e Deploy"
url: "https://rustlang.com.br/blog/sqlx-migrations-rust-postgresql-ci-deploy-2026/"
markdown_url: "https://rustlang.com.br/blog/sqlx-migrations-rust-postgresql-ci-deploy-2026.MD"
description: "Aprenda a criar, executar e reverter migrations SQLx em Rust com PostgreSQL. Guia prático sobre CLI, testes, CI, deploy, locks e rollback de schema seguro."
date: "2026-08-23"
author: "Equipe Rust Brasil"
---

# SQLx Migrations em Rust: PostgreSQL, CI e Deploy

Aprenda a criar, executar e reverter migrations SQLx em Rust com PostgreSQL. Guia prático sobre CLI, testes, CI, deploy, locks e rollback de schema seguro.


**Para trabalhar com migrations SQLx em Rust, instale o `sqlx-cli`, mantenha os arquivos SQL versionados no repositório e execute `sqlx migrate run` contra um banco controlado antes do deploy em produção.** O fluxo mínimo é simples, mas uma estratégia segura também precisa considerar rollback, locks, compatibilidade entre versões da aplicação e testes em banco vazio.

```bash
cargo install sqlx-cli --no-default-features --features rustls,postgres
sqlx migrate add -r criar_usuarios
sqlx migrate run
sqlx migrate info
```

O SQLx registra quais migrations já foram aplicadas e executa somente as pendentes. Isso evita scripts manuais diferentes por ambiente e aproxima a evolução do schema do mesmo processo de revisão usado para o código Rust. Para APIs com [Axum](/ecossistema/axum/), [SQLx](/ecossistema/sqlx/) e PostgreSQL, essa disciplina é parte central de um deploy previsível.

## Resposta rápida: comandos essenciais do SQLx CLI

| Objetivo | Comando |
|---|---|
| Instalar a CLI para PostgreSQL | `cargo install sqlx-cli --no-default-features --features rustls,postgres` |
| Criar uma migration simples | `sqlx migrate add criar_usuarios` |
| Criar migration reversível | `sqlx migrate add -r criar_usuarios` |
| Aplicar pendências | `sqlx migrate run` |
| Ver o estado das migrations | `sqlx migrate info` |
| Reverter a última migration reversível | `sqlx migrate revert` |
| Criar o banco configurado | `sqlx database create` |
| Apagar o banco configurado | `sqlx database drop` |
| Recriar banco e aplicar migrations | `sqlx database reset` |

Confirme os argumentos com `sqlx --help` e `sqlx migrate --help` na versão homologada pelo projeto. A CLI evolui junto com o ecossistema, portanto pipelines devem fixar uma versão conhecida em vez de instalar silenciosamente qualquer release nova.

## O que é uma migration no SQLx

Uma migration é uma alteração versionada no schema do banco. Ela pode criar uma tabela, adicionar um índice, mudar uma restrição, popular uma coluna ou preparar a remoção de uma estrutura antiga. No SQLx, os arquivos ficam normalmente no diretório `migrations/` e fazem parte do repositório.

Ao executar as migrations, o SQLx mantém uma tabela interna de controle no banco. Essa tabela registra informações como a versão aplicada e o checksum do arquivo. O histórico permite distinguir uma migration já executada de uma pendente e também ajuda a detectar alterações indevidas em arquivos antigos.

A regra prática é: **depois que uma migration chegou a um ambiente compartilhado, não edite o arquivo como se ele ainda fosse um rascunho**. Crie uma nova migration corretiva. Alterar o passado pode fazer ambientes divergirem e quebrar a verificação por checksum.

Esse modelo complementa as queries verificadas em compile-time explicadas no guia de [SQLx Rust](/ecossistema/sqlx/). As macros validam o código contra um schema conhecido; migrations tornam a construção desse schema reproduzível.

## Preparando SQLx CLI e PostgreSQL

Em um projeto PostgreSQL, instale a CLI somente com os recursos necessários:

```bash
cargo install sqlx-cli \
  --no-default-features \
  --features rustls,postgres
```

O uso de `--no-default-features` evita compilar suporte a bancos que o projeto não utiliza. A feature de TLS pode variar conforme a política do ambiente; confira a documentação e as opções da versão instalada.

Configure a URL em um arquivo `.env` local que não seja versionado:

```dotenv
DATABASE_URL=postgres://app:senha_local@localhost:5432/app_dev
```

Não coloque credenciais reais em migrations, exemplos do repositório ou logs de CI. Em produção, injete `DATABASE_URL` pelo gerenciador de segredos da plataforma.

Para subir um PostgreSQL descartável durante o desenvolvimento, uma opção é Docker:

```bash
docker run --name sqlx-postgres \
  -e POSTGRES_USER=app \
  -e POSTGRES_PASSWORD=senha_local \
  -e POSTGRES_DB=app_dev \
  -p 5432:5432 \
  -d postgres:17
```

Depois, valide a conexão e o estado:

```bash
sqlx database create
sqlx migrate info
```

Se você usa uma composição completa com API e banco, o tutorial de [deploy de Axum, Docker Compose e PostgreSQL](/blog/deploy-axum-docker-compose-postgresql-2026/) mostra como separar serviços e health checks.

## Criando a primeira migration

Crie uma migration reversível para a tabela de usuários:

```bash
sqlx migrate add -r criar_usuarios
```

A opção `-r` cria um par de arquivos: um para aplicar a mudança (`up`) e outro para revertê-la (`down`). Os nomes incluem uma versão temporal e uma descrição, preservando a ordem.

No arquivo `up`, escreva:

```sql
CREATE TABLE usuarios (
    id BIGSERIAL PRIMARY KEY,
    nome TEXT NOT NULL,
    email TEXT NOT NULL UNIQUE,
    ativo BOOLEAN NOT NULL DEFAULT TRUE,
    criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_usuarios_ativos
    ON usuarios (criado_em DESC)
    WHERE ativo = TRUE;
```

No arquivo `down`:

```sql
DROP TABLE usuarios;
```

Agora aplique e confira:

```bash
sqlx migrate run
sqlx migrate info
```

O arquivo `down` é útil no desenvolvimento, mas não transforma toda alteração em um rollback seguro. Se a migration `up` apagar dados, o `down` não conseguirá recriá-los. Reversibilidade sintática e recuperação real são conceitos diferentes.

## Integrando migrations ao código Rust

Também é possível incorporar as migrations no binário com a macro `sqlx::migrate!`:

```rust
use sqlx::PgPool;

pub async fn executar_migrations(pool: &PgPool) -> Result<(), sqlx::migrate::MigrateError> {
    sqlx::migrate!("./migrations")
        .run(pool)
        .await
}
```

Uma aplicação pequena pode chamar essa função antes de iniciar o servidor:

```rust
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let database_url = std::env::var("DATABASE_URL")?;
    let pool = sqlx::postgres::PgPoolOptions::new()
        .max_connections(10)
        .connect(&database_url)
        .await?;

    executar_migrations(&pool).await?;

    // Criar o Router do Axum e iniciar o servidor.
    Ok(())
}
```

A abordagem reduz a chance de publicar um binário sem os arquivos necessários. Por outro lado, executar mudanças de schema durante o startup mistura duas responsabilidades. Se dez réplicas iniciarem juntas, todas tentarão verificar migrations; mesmo com controle transacional e de concorrência do mecanismo, o startup passa a depender da duração e do sucesso de uma operação administrativa.

Para produção, uma etapa única costuma ser mais clara:

1. construir e testar o artefato;
2. executar um job de migration com identidade e permissões próprias;
3. verificar o resultado;
4. liberar gradualmente a nova versão da aplicação;
5. observar erros, latência e conexões.

## Como testar migrations na CI

O teste mais básico começa com um PostgreSQL vazio. Um pipeline deve:

1. iniciar o serviço de banco;
2. esperar o health check;
3. criar um banco isolado;
4. executar todas as migrations;
5. rodar testes de integração;
6. descartar o ambiente.

Exemplo conceitual de comandos:

```bash
export DATABASE_URL=postgres://app:teste@127.0.0.1:5432/app_ci
sqlx database create
sqlx migrate run
cargo test --locked
```

O banco vazio detecta migrations fora de ordem, SQL inválido e dependências implícitas. Porém, ele não encontra todos os problemas de produção. Uma alteração pode funcionar em uma tabela vazia e falhar ao converter milhões de linhas.

Para mudanças relevantes, adicione um segundo cenário:

- restaure um dump sanitizado ou gere dados representativos;
- aplique a versão anterior do schema;
- execute a nova migration;
- meça tempo, locks e crescimento de armazenamento;
- inicie a versão antiga e a nova da aplicação contra o estado intermediário quando houver deploy gradual.

A suíte de [testes em Rust](/blog/testes-rust-estrategias-boas-praticas-2026/) deve incluir tanto a lógica de domínio quanto a integração com o banco. Mocks não revelam constraints, tipos PostgreSQL, isolamento de transações ou comportamento real de índices.

## Migrations e queries offline no SQLx

Migrations e preparação offline resolvem problemas relacionados, mas diferentes:

- **migrations** constroem e evoluem o schema;
- **metadados offline** permitem verificar macros de query sem conectar o compilador ao banco durante todo build.

Um fluxo comum é aplicar as migrations em um banco de desenvolvimento e depois preparar os metadados usados pela verificação offline:

```bash
sqlx migrate run
cargo sqlx prepare --workspace
```

Versione o artefato de metadados esperado pela versão atual do SQLx e faça a CI verificar se ele está atualizado conforme o procedimento do projeto. Isso é especialmente importante em [Cargo workspaces](/blog/cargo-workspaces-monorepos-rust-2026/) com vários serviços e crates de acesso a dados.

Não confunda preparação offline com execução de migrations no ambiente final. Um build bem-sucedido não prova que o banco de produção já está no schema esperado.

## Estratégia segura: expandir, migrar e contrair

Mudar uma coluna em uso por várias réplicas exige compatibilidade entre versões. O padrão **expandir e contrair** evita depender de uma troca instantânea.

Imagine que `usuarios.nome` será dividido em `nome` e `sobrenome`.

### 1. Expandir

Adicione a nova coluna sem remover a antiga:

```sql
ALTER TABLE usuarios ADD COLUMN sobrenome TEXT;
```

Publique uma versão da aplicação capaz de conviver com os dois formatos. Quando necessário, faça dual write temporário ou mantenha fallback de leitura.

### 2. Migrar os dados

Preencha a coluna em lotes, fora de uma transação gigantesca quando o volume justificar. Monitore duração, locks, I/O e réplicas.

### 3. Contrair

Depois que todas as versões antigas saírem de circulação e os dados forem validados, crie outra migration para adicionar constraints ou remover a estrutura obsoleta.

Esse método é mais trabalhoso do que um `ALTER TABLE` agressivo, mas reduz o risco em rolling deploys. Ele também permite rollback da aplicação sem exigir que o schema volte no tempo.

## Locks e migrations em tabelas grandes

Operações de DDL podem adquirir locks e bloquear queries. O impacto depende do PostgreSQL, da operação, do tamanho da tabela e do tráfego. Antes de executar uma mudança grande:

- leia o plano operacional da versão do PostgreSQL usada;
- teste com volume semelhante ao real;
- configure timeouts apropriados para não esperar indefinidamente;
- evite reescrever uma tabela inteira durante o horário de pico;
- crie índices grandes com a estratégia adequada ao ambiente;
- acompanhe sessões bloqueadas e latência;
- tenha um procedimento de interrupção e recuperação.

Não coloque `CREATE INDEX CONCURRENTLY` ou outras operações especiais em uma migration sem verificar as regras transacionais da ferramenta e do PostgreSQL. Algumas operações não podem rodar dentro de uma transação comum; confirme como a versão do SQLx CLI trata o arquivo e teste o procedimento completo antes da produção.

## Rollback: aplicação e banco não voltam juntos

`sqlx migrate revert` é conveniente no ambiente local:

```bash
sqlx migrate revert
```

Em produção, o melhor rollback muitas vezes é **republicar a versão anterior da aplicação mantendo o schema expandido**. Isso só funciona quando a migration foi desenhada para compatibilidade.

Evite depender de rollback destrutivo para situações como:

- remoção de coluna com dados;
- conversão irreversível de formato;
- consolidação de tabelas;
- deleção de registros;
- alteração que a versão antiga não consegue ler.

Para esses casos, use backup validado, migrations corretivas e rollout gradual. O objetivo não é fazer toda migration possuir um `down` elegante; é preservar dados e permitir recuperação previsível.

## Boas práticas para equipes

### Uma mudança lógica por migration

Arquivos menores são mais fáceis de revisar, medir e corrigir. Não misture criação de tabela, backfill massivo e remoção de estrutura antiga sem necessidade.

### SQL explícito e revisado

Migration é código de produção. Exija revisão de constraints, defaults, nulabilidade, índices, foreign keys e impacto operacional.

### Nunca use dados secretos no arquivo

Migrations ficam no Git e circulam entre ambientes. Segredos devem vir do ambiente, e dados sensíveis não devem aparecer em fixtures públicas.

### Fixe versões da ferramenta

Uma imagem de CI ou etapa de setup deve instalar a versão homologada do `sqlx-cli`. Combine isso com `cargo test --locked` e revisão do [Cargo.lock](/blog/cargo-tree-grafo-dependencias-rust-2026/).

### Separe permissões

A aplicação normalmente precisa ler e modificar dados; o job de migration pode precisar alterar schema. Usar papéis diferentes reduz a superfície de privilégio do processo web.

### Observe o deploy

Registre início, fim, versão e resultado da migration. Não exponha `DATABASE_URL` em logs. Integre a etapa à observabilidade descrita no guia de [Tracing em Rust](/ecossistema/tracing/).

## Checklist antes de executar em produção

- [ ] A migration foi aplicada do zero em CI.
- [ ] O upgrade a partir do schema anterior foi testado.
- [ ] O SQL recebeu revisão de outra pessoa.
- [ ] O arquivo antigo não foi alterado depois de aplicado.
- [ ] O impacto de locks foi medido com dados representativos.
- [ ] A versão antiga da aplicação tolera o schema expandido.
- [ ] Existe backup recente e recuperação testada quando a mudança envolve dados.
- [ ] O job usa o banco e as credenciais do ambiente correto.
- [ ] Logs não exibem segredos.
- [ ] Há observabilidade e responsável disponível durante a execução.
- [ ] O plano de rollback distingue aplicação, schema e dados.

## SQLx migrations ou migration de outro ORM?

Se o projeto usa SQLx diretamente, o sistema integrado é uma escolha natural: SQL versionado, CLI simples e pouca abstração. Diesel e SeaORM também possuem fluxos próprios, mais alinhados aos modelos e ferramentas desses ecossistemas.

A comparação [SQLx vs Diesel vs SeaORM](/blog/rust-banco-dados-sqlx-diesel-seaorm-2026/) ajuda a escolher a camada de acesso a dados. Depois da escolha, evite misturar três gerenciadores de migration sem uma responsabilidade clara. O banco precisa de uma única ordem confiável de evolução.

## Conclusão

SQLx migrations dão a projetos Rust uma forma direta de versionar PostgreSQL, reproduzir ambientes e integrar mudanças de schema à CI. O começo cabe em três comandos: criar a migration, executar pendências e consultar o estado.

A maturidade aparece depois: migrations pequenas, testes com dados reais, deploy separado, expansão compatível, atenção a locks e rollback que prioriza preservação de dados. Em uma API Axum com SQLx, esse processo é tão importante quanto escrever handlers rápidos ou queries tipadas.

Comece criando um banco descartável, aplique todo o diretório `migrations/` e automatize esse caminho na CI. Quando o mesmo procedimento funciona de forma repetível do notebook ao ambiente de produção, o schema deixa de ser uma sequência de comandos manuais e passa a ser parte verificável do software.
