Skip to content
BoringStack
GitHub

Why BoringStack

8 min read

Architecture

The MIT templates compose the full stack: apps/api (Bun, Elysia, Drizzle), apps/ui (Vite, React, generated OpenAPI client), infra/compose (Postgres, Valkey, Traefik), and optional infra/bootstrap for VPS bootstrap. Auth, billing, queues, email, env validation, and deploy scripts are wired and documented; custom ESLint plugins enforce folder and contract shape in CI.

auth

sessions, OAuth, verification

ops

deploys, logs, backups

lint

ESLint plugins + validate

First clone

Accounts, API, UI, and Compose boot together.

The first clone includes multi-tenant schema, Stripe webhooks, BullMQ on Valkey, Mailpit overlay, and compose/dev.sh for local Postgres + api-dev + ui-dev.

OpenAPI

bun run generate:api fails on schema drift.

TypeBox routes emit /openapi.json; apps/ui regenerates src/lib/api/schema.d.ts. Drift fails typecheck before review.

Lint

validate is the merge gate.

Custom @boring-stack-pkg/eslint-plugin-* rules encode route, service, env, and queue layout. AGENTS.md states intent; ESLint enforces it.

BoringStack is a good fit if…It is probably the wrong fit if…
You need accounts, auth, billing, email, and background jobs with Postgres + Valkey on your own VPS.You only need a static site or a no-code prototype with no custom backend.
You want Postgres, Valkey, Traefik, and Docker Compose under your control.You want a serverless-first Vercel/Netlify path where the platform owns runtime decisions.
You want API and UI as separate deployable apps with an OpenAPI contract between them.You prefer a single full-stack framework with one app boundary.
You want ESLint rules and validate CI to enforce architecture, including agent edits.You rely on conventions, README prose, and review alone.
You want Compose on one VPS now, with an optional path to managed Postgres or Kubernetes later.You need Kubernetes, multi-region, or managed-everything infrastructure on day one.
apps/api

Auth, queues, and billing hooks in src/api/.

Auth, sessions, OAuth, email, env validation, audit log, billing hooks, and queues are wired and documented.

contract

OpenAPI at /swagger/json.

OpenAPI at /swagger/json feeds the generated UI client; contract drift fails typecheck instead of shipping.

lint

Custom ESLint plugins in validate.

Custom ESLint plugins encode route, service, env, and queue shape. validate is the merge gate.

infra

compose/prod.sh and Traefik TLS.

Compose, Traefik TLS, optional OpenTofu, observability, logs, and backups already have a home.

queues

BullMQ workers on Valkey.

Notification events, dispatch jobs, email, and queues live in the API instead of ad-hoc frontend retries.

cost

Postgres + Valkey on one VPS.

Postgres, Valkey, workers, and web traffic can start on one capable VPS before you buy managed services.

The name is deliberate. Boring means proven primitives wired with explicit boundaries.

Postgres, HTTP APIs, browsers, Docker Compose, TLS, Redis-protocol queues: pieces with long production track records. They are linked in a layout you can operate: compose/dev.sh locally, compose/prod.sh on a VPS, OpenTofu optional for first boot.

The inventory of choices is on Stack at a glance. This page explains repo boundaries and default trade-offs.

Many starters defer infra cost until traffic grows. BoringStack defaults to self-hosted Postgres, Valkey, and Traefik so monthly COGS stays tied to VPS size, not platform usage meters.

The core runtime is open source and self-hostable:

  • Data plane: Postgres + Valkey.
  • Edge and deploy: Docker Compose + Traefik on a VPS.
  • Background work: BullMQ on Valkey.
  • Observability: Prometheus, Grafana, Loki, Promtail, and Alertmanager as an opt-in overlay.
  • Error tracking: hosted Sentry if you want it, self-hosted GlitchTip if margin matters.
  • Email development: Mailpit locally; Cloudflare Email, SMTP, Resend, or SendGrid in production.
  • Provisioning: OpenTofu instead of click-by-click cloud setup.

That does not mean “never use SaaS.” Stripe, Cloudflare, Sentry, Resend, SendGrid, Neon, Supabase, and other managed services can all make sense. BoringStack does not force your core application onto a platform meter before you have revenue. You can start nearly free on one capable VPS, then buy managed services only where the trade-off is worth it.

