---
title: "Deploy Rust em VPS: Docker, systemd e Nginx em 2026"
url: "https://rustlang.com.br/blog/deploy-rust-vps-docker-systemd-2026/"
markdown_url: "https://rustlang.com.br/blog/deploy-rust-vps-docker-systemd-2026.MD"
description: "Guia prático para publicar uma API Rust em uma VPS usando Docker multi-stage, systemd, Nginx, health checks, logs e rollback seguro."
date: "2026-05-19"
author: "Equipe Rust Brasil"
---

# Deploy Rust em VPS: Docker, systemd e Nginx em 2026

Guia prático para publicar uma API Rust em uma VPS usando Docker multi-stage, systemd, Nginx, health checks, logs e rollback seguro.


## Introdução

Deploy de Rust em produção costuma cair em dois extremos: ou um Kubernetes completo para uma aplicação pequena, ou um binário copiado por SSH sem logs, health check nem rollback. Para a maioria dos produtos pequenos e médios, uma **VPS com Docker, systemd e Nginx** é o ponto de equilíbrio: simples de operar, barato, reproduzível e seguro o bastante para tráfego real.

Este guia mostra um caminho pragmático para publicar uma API Rust, especialmente uma aplicação **Axum** ou **Actix Web**, em uma VPS Linux. A ideia é sair de `cargo run` local para um serviço com:

- imagem Docker pequena;
- variáveis de ambiente separadas do código;
- Nginx com TLS na frente;
- health check para monitoramento;
- logs via `journalctl` e Docker;
- rollback rápido quando algo der errado.

Se você ainda não otimizou a imagem, leia também o guia de [Rust e Docker para builds de produção](/blog/rust-docker-builds-otimizados-producao-2026/), o artigo de [CI/CD para Rust](/artigos/ci-cd-rust/) e a página de [Axum no ecossistema Rust](/ecossistema/axum/) quando a aplicação for uma API HTTP. Aqui vamos focar no servidor.

## Arquitetura Recomendada

A topologia é simples:

```text
Internet
   |
   v
Nginx :443  --->  container rust-api :3000
   |
   +-- /healthz para monitoramento externo
```

Nginx termina TLS, aplica limites básicos e encaminha as requisições para a aplicação Rust rodando em `127.0.0.1:3000` ou em uma rede Docker local. A aplicação não precisa expor porta pública diretamente.

## Preparando a API Rust

Tenha pelo menos um endpoint de saúde. Em Axum:

```rust
use axum::{routing::get, Router};
use std::net::SocketAddr;

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

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

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

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

O endpoint deve ser barato, sem depender de APIs externas. Se a aplicação usa Postgres ou Redis, crie um segundo endpoint mais profundo, como `/readyz`, para checar dependências internas quando fizer sentido.

## Dockerfile Multi-Stage

Um Dockerfile mínimo e eficiente:

```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 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     && 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"]
```

Para projetos maiores, substitua a etapa de build por `cargo-chef`, como no guia de [builds Docker otimizados](/blog/rust-docker-builds-otimizados-producao-2026/). O importante é não rodar a aplicação final dentro da imagem `rust:*`.

## docker-compose.yml de Produção

Mesmo com um único serviço, `docker compose` ajuda a padronizar restart, env file e rede:

```yaml
services:
  rust-api:
    image: registry.example.com/minha-api:2026-05-19
    container_name: rust-api
    restart: unless-stopped
    env_file:
      - /etc/minha-api/minha-api.env
    ports:
      - "127.0.0.1:3000:3000"
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/healthz"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 10s
```

O arquivo `/etc/minha-api/minha-api.env` deve ficar fora do repositório:

```bash
DATABASE_URL=postgres://app:senha@postgres.internal:5432/minha_api
RUST_LOG=info,tower_http=warn
APP_ENV=production
```

Nunca coloque secrets no Dockerfile, no `docker-compose.yml` versionado ou no histórico do shell.

## systemd para Subir no Boot

Crie `/etc/systemd/system/minha-api.service`:

```ini
[Unit]
Description=Minha API Rust
After=docker.service network-online.target
Requires=docker.service
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/minha-api
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target
```

Depois:

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now minha-api
sudo systemctl status minha-api
```

`systemd` garante que a stack volte após reboot. O `restart: unless-stopped` no Compose cuida de crashes do container.

## Nginx como Reverse Proxy

Um bloco básico com proxy headers e timeouts:

