SaaS Starter

Docker

Levantá el stack completo (Postgres, Redis, server, client) con un solo comando para desarrollo local o builds production-like.

El repo trae dos archivos Compose. Elegí el que coincida con lo que necesitás:

ArchivoPropósitoQué incluye
docker-compose.dev.ymlDesarrollo local. Hot reload, source mounted, secrets efímeros.Postgres 16 · Redis 7 · server (Bun + --hot) · client (next dev)
docker-compose.ymlBuild production-like. Imágenes standalone, sin source mount, sin DB.imagen del server · imagen del client (vos traés tu Postgres + Redis)

Si querés probar el boilerplate end-to-end sin provisionar nada, usá docker-compose.dev.yml — levanta el stack completo.

Quick start (desarrollo)

Desde la raíz del repo:

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

Ese único comando no buildea nada — pulla imágenes públicas y montea tu working tree. Esperá ~30 segundos para que bun install termine adentro de ambos containers.

Obtenés:

ServicioURL / PortNotas
Clienthttp://localhost:3004Next.js con hot reload
Serverhttp://localhost:3005Express en Bun, hot reload vía bun --hot
Postgrespostgresql://postgres:postgres@localhost:5432/boilerplateUsuario/pass/db son seedeados por el archivo compose
Redisredis://localhost:6379Usado por BullMQ + store de rate-limit

El server lee su config desde variables de entorno horneadas en el archivo compose — no se necesita .env para arrancar.

[!NOTE] El container del client necesita dos URLs de la API en compose: NEXT_PUBLIC_API_URL=http://localhost:3005 para el browser, y INTERNAL_API_URL=http://server:3005 para los fetches del middleware / RSC de Next.js que corren dentro del container. Setups single-host (Vercel, bare-metal) solo necesitan NEXT_PUBLIC_API_URL — el middleware cae a esa cuando INTERNAL_API_URL no está seteada.

Primer run — aplicar migrations

El archivo compose arranca el server pero no corre las migrations de Prisma automáticamente (para que puedas elegir la estrategia correcta para tu branch). En el primer boot, en una segunda terminal:

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

Para un schema fresco durante desarrollo podés usar migrate dev en su lugar — eso crea una migration nueva desde tu schema.prisma actual:

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

Primer run — seed de roles + permissions

El boilerplate trae un script de seed que crea los roles default owner / admin / member para el módulo IAM. Corrélo una vez:

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

Listo. Registrate en http://localhost:3004/register y vas a aterrizar en el dashboard.

Comandos comunes

# Tail de logs del server (el más útil)
docker compose -f docker-compose.dev.yml logs -f server

# Abrir un shell dentro del container del server (debug Prisma, correr scripts, etc.)
docker compose -f docker-compose.dev.yml exec server sh

# Conectarse a Postgres con psql
docker compose -f docker-compose.dev.yml exec postgres \
  psql -U postgres -d boilerplate

# Parar todo (los containers quedan — `up` la próxima vez es más rápido)
docker compose -f docker-compose.dev.yml stop

# Bajar todo INCLUYENDO el volume de Postgres (destruye data)
docker compose -f docker-compose.dev.yml down -v

Servicios opcionales

El compose de dev mantiene la surface chica intencionalmente. Si necesitás los adapters opcionales que vienen con el boilerplate, agregalos vos mismo:

  • BullMQ Bull Board — arranca dentro del container del server automáticamente cuando REDIS_URL está seteado; visitá http://localhost:3005/admin/queues.
  • Mailpit / MailHog para capturar emails de dev — agregá un service block, después seteá EMAIL_FROM=dev@local y (si usás Resend) dejá RESEND_API_KEY sin definir para que el LogEmailProvider caiga.
  • MongoDB — el archivo compose tiene un block mongodb comentado. Descomentalo, seguí ADR-002 para cambiar el provider de Prisma, y actualizá DATABASE_URL.

Build production-like

El otro archivo compose (docker-compose.yml) buildea imágenes inmutables de server y client — útil para verificar que tu código pasa el mismo build pipeline que CI usa, o para pushear a un registry.

# Provisioná tu propio Postgres + Redis primero, después exportá sus URLs:
export DATABASE_URL=postgresql://...
export REDIS_URL=redis://...
export BETTER_AUTH_SECRET=$(openssl rand -hex 32)

docker compose up --build

Esto no incluye una database — el compose de prod asume que estás corriendo contra Postgres managed (Supabase, Neon, RDS, etc.) y Redis managed (Upstash, ElastiCache, etc.). Ver la guía de Supabase para un setup de un solo vendor.

La imagen del server corre prisma migrate deploy en el boot, así que las migrations se aplican automáticamente contra el DATABASE_URL configurado.

Troubleshooting

port is already allocated — algo en tu host está usando 3004, 3005, 5432, o 6379. O paralo o remapeá el port host-side en el archivo compose ("3104:3004").

El server arranca pero cada request da 500 con "Cannot find module '@prisma/client'" — el cliente de Prisma no se generó. El container de dev corre bun install en el boot que dispara el postinstall hook; si compitió con la primera request, reiniciá el server: docker compose -f docker-compose.dev.yml restart server.

El hot reload no levanta cambios en macOS — el filesystem polling de Docker Desktop es lento. El archivo compose montea todo el repo con volumes: - .:/app; si ves lag, cambiá Docker Desktop a VirtioFS (Settings → General → Virtual Machine Manager).

Las migrations fallan con "database does not exist" — el healthcheck de Postgres reporta ready antes de que la database boilerplate se cree en el primer boot. Esperá 5 segundos y reintentá el comando migrate.

bun install corre cada vez y es lento — eso es esperado: el compose montea un volume para node_modules para que persista entre restarts, pero el primer install paga el costo completo de red. Boots subsiguientes reusan el volume.

Un container desapareció a mitad de corrida y volvió solo — server, worker y client llevan restart: unless-stopped en el compose de dev. Es intencional: suites E2E pesadas pueden OOM-killear el proceso de Bun bajo flujos de auth concurrentes + encolados de BullMQ, y el auto-restart te ahorra correr docker start a mano. Si un container reinicia repetidamente, mirá los logs (logs -f <service>) — tenés un crash real, no un one-off.

En esta página