Skip to main content

Documentation

Self-hosting guide

CivicLedger is a TanStack Start application backed by a Postgres database with row-level security. Everything below runs on infrastructure you control.

1What you need

  • Node.js 20+ (or Bun 1.1+) and Git on the build machine.
  • A Postgres database with the Supabase stack (Auth + PostgREST). Either Supabase Cloud or a self-hosted Supabase deployment via docker compose.
  • A host for the web app: any Node server, container platform, or an edge runtime such as Cloudflare Workers.
  • An SMTP provider if you want password resets and email notices.

2Get the code

git clone <your-fork-url> civicledger
cd civicledger
npm install     # or: bun install

3Stand up the database

For a fully self-hosted backend, run the official Supabase Docker bundle, which brings Postgres, GoTrue (auth), PostgREST (the data API) and Storage:

git clone --depth 1 https://github.com/supabase/supabase
cd supabase/docker
cp .env.example .env      # set POSTGRES_PASSWORD, JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY
docker compose up -d

Then apply the CivicLedger schema. Every migration lives in supabase/migrations and is plain SQL, applied in filename order:

npx supabase link --project-ref <ref>   # cloud
npx supabase db push

# or, against any Postgres URL:
for f in supabase/migrations/*.sql; do psql "$DATABASE_URL" -f "$f"; done

The migrations create the bodies, seats, item types, numbering rules, vote rules, legislative files, meetings, agendas, motions, votes, minutes and audit tables, plus the row-level-security policies and the assign_item_number numbering function.

4Configure environment variables

Browser-visible values are prefixed VITE_. Everything else is server-only and must never be exposed to the client.

# .env
VITE_SUPABASE_URL=https://db.your-domain.gov
VITE_SUPABASE_PUBLISHABLE_KEY=<anon/publishable key>

SUPABASE_URL=https://db.your-domain.gov
SUPABASE_PUBLISHABLE_KEY=<anon/publishable key>
SUPABASE_SERVICE_ROLE_KEY=<service role key — server only>

The service-role key bypasses row-level security. Keep it in your host's secret store, never in the repository or in any VITE_ variable.

5Build and run

npm run dev             # local development on http://localhost:8080
npm run build           # production build
node .output/server/index.mjs   # serve the build

The build output is a standard server bundle plus static assets. To deploy to Cloudflare Workers instead, keep the default Nitro Cloudflare preset and publish with wrangler deploy. Put the app behind TLS and your municipality's domain.

6Create the first administrator

Register through the staff sign-in page, then grant yourself the admin role directly in the database — roles are stored in a dedicated user_roles table, never on the profile, so they cannot be escalated from the client:

insert into public.user_roles (user_id, role)
select id, 'admin' from auth.users where email = 'clerk@your-city.gov';

After that, every other role (clerk, attorney, department staff, board member, viewer) is assigned from the administration portal.

7Configure your municipality

Sign in and work through Administration in order:

  1. Municipality settings — name, clerk contact, notice days, posting deadline.
  2. Bodies — council, commissions and boards, with seat counts and quorum rules.
  3. Members and seats — who occupies each seat, for roll call and voting.
  4. Item types — ordinance, resolution, proclamation; readings and hearing rules.
  5. Vote rules — fraction, basis, abstention handling, recusal treatment.
  6. Numbering — patterns such as {YYYY}-{seq:000} and reset policy.

8Backups and operations

  • Nightly logical backups: pg_dump --format=custom, retained off-site for at least as long as your records-retention schedule requires.
  • Generated agenda packets and minutes are versioned; every download is logged in document_exports with its version and filename for the official record.
  • The audit_log table records number assignments and workflow actions — never truncate it.
  • Upgrades: pull, run new migrations, rebuild, restart. Migrations are additive and ordered.

9Licensing and support

Self-hosted instances receive no warranty. Before adopting CivicLedger as the system of record, confirm with your attorney that the retention, notice and open-meeting requirements of your jurisdiction are satisfied by your configuration.