Pular para o conteúdo

Fazendo deploy do Rebase na Hetzner Cloud

A Hetzner Cloud oferece uma relação custo-benefício excepcionalmente boa e é uma excelente escolha para projetos que precisam de soberania de dados europeia, com datacenters em Nuremberg, Falkenstein e Helsinki.

Nada aqui sobre o seu projeto é específico da Hetzner. Um deploy do Rebase consiste em duas partes separáveis — a imagem de runtime publicada e o bundle que o rebase build produz — e o mesmo bundle roda sob o Docker Compose em um laptop, no Rebase Cloud, no Helm chart e em uma máquina da Hetzner. Mudar entre eles é uma mudança de infraestrutura, não da aplicação.

O módulo terraform-hcloud-rebase provisiona o servidor, um firewall, um IP estável e — a parte que importa — um volume que armazena os dados do Postgres, para que a substituição do host não destrua o banco de dados.

module "rebase" {
source = "rebasepro/rebase/hcloud"
domain = "api.example.com"
cors_origins = ["https://app.example.com"]
ssh_public_keys = [file(pathexpand("~/.ssh/id_ed25519.pub"))]
bundle_url = "https://storage.example.com/bundles/app-1.4.0.tar.gz"
s3_bucket = "example-uploads"
s3_access_key_id = var.s3_access_key_id
s3_secret_access_key = var.s3_secret_access_key
}

Uma coisa importante antes do primeiro apply: o registro A para domain já deve apontar para o servidor, caso contrário o desafio Let’s Encrypt do Caddy falhará. O endereço é criado independentemente do servidor, portanto você pode obtê-lo primeiro com terraform apply -target=hcloud_primary_ip.ipv4, configurar o DNS e então aplicar adequadamente.

O restante desta página aborda o mesmo deploy manualmente.

  1. No Hetzner Cloud Console, clique em Add Server.
  2. Escolha uma Location — Falkenstein, Nuremberg ou Helsinki para residência de dados na UE.
  3. Escolha uma Image: Ubuntu 24.04.
  4. Escolha um Type: CPX21 (3 vCPUs / 4GB) é um patamar mínimo viável, CX32 (4 vCPUs / 8GB) é confortável para o runtime mais o Postgres.
  5. Adicione um Volume para o banco de dados. Os dados no próprio disco do servidor são perdidos junto com o servidor.
  6. Adicione sua chave SSH e crie-o.
ssh root@<your-server-ip>
apt update && apt install -y docker.io docker-compose-v2

Não há imagem de aplicação para construir. O rebase build produz um diretório dist-bundle, e a imagem de runtime publicada o executa:

rebase build
rsync -a dist-bundle/ root@<your-server-ip>:/opt/rebase/dist-bundle/

Para um deploy real, prefira uma das duas abordagens que não envolvam copiar arquivos manualmente para o servidor:

  • Embutir em uma imagem — FROM rebasepro/server:0.24.0 e depois COPY dist-bundle /bundle, e faça o deploy alterando uma tag.
  • Servir via HTTP — defina REBASE_BUNDLE_URL e o runtime baixa e descompacta o bundle a cada inicialização. Isso é o que o módulo Terraform acima faz e é o mesmo mecanismo usado pelo Helm chart.

O Rebase fornece um arquivo Compose exatamente para isso: infra/docker/docker-compose.selfhost.yml. É a receita canônica de self-hosting — Postgres e o runtime, com seu bundle montado — e vale a pena lê-lo em vez de apenas copiar, porque seus comentários explicam cada escolha.

Crie o ambiente que ele espera:

POSTGRES_PASSWORD=a_long_random_string
JWT_SECRET=another_long_random_string_at_least_32_chars
REBASE_SERVICE_KEY=a_third_long_random_string_at_least_32_chars
CORS_ORIGINS=https://app.yourdomain.com
REBASE_ADMIN_EMAIL=you@yourdomain.com
REBASE_ADMIN_PASSWORD=at_least_twelve_characters

