Self-Hosting Guide
Everything here applies to any fork or clone. Nothing assumes a particular hosting provider — the frontend is a static Vite build that deploys the same way to Amplify, Vercel, Netlify, or Cloudflare Pages, and the collector is a Node script that runs the same way from the committed GitHub Actions cron or any other scheduler.
Local development
Section titled “Local development”- Install Docker Desktop and make sure it’s running.
- Run
npx supabase start— boots a local Postgres/Auth/REST stack with all migrations and seeds. - Point local
.envat the printedAPI_URL,ANON_KEY, andSERVICE_ROLE_KEY. - Create a local login user and allowlist row (the local stack starts with no users):
Terminal window curl -X POST "http://127.0.0.1:54321/auth/v1/admin/users" \-H "apikey: <local service_role key>" \-H "Authorization: Bearer <local service_role key>" \-H "Content-Type: application/json" \-d '{"email":"you@example.com","password":"<a local-only password>","email_confirm":true}'INSERT INTO public.dashboard_users (email, note)VALUES ('you@example.com', 'local dev')ON CONFLICT (email) DO NOTHING;
1. Create your Supabase project
Section titled “1. Create your Supabase project”- Create a Supabase project for your dashboard instance.
- Apply migrations in
supabase/migrations/. - Run
supabase/seed.sql(providers registry), then insert your own project rows — use a git-ignoredsupabase/seed.local.sqlso real slugs, names, and URLs never land in git. - In Supabase Auth, disable public signup if you want single-owner access.
- Create your Auth user manually in the Supabase dashboard.
- Add the same email to the allowlist:
INSERT INTO public.dashboard_users (email, note)VALUES ('you@example.com', 'owner')ON CONFLICT (email) DO NOTHING;
You manually create the login user in Supabase. The app checks that signed-in user’s email
against VITE_DASHBOARD_ALLOWED_EMAIL, and the database checks the same email against
dashboard_users through RLS. To allow more than one person in, use a comma-separated list and
add each email to both gates.
2. Configure the collector
Section titled “2. Configure the collector”Real IDs and secrets never belong in git — put them in .env files or your scheduler’s secret
store.
- Copy
projects.example.jsontoprojects.config.json(git-ignored). - Replace placeholder URLs and resource IDs with your own. Any field can use
${ENV_VAR}placeholders — the collector resolves them from the environment at runtime. - Add secrets to your local env or scheduler.
- Run
npm test, thennpm run collect:status. - After it works locally, add the same config/secrets to your scheduled workflow.
See Environment Variables for the full list of required and optional secrets.
3. Set frontend secrets
Section titled “3. Set frontend secrets”Set these in your hosting provider’s environment variables:
VITE_SUPABASE_URLVITE_SUPABASE_ANON_KEYVITE_DASHBOARD_ALLOWED_EMAIL
4. Deploy the static site
Section titled “4. Deploy the static site”npm run buildOutput directory: dist
Any static host works — Amplify, Vercel, Netlify, Cloudflare Pages. Most hosts also offer branch-level access control as an optional first layer; keep Supabase Auth and RLS regardless.
5. Add the dashboard to itself
Section titled “5. Add the dashboard to itself”The dashboard can track itself: add a row for it in your projects table, and set
"hubSupabase": true in the collector config so the self-health collector targets it.
6. Run the scheduled collector
Section titled “6. Run the scheduled collector”The committed .github/workflows/collect.yml runs npm run collect:status on a daily cron and
works as-is on any fork — just add the required secrets. Any other scheduler that can run
npm run collect:status works the same way.
7. Verify
Section titled “7. Verify”npm testnpm run lintnpm run buildThen open the deployed site and check:
- Signed out → login form appears
- Wrong email → sign-in is rejected
- Allowed email → dashboard renders
- Browser anon queries → RLS only returns rows for allowlisted users