```nginx
server {
    listen 80;
    server_name api.exemplo.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;
        proxy_read_timeout 30s;
        proxy_connect_timeout 5s;
    }

    location /healthz {
        proxy_pass http://127.0.0.1:3000/healthz;
        access_log off;
    }
}
```

Depois use Certbot ou seu gerenciador preferido para TLS:

```bash
sudo certbot --nginx -d api.exemplo.com
```

Para APIs públicas, adicione rate limit no Nginx e limite tamanho de body:

```nginx
client_max_body_size 2m;
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

location / {
    limit_req zone=api_limit burst=20 nodelay;
    proxy_pass http://127.0.0.1:3000;
}
```

## Deploy e Rollback

Um deploy manual seguro pode ser apenas:

```bash
cd /opt/minha-api
docker compose pull
docker compose up -d
curl -fsS https://api.exemplo.com/healthz
```

Para rollback, mantenha a tag anterior no `docker-compose.yml` ou em um arquivo `.env`:

```bash
APP_IMAGE=registry.example.com/minha-api:2026-05-18
docker compose up -d
curl -fsS https://api.exemplo.com/healthz
```

Evite `latest` em produção. Tags imutáveis por commit SHA ou data facilitam auditoria e rollback.

## Logs e Observabilidade

Com logs JSON via `tracing`, você consegue inspecionar rapidamente:

```bash
docker logs --tail=100 -f rust-api
journalctl -u minha-api -n 100 --no-pager
```

Para serviços em crescimento, envie logs para Loki, Datadog ou outro agregador. Métricas básicas podem começar com contador de requisições, latência p95 e erros 5xx. O artigo de [logging e observabilidade em Rust](/artigos/logging-observabilidade/) mostra a base com `tracing`, OpenTelemetry e Prometheus.

## Checklist de Produção

Antes de apontar tráfego real, confirme:

- [ ] container não roda como root;
- [ ] secrets ficam em `/etc/minha-api/*.env`, cofre ou secret manager;
- [ ] `/healthz` responde 200;
- [ ] Nginx usa HTTPS e encaminha `X-Forwarded-*`;
- [ ] `RUST_LOG` está em nível adequado;
- [ ] rollback foi testado pelo menos uma vez;
- [ ] backups e migrações de banco foram definidos;
- [ ] monitoramento externo alerta se `/healthz` falhar;
- [ ] portas internas não estão expostas publicamente.

## Erros Comuns

### Rodar `cargo run --release` no servidor

Funciona para teste, mas mistura toolchain, código-fonte e runtime em produção. Gere imagem ou binário versionado.

### Expor a porta 3000 para a internet

Prefira `127.0.0.1:3000:3000` no Compose e deixe Nginx ser a única entrada pública.

### Usar `latest`

`latest` torna deploys não reprodutíveis. Use `minha-api:<sha>` ou `minha-api:2026-05-19`.

### Health check que depende de terceiros

Se `/healthz` chama gateway de pagamento, API externa ou LLM, você transforma instabilidade externa em restart interno. Separe health básico de readiness profundo.

## Conclusão

Rust já entrega binários rápidos e confiáveis; o deploy precisa preservar essas qualidades. Uma VPS com Docker, systemd e Nginx é uma base simples para produção: barata, debuggável e com rollback claro. Quando o tráfego ou a equipe crescerem, a mesma disciplina — imagem pequena, health check, logs estruturados e tags imutáveis — migra bem para Kubernetes ou plataformas gerenciadas.

Esse tipo de experiência também ajuda em [vagas Rust](/vagas/) de backend, plataforma e infraestrutura, principalmente quando você consegue explicar trade-offs de operação e não apenas sintaxe da linguagem. Se a empresa mantém serviços em várias linguagens, vale comparar o mesmo desenho com o ecossistema do <a href="https://golang.com.br/aprenda/golang-docker/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go Brasil para Docker e deploy</a>, porque Go segue muito forte em control planes, CLIs e serviços cloud native enquanto Rust ganha espaço em componentes que pedem binário enxuto, segurança de memória e controle fino de recursos.

O próximo passo é automatizar esse fluxo com CI/CD, gerar tags por commit e publicar a imagem apenas depois de `cargo test`, `cargo clippy` e build de release passarem. Para transformar isso em portfólio, publique um repositório pequeno com README de arquitetura, Dockerfile, Compose, unidade systemd e um postmortem simulado de rollback. Depois, conecte esse projeto à sua página de [empresas que usam Rust](/empresas/) e mostre que você entende deploy, observabilidade e manutenção, não só código local.
