Pular para o conteúdo

As nossas apps são React + TanStack Start (ADR-0001, ADR-0002) a correr em Bun (ADR-0035) — full-stack: SSR, server functions e as rotas da API (ADR-0036) saem de uma única base de código. O destino de deploy é decidido antes da base de dados, da limitação de pedidos e da autenticação porque condiciona todas elas:

  • Base de dados — os edge isolates (Cloudflare Workers) conseguem abrir sockets TCP para o Postgres mas não conseguem manter um pool de ligações de longa duração, por isso precisam de um proxy de pooling (Hyperdrive); os nossos contentores fazem pooling diretamente.
  • Limitação de pedidos — a Vercel e a Cloudflare oferecem primitivas nativas de edge; nos nossos contentores construímo-la nós.
  • Autenticação — o armazenamento de sessões e o comportamento do middleware diferem consoante a plataforma.
  • Modelo de custos — preço por pedido (serverless) vs. mensalidade fixa (contentores) muda o que significa “barato” à nossa escala.

O TanStack Start faz deploy em todos os candidatos (existem adaptadores/presets oficiais para cada um), pelo que a escolha é sobre a plataforma, não sobre compatibilidade de framework.

Serverless independente de framework: funções Node e edge, previews git por app, zero-ops.

Prós

  • Zero trabalho de infraestrutura; deploy a cada push, preview por PR
  • Runtime Node disponível → as ligações diretas ao Postgres funcionam bem
  • Boa DX: logs, analytics, rollback no dashboard
  • Escala de zero para cima sem ser preciso pensar nisso

Contras

  • A opção mais cara com tráfego sustentado; preços por pedido e por largura de banda
  • Os limites de execução das funções (duração, memória) condicionam o desenho da app
  • Pressão de vendor lock-in em torno das funcionalidades da plataforma
  • O custo para toda a equipa cresce com o número de licenças

O TanStack Start faz deploy na Cloudflare através do plugin do Vite; runtime edge de isolates V8.

Prós

  • A mais barata em escala; escalão gratuito generoso
  • Baixa latência global por predefinição
  • Primitivas de plataforma robustas para ADRs futuros: KV, D1, R2, Durable Objects, Queues, limitação de pedidos nativa

Contras

  • Sem pool de ligações de longa duração — os Workers conseguem abrir sockets TCP (o pg funciona com nodejs_compat), mas os isolates têm vida curta, pelo que o Postgres precisa do Hyperdrive à frente para fazer pooling. O maior efeito em cascata sobre o ADR da base de dados.
  • Restrições dos isolates V8: superfície da API Node limitada, sem ligações de longa duração, limites de tempo de CPU
  • Os isolates são um runtime à parte, com as suas próprias particularidades — não o runtime Bun que uniformizámos (ADR-0035)
  • Depuração e observabilidade mais fracas do que num processo de servidor simples

O servidor TanStack Start a correr em Bun, empacotado como imagem Docker, executado na nossa própria infraestrutura (VM, Fly.io/Railway/Render ou qualquer host de contentores). Esta é a decisão abaixo.

Prós

  • Um runtime de servidor completo: qualquer driver de base de dados, qualquer pacote npm que o Bun suporte, websockets, pedidos longos
  • O mesmo runtime em dev, CI e prod — a versão do Bun fixada em package.json nos portáteis e no CI, a tag de imagem correspondente no Dockerfile (ADR-0035)
  • Custo fixo e previsível; o mais barato com carga pequena e estável
  • Sem restrições de plataforma — todas as decisões seguintes (BD, limitação de pedidos, filas) têm liberdade máxima
  • Pode ter deploy em qualquer lado; o menor lock-in; a mesma imagem corre localmente, no CI e em produção

Contras

  • A operação fica por nossa conta: gestão de processos, TLS, escalabilidade, deploys, health checks
  • Sem previews por PR nem edge global sem trabalho adicional
  • Escalar para além de uma máquina é um projeto, não é só marcar uma opção

O contentor Docker (self-hosted) com o runtime Bun é o destino de deploy padrão da empresa para todas as novas apps. (Alterado em 2026-11-13: este ADR nomeou primeiro o Node LTS; o ADR-0035 substituiu-o pelo Bun.)

O servidor TanStack Start corre em Bun dentro de uma imagem Docker, com deploy onde escolhermos. Os fatores decisivos: total liberdade de runtime para as decisões seguintes (Postgres direto, limitação de pedidos feita por nós, qualquer pacote npm), um único runtime fixado em dev/CI/prod, custo fixo e previsível, sem vendor lock-in, e um único artefacto que corre de forma idêntica num portátil, no CI e em produção. Aceitamos ficar com a operação em troca de máxima liberdade e controlo.

Mais fácil — o que isto desbloqueia para ADRs futuros:

  • Base de dados: Postgres (ou qualquer BD) por TCP direto a partir do contentor da app — sem proxy, sem restrições de edge
  • Limitação de pedidos: construímo-la na app como middleware — controlo total, sem dependência de plataforma
  • Autenticação: qualquer estratégia de sessão funciona (cookies, sessões em BD, JWT) — sem limites de runtime
  • Dev local = prod: docker compose up espelha exatamente a produção, com a versão do Bun fixada em package.json e na tag de imagem do Dockerfile (ADR-0035)

Mais difícil — o que passamos a ter a cargo:

  • A terminação de TLS, os health checks, os rolling deploys e a política de reinício são da nossa responsabilidade (compose / reverse proxy / orquestrador)
  • O escalonamento horizontal exige um plano explícito quando um produto ultrapassar uma só máquina
  • Os ambientes de preview por PR têm de ser construídos, se os quisermos
  • Ficamos com a imagem do Bun e o seu ritmo de atualização (ADR-0035); a sua tag tem de corresponder a packageManager em package.json, e o Renovate atualiza ambos no mesmo pull request (ADR-0016)

Do que abdicamos: da DX zero-ops da Vercel e do edge global gratuito da Cloudflare. Aceite — o controlo, a consistência do runtime e a previsibilidade de custos importam-nos mais do que a conveniência gerida nesta fase.