---
title: "Deploy Axum com Docker Compose e PostgreSQL em 2026"
url: "https://rustlang.com.br/blog/deploy-axum-docker-compose-postgresql-2026/"
markdown_url: "https://rustlang.com.br/blog/deploy-axum-docker-compose-postgresql-2026.MD"
description: "Blueprint prático para publicar uma API Axum com Docker Compose, PostgreSQL, migrations, health checks, logs e rollback seguro em uma VPS."
date: "2026-05-21"
author: "Equipe Rust Brasil"
---

# Deploy Axum com Docker Compose e PostgreSQL em 2026

Blueprint prático para publicar uma API Axum com Docker Compose, PostgreSQL, migrations, health checks, logs e rollback seguro em uma VPS.


## Por que um blueprint Axum + PostgreSQL ajuda mais do que um deploy genérico

Muitos guias de deploy Rust param no ponto em que o binário responde `200 OK`. Isso é útil, mas incompleto para quem está construindo uma API real. Uma aplicação [Axum](/ecossistema/axum/) em produção normalmente precisa de banco PostgreSQL, migrations, variáveis de ambiente, logs estruturados, endpoint de saúde, backup, rollback e um jeito simples de subir tudo sem depender de Kubernetes no primeiro dia.

Para produtos pequenos, portfólios profissionais, APIs internas e MVPs brasileiros, **Docker Compose com PostgreSQL em uma VPS** continua sendo um caminho muito competitivo. Ele é mais explícito do que uma plataforma mágica, mais barato do que um cluster gerenciado e simples o bastante para uma pessoa operar. Ao mesmo tempo, força boas práticas que aparecem em [vagas Rust](/vagas/): containerização, banco relacional, observabilidade, deploy reproduzível e disciplina operacional.

Este guia é um blueprint prático. Ele não tenta substituir uma arquitetura enterprise, mas entrega uma base segura para publicar uma API Axum com SQLx e PostgreSQL usando Compose. Se você ainda está montando a imagem Docker, leia também [Rust e Docker para builds de produção](/blog/rust-docker-builds-otimizados-producao-2026/). Se quer uma visão mais ampla de VPS, Nginx e systemd, veja [Deploy Rust em VPS](/blog/deploy-rust-vps-docker-systemd-2026/).

## Arquitetura de produção mínima

A topologia recomendada é simples:

```text
Internet
   |
   v
Nginx / TLS
   |
   v
docker compose network
   |-- axum-api :3000
   |-- postgres :5432
```

Nginx fica na borda, termina TLS e encaminha tráfego para o container `axum-api`. O PostgreSQL não expõe porta pública. A API acessa o banco pela rede interna do Compose usando o hostname `postgres`. Essa separação reduz a superfície de ataque e mantém o deploy fácil de entender.

Para uma primeira versão, rode tudo em uma única VPS com disco persistente e backup externo. Quando o produto crescer, você pode mover o banco para um PostgreSQL gerenciado, adicionar Redis, separar workers ou migrar para Kubernetes. O importante é não começar com complexidade que você ainda não consegue operar.

## Aplicação Axum pronta para container

Uma API Axum precisa escutar em `0.0.0.0`, carregar configuração por ambiente e expor um health check barato. Um esqueleto típico:

```rust
use axum::{extract::State, routing::get, Router};
use sqlx::PgPool;
use std::net::SocketAddr;

#[derive(Clone)]
struct AppState {
    db: PgPool,
}

async fn healthz() -> &'static str {
    "ok"
}

async fn readyz(State(state): State<AppState>) -> Result<&'static str, String> {
    sqlx::query_scalar::<_, i32>("select 1")
        .fetch_one(&state.db)
        .await
        .map_err(|err| err.to_string())?;

    Ok("ready")
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .json()
        .with_env_filter(std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into()))
        .init();

    let database_url = std::env::var("DATABASE_URL")?;
    let db = PgPool::connect(&database_url).await?;
    let state = AppState { db };

    let app = Router::new()
        .route("/healthz", get(healthz))
        .route("/readyz", get(readyz))
        .with_state(state);

    let addr: SocketAddr = "0.0.0.0:3000".parse()?;
    let listener = tokio::net::TcpListener::bind(addr).await?;
    tracing::info!(%addr, "api axum iniciada");
    axum::serve(listener, app).await?;
    Ok(())
}
```

Separe `/healthz` de `/readyz`. O primeiro responde se o processo está vivo. O segundo verifica dependências, como PostgreSQL. Isso evita derrubar a aplicação inteira por uma oscilação curta do banco e ajuda a depurar incidentes sem adivinhar.

## Dockerfile para a API

Um Dockerfile multi-stage mantém a imagem final pequena e sem toolchain de compilação:

```dockerfile
FROM rust:1.88-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
    pkg-config libssl-dev ca-certificates \
    && rm -rf /var/lib/apt/lists/*
COPY Cargo.toml Cargo.lock ./
COPY migrations ./migrations
COPY src ./src
RUN cargo build --release

FROM debian:bookworm-slim AS runtime
RUN apt-get update && apt-get install -y --no-install-recommends \
    ca-certificates curl \
    && rm -rf /var/lib/apt/lists/*
RUN useradd -r -u 10001 appuser
COPY --from=builder /app/target/release/minha-api /usr/local/bin/minha-api
USER appuser
EXPOSE 3000
ENV RUST_LOG=info
CMD ["/usr/local/bin/minha-api"]
```

