RAMSIO

Docs

Deployment

The repo ships Dockerfiles for both apps and a docker-compose stack for local Postgres + Valkey. This page covers the generic shape of each documented deploy target — not a specific account’s infrastructure.

Generate secrets first

Four random secrets, all required before the API will start. See Environment Variables for what each one gates.

openssl rand -base64 32   # JWT_SECRET
openssl rand -base64 32   # JWT_REFRESH_SECRET
openssl rand -base64 32   # ENCRYPTION_KEY
openssl rand -base64 32   # COOKIE_SECRET

Each one needs to be at least 32 characters — the boot-time check below rejects anything shorter, along with the placeholder values shipped in .env.example.

Deploy targets

Railway (recommended)

Built-in PostgreSQL and Redis. Add the API service and the web service separately, run the database migration once from the API service shell (or the Railway CLI), then deploy.

Render

Same shape as Railway — a Web Service per app, a managed PostgreSQL and a managed Redis instance, migrations run once from the Render shell on the API service.

Docker / VPS

Clone, configure .env, and bring up Postgres, Valkey, the API, and the web app with Docker Compose, behind whatever reverse proxy you put in front (an example Nginx config ships in the guide) for TLS termination. Point your payment provider webhooks at the proxied HTTPS URL, not the container port directly.

Health & metrics endpoints

Four health endpoints, each meant for a different consumer — a load-balancer probe interval or several replica pods sharing a rate-limit key can otherwise push probe traffic over the default limit and get 429’d, which for a k8s liveness/readiness probe means pods get killed or pulled from rotation for being “unhealthy” when they’re actually fine — which is why these four routes are explicitly exempted from rate limiting.

EndpointUse for
GET /health/liveKubernetes-style liveness probe — process is up, no dependency checks, always 200.
GET /health/readyKubernetes-style readiness probe — 503 if the database is unreachable, so traffic is pulled from this instance.
GET /healthDetailed JSON status (database + Valkey connectivity) for ops dashboards. Always returns 200, even when the body reports status: "error" or "degraded" — not useful for anything that only checks the HTTP status code.
GET /health/pingTrivial liveness check — returns { message: "pong" }.

GET /metrics exposes a Prometheus-format scrape endpoint (unauthenticated, unversioned, excluded from the generated API docs).

Boot-time checks

The API validates its environment at startup and exits immediately if any of these are true — a fast, loud failure instead of a live deployment quietly running on placeholder secrets:

  • A critical variable is missing (DATABASE_URL, JWT_SECRET, JWT_REFRESH_SECRET, ENCRYPTION_KEY, COOKIE_SECRET)
  • Any secret still contains a placeholder string, or is under 32 characters
  • DATABASE_URL isn’t a valid PostgreSQL connection string
  • FRONTEND_URL isn’t a valid http/https URL, or PORT/NODE_ENV are missing or invalid

Recommended-but-not-critical variables (Resend, Stripe, Razorpay, the audit-log HMAC secret, API_URL) only print a startup warning when missing — the app still boots, but the feature that depends on them won’t work.

Trusted proxy addresses

Behind a real reverse proxy (nginx-ingress, an ALB, Cloudflare), set TRUST_PROXY_ADDRESSES to that proxy’s own IP/CIDR — never leave it unset in that topology, and never set it to a hop count. Left unset behind a real proxy, a client can forge X-Forwarded-For to get a fresh, never-throttled IP-based rate-limit bucket on every request, which this template’s own security audit confirmed is exploitable against /auth/login. Per-account brute-force lockout is unaffected either way, since that’s tracked by user row, not IP.

Database migrations

cd packages/database
pnpm prisma migrate deploy
pnpm prisma db seed   # first deployment only

Before you launch to real users

The template ships with placeholder branding and structural-only legal pages — rebrand apps/web/src/config/brand.config.ts, replace the placeholder logo assets, and get the legal pages under apps/web/src/app/ (terms, privacy, refund, cookie policy) reviewed by a lawyer for your jurisdiction before they go live — they carry a visible “Placeholder” banner until you do.