Monthly cost across stages

Monthly infra cost by tier

Select a traffic tier. The three columns are solution categories (not named vendors), using public rate cards as of 2026-05. See the methodology page for assumptions and math.

MAU

~2k

req / day

80k

egress

50 GB / mo

DB storage

5 GB

~2k MAU, 80k req/day, moderate egress.

BoringStack

VPS + Cloudflare + B2

$8/ mo

You own the box. You own the pager.

Managed-PaaS bundle

10×

Edge functions + managed Postgres

$80/ mo

Vendor lock-in; cold starts; bandwidth meter.

Hosted SaaS platform

25×

All-in-one with per-seat / per-event pricing

$200/ mo

Price scales with users, not with usage.

Prices as of 2026-05. Every figure rounded; comparisons use public list-price rate cards, not enterprise negotiations. See the cost methodology page for the math, the assumptions, and the citations.

Decision pointBoringStackTypical Vercel/serverless starterHosted backend starterOne-off SaaS boilerplate
Deployment pathSingle-host VPS first; optional OpenTofu bootstrapPlatform-first; VPS path is usually DIYHosted service firstVaries by vendor
Core data planePostgres + Valkey under your controlUsually external servicesHosted database/auth/storageVaries
COGS postureOpen-source core; one VPS can carry the whole appUsage-based platform bill grows with trafficVendor pricing becomes part of the app shapeVaries
API/UI contractOpenAPI generated clientOften framework-coupled or hand-rolledSDK/client generated by providerVaries
Auth/billing/queues/emailWired as product infrastructureMostly app-specific assemblyAuth often built in; queues/billing/email varyOften included, quality varies
Architecture enforcementCustom lint plugins and validate gatesMostly conventionsMostly provider boundariesUsually prose and examples
Best forTeams that self-host Postgres/Valkey and want OpenAPI + lint gatesFast frontend-heavy apps on a managed platformApps that fit the provider SDK and hosted DB modelBuying a finished opinionated app shell

The choice is not moral. BoringStack is intentionally biased toward ownership, explicit boundaries, low recurring infra cost, and code that remains understandable after many human and agent edits.

flowchart TB
  subgraph ui [apps/ui]
    spa[Vite + React SPA]
  end
  subgraph api [apps/api]
    http[Bun + Elysia API]
    jobs[BullMQ workers]
  end
  subgraph infra [infra/compose]
    edge[Traefik TLS routing]
    data[(Postgres + Valkey)]
  end
  spa -->|OpenAPI JSON| http
  http --> data
  jobs --> data
  edge --> spa
  edge --> http

Three stacked layers: the apps/ui SPA sits above the apps/api (HTTP server plus BullMQ workers), which sits on the infra layer (Traefik routing in front of Postgres and Valkey). The SPA reaches the API over a typed OpenAPI contract; both the API and the workers read and write the same data plane.

Security, validation, persistence, background jobs, secrets, audit trail. The system of record lives here.

Rendering, interaction, client state, i18n. Vite keeps local feedback fast. The UI calls the API through a generated, typed client.

TLS, routing, databases, queue store, optional metrics and logs. In production, same-origin path routing (/ for the SPA, /api/* for the API) keeps browser security simple. The code boundaries stay separate.

Separation of concerns goes deeper on what each layer delivers and how releases stay independent.

The templates come from codebases that have been in production for many years. File layout, feature anatomy, env discipline, queue patterns, and deploy defaults are decisions you would otherwise make again on every project. You extend what ships. You do not reinvent the spine.

AGENTS.md, CLAUDE.md, and AGENT_CONTRACT.md explain intent. Custom ESLint plugins enforce it. Machine-checked rules survive refactors, new teammates, and agent-generated diffs. They cover where routes live, how env is read, and how queues are shaped. When lint fails, the fix is specific.

Architecture lives in tooling. Prose is context. Read Lint as the contract.

Email, notifications, and other background work already have a home in the API. When volume, team boundaries, or compliance need a dedicated process, you extract a piece and keep the same job contracts. See Background work.