The public source release is in preparation, so there is no repository to clone yet. This guide documents the steps the release ships with. Request early access and we will tell you when it is public.
Before you start
You need three things: the Node.js version pinned in the repository's .nvmrc, PostgreSQL 18, and npm.
Keep npm install scripts enabled. On Linux the embedded PostgreSQL package restores its shared-library symlinks during postinstall. Never run PostgreSQL as root.
The repository can start an embedded development cluster with npm run db:server, or you can point it at a PostgreSQL 18 server you manage yourself. The embedded cluster is a development convenience, not a production database.
Supply these through your shell or your process manager. Optional integration keys can stay unset.
| Name | Use |
|---|---|
DATABASE_URL | The PostgreSQL connection string the app and the migration tools use. |
PGPORT | The embedded development cluster's port, when you use npm run db:server. |
NODE_ENV | Set to development for a local exercise. |
You can use any secret manager. Azure Key Vault is not a runtime requirement. Never commit credentials or put them in an example file.
Run it locally
- Install dependencies from the lockfile.
- In a first terminal, start the embedded cluster. Skip this if you run your own PostgreSQL.
- In a second terminal, against a new, disposable development database, apply the migrations and then install the integrity triggers.
- Start the app and open
http://localhost:3000.
npm cinpm run db:servernpm run db:migratenpm run db:extrasnpm run devMigrations and scripts/sql/triggers.sql are a pair. db:extras installs the integrity triggers after the migration, so do not skip it: they are the second lock on every posting rule. How double-entry is enforced explains why.
An empty install sends you to onboarding, which creates your first entity, a template chart of accounts and open periods. See First-run setup.
Do not run the seed, reset or recreate commands against a database that holds real books. The seed can truncate tables.
Choose a sign-in mode
Left unset, authentication is off. That legacy development mode lets requests through without signing in, and is for a trusted, disposable local exercise only. Never expose it to a network.
For anything that other people or other devices can reach, turn the session gate on. Preview Password sign-in and first-owner setup are in Preview.
| Mode | Set | Behaviour |
|---|---|---|
LEDGER_DEPLOYMENT unset | Your existing configuration | Development defaults to authentication off. Production OAuth behaviour is preserved. |
local | AUTH_ENABLED=1, AUTH_PASSWORD=1, AUTH_SECRET, AUTH_URL | Loopback HTTP URL, host-only HttpOnly SameSite=Lax session cookies. Fails closed with a 503 on protected routes if authentication is off. |
hosted | AUTH_ENABLED=1, AUTH_SECRET, AUTH_URL | One isolated client instance. Use HTTPS. Production needs password sign-in, or Google or Microsoft sign-in with an ALLOWED_EMAILS list. |
To claim the first owner, set LEDGER_SETUP_TOKEN to a long random value and privately open /setup?token= followed by that value. Enter a name, email and a password of at least 12 characters. Remove the token afterwards: if every owner is later deactivated, a configured token would reopen setup.
Run behind HTTPS and a restricted reverse proxy for remote access. The proxy must append or overwrite X-Forwarded-For and block direct access to the app port, because the failed-sign-in limit keys on that address.
You can also add a user from the host with an operator command that reads the password from a private pipe, never from an argument:
npm run user:add -- --email person@example.com --name "Person" --role ownerRoles are owner, accountant and viewer. Interactive terminal input is refused so the password is never echoed.
Verify the build
The tests start their own throwaway PostgreSQL cluster, apply the migrations and the real triggers, and clean up. You need no Docker and no external test database. Give concurrent suites a unique TEST_PG_PORT.
npm run typechecknpm run lintnpm testnpm run check:test-exit-codenpm run buildnpm run check:licensescheck:test-exit-code proves that a red suite still exits non-zero, so the gate means what it says.
What this guide does not cover
- A production runbook for a source install. For a supervised, per-client production setup, the managed hosted instance is built from a Docker, PostgreSQL 18 and Caddy kit that we run for you.
- Bank feeds, AI providers and payments. Those keys are optional; see Bring your own API keys.
- Support. Report bugs through the repository's issue tracker once it is public, and report security issues privately.