Skip to content
BoringStack
Star

Infra template: overview

4 min read

infra/compose composes apps/api and apps/ui into one running stack. It targets your laptop and a first VPS before you need cluster machinery, while keeping observability, email, and queue tooling close to the app.

The base stack (always on):

  • postgres - Always-on app database
  • valkey - Redis-protocol cache plus BullMQ queue backend
  • traefik - Prod reverse proxy, ACME, and one-domain path routing
  • api-migrate - One-shot schema push plus optional superuser seed
  • api-dev / api - Bun + Elysia in dev, GHCR image in production
  • ui-dev / ui - Vite dev server locally, nginx static image in production

Optional overlays:

  • observability - Prometheus, Grafana, Loki, Promtail, and exporters
  • error tracking - GlitchTip
  • mail and queue tools - Mailpit, Bull Board, WUD image-update detector

The profile system uses environment flags to compose overlays over the base stack:

Terminal window
./dev.sh up -d # default: api + ui + postgres + valkey + observability + glitchtip
STACK=prod ./dev.sh up -d # prod: GHCR images, Traefik, HTTPS via ACME (same defaults)
WITH_OBSERVABILITY=0 ./dev.sh up -d # skip observability
WITH_GLITCHTIP=0 ./dev.sh up -d # skip glitchtip
WITH_BULLMQ=1 ./dev.sh up -d # add bull board (opt-in)
WITH_WUD=1 ./dev.sh up -d # add WUD image-update detector (opt-in)
WITH_MAILPIT=1 ./dev.sh up -d # add Mailpit local SMTP (opt-in)

The default stack includes Prometheus, Grafana, Loki, and GlitchTip. Niche tooling (Bull Board, WUD, Mailpit) is opt-in. See Profiles & overlays.

Under infra/compose/:

compose/ - Compose files and per-profile service overlays

  • docker-compose.yml - Base: postgres, valkey, api-migrate, api/ui, Traefik in prod
  • docker-compose.development-labels.yml - Dev overlay: host-published data ports
  • docker-compose.production-labels.yml - Prod overlay: HTTPS, ACME, path routing, security headers
  • docker-compose.observability.yml - Prometheus, Grafana, Loki, Promtail, exporters
  • docker-compose.glitchtip.yml - GlitchTip dev + prod
  • docker-compose.glitchtip-prod-labels.yml - Prod-only HTTPS + BasicAuth labels
  • docker-compose.bullmq.yml - Bull Board in development
  • docker-compose.wud.yml - WUD image-update detector
  • docker-compose.mailpit.yml - Local SMTP catcher in development
  • dev.sh - Orchestrator
  • .env.example - All knobs in one file
  • prometheus/ - Scrape config
  • grafana/ - Datasource provisioning
  • alertmanager/ - Alert routes
  • promtail/ - Log scrape config
  • glitchtip/ - init-db.sql

scripts/ - Operational wrappers

  • compose-up.sh - bring the stack up
  • compose-down.sh - stop without deleting data
  • compose-down-clean.sh - clean local services intentionally
  • backup-wrapper.example.sh - Postgres to rclone, with retention
  • ufw.example.sh - UFW + Cloudflare IP allowlist
  • glitchtip-bootstrap.sh - Prod GlitchTip migrate + superuser

docs/ - Runbooks and cheatsheets

Two bridge networks. Services are split on purpose:

backend (data plane). postgres, valkey, api[-dev], api-migrate, exporters, glitchtip, and mailpit.

frontend (ingress). traefik in prod, ui[-dev], and glitchtip-web. Traefik joins both when it needs to route.

In prod, Traefik joins both networks so it can route HTTPS from frontend to services on backend. The data plane (Postgres, Valkey) is never exposed to frontend.

Every service has deploy.resources.limits and reservations driven by env vars with sane defaults sized for a 4-vCPU / 8 GB host. Adjust in compose/.env; see Resource limits for sizing guidance.

  • Kubernetes manifests in this (Compose) target. BoringStack is Compose-first, and infra/compose deliberately stays cluster-free so the single-host path is clear. If you run a Kubernetes cluster and want GitOps, the opt-in Kubernetes (k3s) target lives separately under infra/k3s. It never mixes into the Compose flow.
  • Application code. The apps/api and apps/ui own that.
  • Cloud-provider provisioning. The OpenTofu bootstrap in infra/bootstrap handles that.