feat: supaspin, on-demand throwaway Supabase instances on microk8s #1

Merged
igor merged 1 commits from feat/initial-supaspin into main 2026-09-18 20:03:22 +00:00
Owner

What

supaspin: a terminal app that creates, lists, shows and deletes numbered throwaway Supabase instances on a single-node microk8s server, each with capped disk and memory, an HTTPS hostname, and fixed Postgres and Studio ports. Generic: nothing in it is specific to one app.

Verified live on the target server (Debian 13, microk8s, alongside production Supabase)

  • 6 loop-mounted images per instance at their caps (db 2 GB, storage 1 GB, four at 256 MB); all 12 pods ready; priority supaspin-low; memory limits 5.625 GiB, requests 1.875 GiB
  • HTTPS with a valid Let's Encrypt certificate; / (Studio) and /pg/ (pg-meta) return 404 on the public hostname; auth, rest, graphql, realtime, storage and functions answer
  • Upload, public fetch and image resize through /storage/v1/render/image/...; the file lands inside the instance's storage image
  • Postgres 17.6 on <LAN IP>:544N only (127.0.0.1 refuses); Studio on <LAN IP>:300N
  • Three instances side by side; number 1 reused after delete; saved certificate reused with no new Let's Encrypt order
  • Per-instance mail settings reach the auth pod; saved defaults changed from the menu
  • maralago.ru prisma migrate deploy (12 migrations) and the seed ran against an instance; login works; server-side upload works
  • Delete leaves no namespace, volume record, fstab line, mount or folder; half-created and older-layout instances clean up too; a failed create offers the cleanup
  • mount -a dry run and real run clean; production pods' restart counts unchanged; supa.maralago.ru still answers
  • 30 unit tests pass (python3 -m unittest discover -s tests)

Differences from the plan

  • analytics and realtime memory limits are 768 MiB and 384 MiB, not half of production (512 and 256 MiB): at half they were killed for running out of memory in a loop. Total limits are 5.625 GiB, not 5.25 GiB.
  • imgproxy mounts the storage volume and has no image of its own (6 images, not 7); otherwise image resizing cannot see uploaded files.
  • The one-line install needs a token or a checkout because git.didgibot.com requires sign-in even for public repositories.

Pinned chart: supabase-kubernetes 0.8.0 (Postgres 17.6.1.136).

🤖 Generated with Claude Code

## What `supaspin`: a terminal app that creates, lists, shows and deletes numbered throwaway Supabase instances on a single-node microk8s server, each with capped disk and memory, an HTTPS hostname, and fixed Postgres and Studio ports. Generic: nothing in it is specific to one app. ## Verified live on the target server (Debian 13, microk8s, alongside production Supabase) - 6 loop-mounted images per instance at their caps (db 2 GB, storage 1 GB, four at 256 MB); all 12 pods ready; priority `supaspin-low`; memory limits 5.625 GiB, requests 1.875 GiB - HTTPS with a valid Let's Encrypt certificate; `/` (Studio) and `/pg/` (pg-meta) return 404 on the public hostname; auth, rest, graphql, realtime, storage and functions answer - Upload, public fetch and image resize through `/storage/v1/render/image/...`; the file lands inside the instance's storage image - Postgres 17.6 on `<LAN IP>:544N` only (`127.0.0.1` refuses); Studio on `<LAN IP>:300N` - Three instances side by side; number 1 reused after delete; saved certificate reused with no new Let's Encrypt order - Per-instance mail settings reach the auth pod; saved defaults changed from the menu - maralago.ru `prisma migrate deploy` (12 migrations) and the seed ran against an instance; login works; server-side upload works - Delete leaves no namespace, volume record, fstab line, mount or folder; half-created and older-layout instances clean up too; a failed create offers the cleanup - `mount -a` dry run and real run clean; production pods' restart counts unchanged; `supa.maralago.ru` still answers - 30 unit tests pass (`python3 -m unittest discover -s tests`) ## Differences from the plan - analytics and realtime memory limits are 768 MiB and 384 MiB, not half of production (512 and 256 MiB): at half they were killed for running out of memory in a loop. Total limits are 5.625 GiB, not 5.25 GiB. - imgproxy mounts the storage volume and has no image of its own (6 images, not 7); otherwise image resizing cannot see uploaded files. - The one-line install needs a token or a checkout because git.didgibot.com requires sign-in even for public repositories. Pinned chart: supabase-kubernetes 0.8.0 (Postgres 17.6.1.136). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
igor added 1 commit 2026-09-18 20:03:13 +00:00
Adds a terminal app that creates, lists, shows and deletes numbered Supabase
instances on a single-node microk8s server, for testing and staging copies of
any app. Nothing in it is specific to one app.

- supaspin: single-file Python app (menu and commands: create, list, show,
  env, tunnel, delete, mail, setup). Instance N gets namespace supaspin-N,
  hostname slot N, Postgres on <LAN IP>:544N and Studio on <LAN IP>:300N.
  Lowest free number is reused; a refused create says why.
- Disk: fixed-size ext4 image files, loop-mounted from /etc/fstab. db 2 GB,
  storage 1 GB, functions/snippets/deno/pgsodium 256 MB each. imgproxy mounts
  the storage volume (the chart's own imgproxy volume cannot see uploads, so
  image resizing failed). Empty mount points are made read-only until mounted
  so a pod started early after a reboot cannot initialise a database there.
- Memory: limits 5.625 GiB, requests 1.875 GiB per instance, all 12 services.
  Half of a production setup, except analytics (768 MiB) and realtime
  (384 MiB), which were killed for running out of memory at half of it.
- Priority class supaspin-low (-100, never preempts) on every instance pod.
- Public HTTPS through the existing nginx ingress with Let's Encrypt. The
  Studio and pg-meta routes are cut out of the Kong gateway config by a
  post-renderer, which also pins the db and studio pods to fixed host ports.
- Certificates: delete keeps the TLS secret (with its cert-manager issuer
  annotations) in namespace supaspin-certs and create restores it, so
  create/delete cycles do not hit Let's Encrypt's 5-per-week limit.
- Secrets generated per instance: JWT secret, anon and service keys, Postgres
  and Studio passwords, realtime/meta/logflare keys. The rendered values file
  is removed after install; secrets are never written through kubectl apply.
- Instance .env (mode 600) with the usual Supabase variable names, plus an SSH
  tunnel command and DATABASE_URL help.
- Delete is idempotent and finds the real mounts instead of trusting a list,
  so half-created and older instances clean up completely. A failed create
  offers to run it.
- Mail settings: saved defaults changeable from the menu; create asks to use
  them or enter different ones for that instance.
- install.sh: installs the app; accepts a Gitea token or a local checkout
  because git.didgibot.com requires sign-in even for public repositories.
  uninstall.sh: removes the app and, after confirmation, every instance.
- tests: 30 unittest cases (numbering, ports, JWT signing, values, .env,
  fstab handling, post-renderer, certificate secret handling).
- README: install, first-run setup, menu, tunnel, troubleshooting, uninstall.

Pinned chart: supabase-community/supabase-kubernetes 0.8.0 (Postgres 17.6.1.136).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
igor merged commit cfbc4f1612 into main 2026-09-18 20:03:22 +00:00
igor deleted branch feat/initial-supaspin 2026-09-18 20:03:22 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: public/supaspin#1