ADR-0003 Destino de deploy
Destino de deploy padrão da empresa — contentores Docker com Bun, self-hosted.
- Decisão
- Docker, self-hosted, em BunEntregar e operar
- Estado
- Aceite
- Data
- Âmbito
- Predefinição da empresa para todas as novas apps
Ligações
Apoia-se em
Citado por
- 0002Framework de aplicação
- 0004Camada de API
- 0006Runtime e toolchain
- 0007Base de dados
- 0008ORM / camada de queries
- 0009Autenticação
- 0010Limitação de pedidos
- 0015Observabilidade
- 0017Reverse proxy e TLS
- 0025SDK de IA
- 0026Estrutura do projeto
- 0028Jobs em segundo plano e agendamento
- 0029Email transacional
- 0030Armazenamento de ficheiros e objetos
- 0035Runtime e toolchain
Contexto
Seção intitulada “Contexto”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.
Opções consideradas
Seção intitulada “Opções consideradas”1. Vercel
Seção intitulada “1. Vercel”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
2. Cloudflare (Workers/Pages)
Seção intitulada “2. Cloudflare (Workers/Pages)”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
pgfunciona comnodejs_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
3. Contentor Docker (self-hosted), runtime Bun
Seção intitulada “3. Contentor Docker (self-hosted), runtime Bun”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.jsonnos 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
Decisão
Seção intitulada “Decisã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.
Consequências
Seção intitulada “Consequências”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 upespelha exatamente a produção, com a versão do Bun fixada empackage.jsone 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
packageManagerempackage.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.