REBASE_ADMIN_EMAIL e REBASE_ADMIN_PASSWORD são novos: na versão 0.17.3, a primeira conta a se registrar torna-se a administradora, inclusive em produção.

Todas as seis são obrigatórias — o arquivo Compose as declara com ${VAR:?…} e se recusa a fazer a interpolação sem elas.

As duas últimas correspondem ao primeiro administrador. Um banco de dados recém-criado não tem usuários e, fora de produção, o primeiro cadastro é promovido a admin — o que se torna uma condição de corrida no momento em que esta máquina responde em um hostname, pois o Caddy ativa o TLS antes mesmo que você tenha digitado qualquer coisa. Portanto, em produção essa janela é fechada e a conta é especificada aqui; o runtime a cria uma única vez, enquanto a tabela de usuários estiver vazia, e não faz nada nas inicializações seguintes. Faça login e altere a senha.

Em seguida, inicie:

docker compose -f infra/docker/docker-compose.selfhost.yml --env-file .env up -d

O runtime escuta na porta 8080 dentro da rede do Compose.

REBASE_SERVICE_KEY ignora a segurança em nível de linha (row-level security). Trate-a como uma credencial de superusuário do banco de dados, e não como uma chave de API.

Nunca exponha o runtime diretamente. O Caddy provisiona certificados Let’s Encrypt automaticamente; executá-lo como outro serviço do Compose mantém toda a stack em um único arquivo:

caddy:
image: caddy:2-alpine
restart: unless-stopped
ports: ["80:80", "443:443", "443:443/udp"]
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data

Com um Caddyfile assim:

api.yourdomain.com {
reverse_proxy api:8080
}

Aponte o registro A desse domínio para o servidor antes de iniciar o Caddy, caso contrário a solicitação do certificado falhará.

O runtime se recusa a iniciar em produção com armazenamento local configurado, pois o sistema de arquivos do contêiner é destruído a cada reinicialização e um backend local em produção significa perda silenciosa de dados.

O Hetzner Object Storage é compatível com S3 e fica nos mesmos datacenters, sendo a combinação natural:

STORAGE_TYPE=s3
S3_BUCKET=my-uploads
S3_ENDPOINT=https://fsn1.your-objectstorage.com
S3_REGION=fsn1
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...

Se o seu projeto não armazena uploads, defina FORCE_LOCAL_STORAGE=true para confirmar isso explicitamente. Consulte Storage para obter o panorama completo.

Com o REBASE_MIGRATE_ON_BOOT em seu valor padrão de ensure, o runtime provisiona as tabelas das suas coleções e suas políticas de row-level security na inicialização, de forma aditiva. Uma primeira inicialização contra um banco de dados vazio já sobe disponibilizando-os — não há nenhuma etapa de schema para executar antes que o deploy funcione.

O que a inicialização deliberadamente nunca faz é algo destrutivo: ela não altera o tipo de uma coluna, não remove uma coluna nem edita um rótulo de enum existente. O reinício de um contêiner não deve ser capaz de remodelar um schema como efeito colateral.

Uma coisa, portanto, ainda precisa do rebase db push, executado a partir de um checkout ou CI onde a validação de mudanças destrutivas e um backup estejam ao alcance: qualquer alteração que não seja puramente aditiva — uma coluna renomeada, um tipo restringido, um campo removido.

Se o módulo ou o arquivo Compose vincularam o Postgres ao loopback — ambos fazem isso —, acesse-o por meio de um túnel SSH:

ssh -N -L 5433:127.0.0.1:5432 root@<your-server-ip>

Uma porta de banco de dados aberta para a internet é como um deploy do Rebase acaba tendo suas linhas lidas burlando o row-level security em vez de passar por ele.

Altere a tag da imagem e reinicie. Seu bundle permanece intacto, e todos os projetos nesse runtime passam a usar o novo engine.

A exceção é a versão major do Postgres: o Postgres se recusa a iniciar com um diretório de dados gravado por uma versão major mais antiga, portanto essa atualização exige dump e restore, nunca sendo feita in-place.

rebase db backup --out ./backups
# recreate the volume on the new major
rebase db restore ./backups/<file>.dump