Repository navigation
ADR Hosting and Backend
status: implemented decided: 2026-08-15 decision: "Bundle 3 — GCP Cloud Run + Neon Postgres + Google Cloud Storage, auth in Next.js (Auth.js)" related_issues: [52, 53, 46]
Status: Implemented Related: Architecture · GitHub #52 (hosting/pipeline fit) · #53 (data/storage fit) · part of #46
The Next.js migration needs a deployment target, and that choice is coupled to the backend/data choice — a VM naturally pairs with self-hosted Postgres, a serverless platform naturally pairs with a managed one. Both tickets are resolved together here as one decision.
Drivers, in priority order:
- Low cost is the explicit goal — this is a low-traffic side project.
- Live app, no manual wake. A 2-3s cold start is fine; a project that goes to sleep and needs a human to click "restore" in a dashboard is not. This is a real failure mode today: Supabase's free tier pauses a project after ~7 days of inactivity, and un-pausing requires a manual dashboard action — there's no API/CLI for it.
- 2 environments (dev + prod).
- Blob storage for uploaded textbook photos, in every bundle.
- Auth is not a hard backend requirement — it can move into the Next.js app itself (e.g. Auth.js) instead of relying on the backend's auth service.
- Vendor lock-in / migration path. Whichever bundle is picked now, it must stay realistically migratable later to a self-hosted VM + Dockerized Postgres setup. Avoid depending on platform-proprietary auth, storage APIs, or RLS mechanics that would block that path.
- Moving toward server-assisted execution (server-side LLM pipeline jobs, transient API-key handling) — the backend needs to support that, not just serve static pages.
- Coding-agent access to dev resources. Looking ahead, coding agents should be able to query the dev DB, dev blob storage, and logs directly for debugging — without SSH tunnels or one-off credential wrangling.
| Cost (dev+prod) |
|
| No-manual-wake | Only on Pro. Free tier pauses after ~7 days idle and requires a manual "Restore project" click — fails the requirement outright. Pro removes pausing, but that's what pushes the cost up. |
| Next.js pipeline fit | Best possible — Vercel is Next.js's own vendor, git-push-to-deploy, automatic preview envs, zero-config for every Next.js feature. |
| Blob storage | Supabase Storage exposes an S3-compatible endpoint (genuinely portable); the native SDK/REST surface is proprietary. |
| Lock-in | RLS itself is vanilla Postgres and ports cleanly. What doesn't port: Supabase's auth helper functions (auth.uid(), auth.jwt()), its anon/authenticated/service_role roles, and storage RLS on storage.objects. Superuser access is restricted, so a straight pg_dumpall extraction doesn't work — migration needs a more surgical dump/restore. |
| Ops burden | Lowest — fully managed on both sides. |
| Agent access to dev resources | Best in class. Supabase publishes an official, actively maintained MCP server covering SQL execution, schema introspection, and — notably — per-service logs (api, postgres, edge functions, auth, storage) through one integrated tool. Supabase's own guidance is to point it at a dev project only, read-only where possible — which lines up exactly with the "dev DB access for debugging" use case here. |
| Cost (dev+prod) | Cheapest researched — roughly $10-16/mo for two small VMs (e.g. two Hetzner CX22s), or one larger box running both envs. Object storage adds a few dollars more if not self-hosting MinIO. |
| No-manual-wake | N/A — nothing to pause, it's always-on (which is also why it's not free). |
| Next.js pipeline fit | No native git-push-to-deploy. Next.js does officially document and support output: "standalone" Docker builds for exactly this case, so it's a real, first-class-documented path — but replicating a Heroku/Vercel-style deploy UX needs a self-hosted PaaS layer on top (Coolify or Dokploy — both free, open-source, Git-based deploys, auto TLS) or a hand-rolled Docker Compose + GitHub Actions pipeline. |
| Blob storage | Self-hosted MinIO (S3-compatible, |
| Lock-in | Lowest by construction — this bundle is the target migration state everything else is being measured against. Plain Postgres in Docker, standard S3 API, Auth.js in-app. No one-way doors. |
| Ops burden | Highest of the three — OS/security patching, Postgres backups, TLS renewal, uptime monitoring all fall on the maintainer (Coolify/Dokploy reduce but don't eliminate this). For a solo side-project maintainer, this is a real ongoing cost, not just a dollar figure. |
| Agent access to dev resources | Worst of the three. There's no purpose-built MCP for "a Postgres container on someone's VM" — an agent would need either a generic Postgres MCP pointed at an exposed DB port (opens the DB to the network, a real security trade-off) or SSH-based access to run psql/docker logs on the box. Both are more setup and more exposure than the managed options. |
| Cost (dev+prod) | Plausibly $0-10/mo at this traffic level — Cloud Run's free tier (2M requests, 360k GiB-seconds, 180k vCPU-seconds/month) comfortably covers a low-traffic side project on both environments; Neon Postgres free tier (0.5GB storage, 100 CU-hours/mo, scale-to-zero) likely covers dev+prod too; blob storage is cents/month at small photo volumes. |
| No-manual-wake | Neon scales to zero after 5 minutes idle and wakes automatically on the next connection — no dashboard click required. This is the key difference from Supabase's free tier and the main reason this bundle satisfies the requirement without paying for an always-on tier. |
| Next.js pipeline fit | No official first-party adapter (unlike Vercel), but the same output: "standalone" Docker pattern applies — needs a Dockerfile + a GitHub Actions pipeline to build, push to a registry, and deploy. One-time setup, well-trodden, not a hack. |
| Blob storage | GCS Standard, S3-interoperable via its XML API — stays aligned with the "portable to MinIO later" goal. (Azure Blob was also researched but doesn't speak S3 natively, which is a lock-in flag against it specifically — this is why GCP is the pick over Azure for this bundle.) |
| Lock-in | Low. Compute is a thin, replaceable Docker-runtime layer (Cloud Run just runs a container image — the same image would run on the VM bundle later with no changes). Neon is wire-compatible Postgres with no proprietary extensions. GCS's S3-interop mode avoids the Azure Blob gap. |
| Ops burden | Low — everything here is fully managed (no OS patching, no DB backup setup), closer to Bundle 1's profile than Bundle 2's. The only upfront cost is writing the Dockerfile + CI pipeline once. |
| Agent access to dev resources | Good, but split across two vendors instead of Supabase's one. Neon has an official MCP server (mcp.neon.tech) for SQL/schema/migrations, with a standout feature: it can run a migration on an instant copy-on-write branch of dev data first, let the agent verify it, then merge — a safer debugging/iteration loop than a direct dev-DB connection. Google Cloud Logging has an official remote MCP server for reading Cloud Run logs. GCS object access is covered either by the community gcloud-mcp (explicitly preview / not an officially supported Google product) or just plain gcloud storage/gsutil CLI calls, which any agent with shell access can already run. Net effect: full coverage of DB, logs, and storage without SSH or exposing a DB port, just stitched from two vendors' tooling instead of one. |
Bundle 3: GCP Cloud Run + Neon Postgres + Google Cloud Storage, with auth handled in the Next.js app (Auth.js) rather than by the backend.
Reasoning, against the priority order above:
- Cost — cheapest realistic option after Bundle 2, and unlike Bundle 2 it doesn't trade dollars for maintainer time. Bundle 1 is 5-6x the cost of this bundle for a worse fit on the wake requirement.
- No manual wake — this is the deciding factor against Bundle 1. Supabase's free tier fails the requirement directly; fixing it means paying for Pro. Neon's scale-to-zero-and-auto-wake solves the same problem at $0.
- Vendor lock-in / migration path — this bundle is nearly as portable as self-hosting outright: the compute layer is just a Docker container (the same one Bundle 2 would run), Neon is standard Postgres, and GCS's S3-interop mode keeps blob storage swappable for MinIO later. Choosing Auth.js from the start (rather than a backend auth service) sidesteps the biggest lock-in risk seen in Bundle 1's research (Supabase's auth helpers/roles baked into RLS policies).
- Ops burden proportionate to a solo side project — Bundle 2 is the "true" end state this ADR is meant to stay migratable toward, but taking on VM patching/backups/TLS/monitoring now, before there's a reason to, isn't worth it yet. Bundle 3 gets most of Bundle 2's portability without that burden.
- Server-assisted execution — Cloud Run is built for exactly this shape of work (containerized, request- or job-triggered server-side execution with transient credentials), which is where the pipeline is headed.
-
Agent access to dev resources — Supabase's single integrated MCP (Bundle 1) is genuinely the most polished option on this dimension alone, and is worth naming as this bundle's one real edge. But Bundle 3 isn't a compromise here, just a different shape: Neon's official MCP covers DB access (with a safer branch-then-merge debugging workflow than a raw dev-DB connection), Google's official Cloud Logging MCP covers logs, and GCS is reachable with either the community
gcloud-mcpor plain CLI calls. None of it requires opening a DB port or SSHing into a box, which is what ruled out Bundle 2 on this criterion. Given this doesn't change the ranking on cost or the wake requirement, it doesn't outweigh them.
The main cost of this choice versus Bundle 1 is a one-time setup cost: a Dockerfile (via Next.js's own documented output: "standalone" mode) and a GitHub Actions pipeline to build/push/deploy, since there's no Vercel-style git-push-to-deploy here.
Unblocks #56 (runtime-agnostic pipeline refactor): the job/config design should target Cloud Run's container + transient-credential execution model, not a Supabase-Edge-Function-shaped one.
- Auth moves from Supabase Auth into the Next.js app (Auth.js). This is new work the migration ticket needs to scope — it isn't free just because the backend no longer forces it.
- Two environments means two Cloud Run services, two Neon projects (or branches — Neon supports branching, worth evaluating during implementation), and two GCS buckets, kept cheap by both Cloud Run and Neon's usage-based/scale-to-zero pricing rather than a fixed per-env cost.
- The eventual move to Bundle 2 (VM + Dockerized Postgres) — if traffic or cost ever justifies it — should be close to a lift-and-shift: same container image,
pg_dump/pg_restorefrom Neon (standard Postgres, no superuser restrictions to work around), and an S3-compatible copy from GCS to MinIO or another S3-compatible target. - This ADR does not cover account setup, provisioning, or the actual migration — that belongs to the Next.js migration ticket this unblocks.
Pricing and feature claims above were researched in August 2026 against vendor docs and pricing pages (Vercel, Supabase, Hetzner, DigitalOcean, Azure Container Apps, GCP Cloud Run, Neon, Azure Database for PostgreSQL, Cloud SQL, Azure Blob Storage, GCS) plus Next.js's own self-hosting/Docker docs. Given how fast cloud pricing pages move, re-verify current numbers against each vendor's own pricing page before provisioning.