Logosulfite.app
rafagazani/sulfite 999999

Deploy: Cluster (RFC-037)#

Múltiplas réplicas stateless atrás de um load balancer. Todo o estado fica no Postgres e no S3 — qualquer réplica pode atender qualquer requisição sem afinidade de sessão.

Quando usar: alta disponibilidade, zero downtime em deploys, escala horizontal automática.

Pré-requisitos#

  • Docker Compose com suporte a YAML anchors (versão ≥ 2.20)
  • Postgres acessível por todos os containers
  • Bucket S3 (ou MinIO local, como no exemplo)

Configuração#

cd examples/server
cp .env.cluster.example .env.cluster

Edite .env.cluster — todos os segredos devem ser idênticos em todas as réplicas:

POSTGRES_PASSWORD=<senha-segura>
AUTH_SECRET=<openssl rand -hex 32>
EDIT_TOKEN_SECRET=<openssl rand -hex 32>
ENCRYPTION_KEY=<openssl rand -base64 32>

# MinIO local (S3 compatível)
MINIO_USER=minioadmin
MINIO_PASSWORD=minioadmin
S3_BUCKET=sulfite-shares

Subindo com Docker Compose#

docker compose -f docker-compose.cluster.yml --env-file .env.cluster up --build

Isso sobe:

  • Traefik (porta 8080) — load balancer com health checks
  • MinIO (porta 9000) — S3 compatível local
  • Postgres — banco compartilhado
  • api1, api2, api3 — 3 réplicas do servidor

Verifique que todas as réplicas estão saudáveis:

curl http://localhost:8080/health   # via Traefik (qualquer réplica)

Como funciona#

Cliente → Traefik :8080 → api1 / api2 / api3
                               ↓         ↓
                          Postgres    MinIO (S3)

Cada réplica é completamente stateless. O Traefik distribui as requisições em round-robin com health check automático — se uma réplica cair, o tráfego vai para as demais sem intervenção.

Stores e backends#

StoreBackendEnv var
RelatóriosPostgresSTORAGE_BACKEND=postgres
Grupos de relatórios Postgres mesmo DATABASE_URL dos relatórios
Tokens JWTPostgresTOKEN_STORE_BACKEND=postgres
Job queuePostgresJOB_QUEUE_BACKEND=postgres
OTP codesPostgresOTP_STORE_BACKEND=postgres
Share linksPostgresSHARE_LINK_STORE_BACKEND=postgres
Binários de shareS3/MinIOSHARE_OUTPUT_STORE_BACKEND=s3
Rate limitPostgresRATE_LIMIT_STORE_BACKEND=postgres
IPs banidosPostgresBANNED_IP_STORE_BACKEND=postgres
Conexões Postgres ou hub remoto CONNECTION_REGISTRY_BACKEND=postgres ou remote

Cluster mode safety check#

SULFITE_CLUSTER_MODE=true ativa uma verificação no boot: o servidor recusa iniciar se qualquer store estiver configurado com backend memory, file ou sqlite. Isso previne dados inconsistentes entre réplicas. Para conexões, use postgres ou remote.

Exemplo de erro:

StateError: Cluster mode safety check failed:
  - TOKEN_STORE_BACKEND=memory is not safe for multi-container deployments.
    Set TOKEN_STORE_BACKEND=postgres.

Secrets compartilhados#

AUTH_SECRET, EDIT_TOKEN_SECRET e ENCRYPTION_KEY precisam ser idênticos em todas as réplicas. Um token gerado pela api1 precisa ser validável pela api2 e api3.

Migrações de grupos de relatórios#

Em cluster mode o servidor valida o schema no boot, mas não cria tabelas. Antes de subir as réplicas, aplique a migration de grupos de relatórios no Postgres compartilhado:

psql "$DATABASE_URL" \
  -f packages/sulfite_server/migrations/005_report_groups.sql

Se report_groups, report_group_memberships ou report_groups.color_hex estiverem ausentes, a réplica falha cedo com uma mensagem de migration pendente.

S3 externo (AWS, Cloudflare R2, Backblaze)#

Substitua as variáveis MinIO pelas do seu provedor:

# AWS S3
S3_ENDPOINT=https://s3.us-east-1.amazonaws.com
S3_REGION=us-east-1
S3_BUCKET=sulfite-shares-prod
S3_ACCESS_KEY=AKIA...
S3_SECRET_KEY=...

# Cloudflare R2
S3_ENDPOINT=https://<account>.r2.cloudflarestorage.com
S3_REGION=auto

Deploy em produção#

Para Render, Kubernetes ou ECS, veja Escala horizontal — inclui exemplos de render.yaml e manifests Kubernetes.

Ver também#