SaaS Starter

Docker

Suba o stack completo (Postgres, Redis, server, client) com um único comando para desenvolvimento local ou builds production-like.

O repo entrega dois arquivos Compose. Escolha o que combina com o que você precisa:

ArquivoPropósitoO que inclui
docker-compose.dev.ymlDesenvolvimento local. Hot reload, source montado, secrets efêmeros.Postgres 16 · Redis 7 · server (Bun + --hot) · client (next dev)
docker-compose.ymlBuild production-like. Imagens standalone, sem source montado, sem DB.imagem do server · imagem do client (você traz seu Postgres + Redis)

Se você quer experimentar o boilerplate de ponta a ponta sem provisionar nada, use docker-compose.dev.yml — sobe o stack inteiro.

Quick start (desenvolvimento)

Da raiz do repo:

docker compose -f docker-compose.dev.yml up

Esse único comando não builda nada — puxa imagens públicas e monta sua working tree. Espere ~30 segundos para que bun install termine dentro de ambos os containers.

Você obtém:

ServiçoURL / PortNotas
Clienthttp://localhost:3004Next.js com hot reload
Serverhttp://localhost:3005Express em Bun, hot reload via bun --hot
Postgrespostgresql://postgres:postgres@localhost:5432/boilerplateUsuário/pass/db são seedados pelo arquivo compose
Redisredis://localhost:6379Usado por BullMQ + store de rate-limit

O server lê sua config a partir de variáveis de ambiente embutidas no arquivo compose — sem .env necessário para subir.

[!NOTE] O container do client precisa de duas URLs da API no compose: NEXT_PUBLIC_API_URL=http://localhost:3005 para o browser, e INTERNAL_API_URL=http://server:3005 para os fetches do middleware / RSC do Next.js que rodam dentro do container. Setups single-host (Vercel, bare-metal) só precisam de NEXT_PUBLIC_API_URL — o middleware faz fallback para essa quando INTERNAL_API_URL não está setada.

Primeiro run — aplicar migrations

O arquivo compose inicia o server mas não roda as migrations Prisma automaticamente (para você escolher a estratégia certa para seu branch). No primeiro boot, em um segundo terminal:

docker compose -f docker-compose.dev.yml exec server \
  bunx --cwd apps/server prisma migrate deploy

Para um schema fresco durante desenvolvimento você pode usar migrate dev em vez disso — isso cria uma nova migration a partir do seu schema.prisma atual:

docker compose -f docker-compose.dev.yml exec server \
  bunx --cwd apps/server prisma migrate dev

Primeiro run — seed de roles + permissions

O boilerplate entrega um script de seed que cria os roles default owner / admin / member para o módulo IAM. Rode uma vez:

docker compose -f docker-compose.dev.yml exec server \
  bun apps/server/prisma/seed.ts

Pronto. Cadastre-se em http://localhost:3004/register e você vai cair no dashboard.

Comandos comuns

# Tail dos logs do server (o mais útil)
docker compose -f docker-compose.dev.yml logs -f server

# Abrir um shell dentro do container do server (debug Prisma, rodar scripts, etc.)
docker compose -f docker-compose.dev.yml exec server sh

# Conectar ao Postgres com psql
docker compose -f docker-compose.dev.yml exec postgres \
  psql -U postgres -d boilerplate

# Parar tudo (os containers ficam — `up` da próxima vez é mais rápido)
docker compose -f docker-compose.dev.yml stop

# Derrubar tudo INCLUINDO o volume do Postgres (destrói dados)
docker compose -f docker-compose.dev.yml down -v

Serviços opcionais

O compose dev mantém a surface pequena de propósito. Se você precisa dos adapters opcionais que vêm com o boilerplate, adicione você mesmo:

  • BullMQ Bull Board — sobe dentro do container do server automaticamente quando REDIS_URL está definido; visite http://localhost:3005/admin/queues.
  • Mailpit / MailHog para capturar emails de dev — adicione um service block, depois defina EMAIL_FROM=dev@local e (se usando Resend) deixe RESEND_API_KEY vazio para que o LogEmailProvider assuma.
  • MongoDB — o arquivo compose tem um block mongodb comentado. Descomente, siga ADR-002 para trocar o provider Prisma, e atualize DATABASE_URL.

Build production-like

O outro arquivo compose (docker-compose.yml) builda imagens imutáveis de server e client — útil para verificar que seu código passa pelo mesmo build pipeline que o CI usa, ou para fazer push para um registry.

# Provisione seu próprio Postgres + Redis primeiro, depois exporte suas URLs:
export DATABASE_URL=postgresql://...
export REDIS_URL=redis://...
export BETTER_AUTH_SECRET=$(openssl rand -hex 32)

docker compose up --build

Isso não inclui um database — o compose de prod assume que você está rodando contra Postgres gerenciado (Supabase, Neon, RDS, etc.) e Redis gerenciado (Upstash, ElastiCache, etc.). Veja o guia do Supabase para um setup de um vendor só.

A imagem do server roda prisma migrate deploy no boot, então migrations se aplicam automaticamente contra o DATABASE_URL configurado.

Troubleshooting

port is already allocated — algo no seu host está usando 3004, 3005, 5432, ou 6379. Ou pare ou remapeie o port host-side no arquivo compose ("3104:3004").

O server sobe mas toda request dá 500 com "Cannot find module '@prisma/client'" — o client Prisma não foi gerado. O container dev roda bun install no boot que dispara o postinstall hook; se correu junto com a primeira request, reinicie o server: docker compose -f docker-compose.dev.yml restart server.

Hot reload não pega mudanças no macOS — o filesystem polling do Docker Desktop é lento. O arquivo compose monta o repo inteiro com volumes: - .:/app; se você vê lag, troque o Docker Desktop para VirtioFS (Settings → General → Virtual Machine Manager).

Migrations falham com "database does not exist" — o healthcheck do Postgres reporta ready antes do database boilerplate ser criado no primeiro boot. Espere 5 segundos e tente o comando migrate novamente.

bun install roda toda vez e é lento — esperado: o compose monta um volume para node_modules para que persista entre restarts, mas o primeiro install paga o custo completo de rede. Boots subsequentes reusam o volume.

Um container sumiu no meio da execução e voltou sozinho — server, worker e client têm restart: unless-stopped no compose de dev. É intencional: suites E2E pesadas podem OOM-killar o processo do Bun sob fluxos de auth concorrentes + enfileiramentos do BullMQ, e o auto-restart evita rodar docker start na mão. Se um container reinicia repetidamente, siga os logs (logs -f <service>) — você tem um crash real, não um one-off.

Nesta página