Em projetos maiores, adicione `cargo-chef` para cache de dependências. Em projetos com OpenSSL, confira se as bibliotecas necessárias estão presentes na imagem final. Se preferir reduzir ainda mais a imagem, você pode estudar builds com `musl`, mas não comece por isso se ainda está validando produto.

## docker-compose.yml com PostgreSQL

Um `docker-compose.yml` de produção pode ficar assim:

```yaml
services:
  axum-api:
    image: registry.example.com/minha-api:2026-05-21
    container_name: axum-api
    restart: unless-stopped
    env_file:
      - /etc/minha-api/api.env
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "127.0.0.1:3000:3000"
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://127.0.0.1:3000/healthz"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 10s

  postgres:
    image: postgres:16-alpine
    container_name: axum-postgres
    restart: unless-stopped
    env_file:
      - /etc/minha-api/postgres.env
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:
```

No arquivo `/etc/minha-api/api.env`, use a URL interna do Compose:

```bash
DATABASE_URL=postgres://minha_api:senha-forte@postgres:5432/minha_api
RUST_LOG=info,tower_http=info,sqlx=warn
```

No `/etc/minha-api/postgres.env`:

```bash
POSTGRES_DB=minha_api
POSTGRES_USER=minha_api
POSTGRES_PASSWORD=senha-forte
```

Não commite esses arquivos. Em produção, eles pertencem ao servidor ou a um gerenciador de secrets. Se você usa CI/CD, injete variáveis no momento do deploy, não no repositório.

## Migrations: rode antes de liberar tráfego

Com [SQLx](/ecossistema/sqlx/), migrations devem ser parte do processo de deploy, não uma tarefa manual esquecida. O fluxo mais seguro é:

1. Subir uma imagem nova sem remover a anterior.
2. Rodar `sqlx migrate run` contra o banco de produção.
3. Reiniciar a API apontando para a nova imagem.
4. Conferir `/readyz`, logs e métricas.
5. Manter a tag anterior disponível para rollback.

Você pode empacotar o binário `sqlx-cli` em uma imagem separada, rodar migrations a partir do CI ou usar um comando temporário no servidor. O ponto crítico é versionar cada alteração de schema e evitar mudanças irreversíveis sem plano. Para deploys pequenos, uma regra prática funciona bem: primeiro adicione colunas/tabelas compatíveis, publique a aplicação que usa o novo schema e só depois remova campos antigos em outro deploy.

## Nginx na frente da API

Mesmo quando Compose publica a porta só em `127.0.0.1`, coloque Nginx ou Caddy na frente para TLS, compressão e cabeçalhos. Um bloco Nginx mínimo:

```nginx
server {
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

Configure TLS com Let's Encrypt, limite tamanho de payload quando necessário e adicione rate limiting se a API for pública. Em Axum, leia `X-Forwarded-Proto` com cuidado se precisar gerar URLs absolutas. Para autenticação, prefira tokens curtos, rotação clara e logs que não imprimam secrets.

## Logs, backup e rollback

O deploy só está completo quando você consegue responder três perguntas: o que está rodando, se está saudável e como voltar atrás. Use tags imutáveis de imagem, como SHA do commit, em vez de `latest`. Registre o commit em uma variável `APP_VERSION` ou endpoint `/version`. Mantenha logs em JSON com [Tracing](/ecossistema/tracing/) e consulte com `docker logs axum-api` ou agregue em Loki, Vector ou outra stack.

Para PostgreSQL, configure backup antes do primeiro usuário real. Um `pg_dump` diário para armazenamento externo já é melhor do que nada. Teste restore em outro diretório ou outra máquina. Backup que nunca foi restaurado é esperança, não estratégia.

Rollback deve ser simples: trocar a tag da imagem no Compose e rodar `docker compose up -d`. O cuidado está no banco. Se a migration removeu coluna ou transformou dados de forma irreversível, rollback da aplicação pode falhar. Por isso migrations compatíveis e pequenas são parte do design, não burocracia.

## Checklist antes de publicar

Antes de apontar tráfego real para a API, revise:

- `DATABASE_URL` não aparece no repositório nem nos logs;
- PostgreSQL não expõe `5432` para a internet;
- `/healthz` e `/readyz` respondem como esperado;
- migrations rodam em ambiente limpo;
- imagem Docker usa usuário não-root;
- Nginx termina TLS e encaminha cabeçalhos corretos;
- existe backup automático e restore testado;
- a tag anterior da imagem ainda está disponível;
- logs têm request id, status, latência e erro sem dados sensíveis.

Esse checklist transforma um projeto de portfólio em algo muito mais próximo de produção. Para carreira, isso importa. Empresas brasileiras que usam Rust em backend, fintech, infraestrutura ou ferramentas internas querem ver que você entende o ciclo completo: código, banco, deploy, operação e manutenção. Combine este guia com o tutorial de [Rust com PostgreSQL](/tutoriais/rust-postgresql/), a página de [Cargo](/ecossistema/cargo/), o guia de [CI/CD para Rust](/artigos/ci-cd-rust/) e a lista de [empresas que usam Rust](/empresas/) para montar um portfólio forte.

Se você vem de Go e quer comparar a mesma mentalidade operacional em outra linguagem, o ecossistema do <a href="https://golang.com.br/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go Brasil</a> também cobre backend, Docker e serviços em produção. A diferença é que Rust força mais decisões em tipos e em tempo de compilação, o que combina muito bem com APIs que precisam ser rápidas, previsíveis e seguras.
