Skip to content
PreviewThe Windows download and the public source are in preparation. Request early access.
9100Get started

Self-host from source

Run Analytical Ledger on your own PostgreSQL 18 database from a source checkout, and what to set before anyone else can reach it.

Preview7 min readChecked October 26, 2026

Preview

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.

NameUse
DATABASE_URLThe PostgreSQL connection string the app and the migration tools use.
PGPORTThe embedded development cluster's port, when you use npm run db:server.
NODE_ENVSet 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

  1. Install dependencies from the lockfile.
  2. In a first terminal, start the embedded cluster. Skip this if you run your own PostgreSQL.
  3. In a second terminal, against a new, disposable development database, apply the migrations and then install the integrity triggers.
  4. Start the app and open http://localhost:3000.
Terminal 1
npm cinpm run db:server
Terminal 2
npm run db:migratenpm run db:extrasnpm run dev

Migrations 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 seed existing books

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.

ModeSetBehaviour
LEDGER_DEPLOYMENT unsetYour existing configurationDevelopment defaults to authentication off. Production OAuth behaviour is preserved.
localAUTH_ENABLED=1, AUTH_PASSWORD=1, AUTH_SECRET, AUTH_URLLoopback HTTP URL, host-only HttpOnly SameSite=Lax session cookies. Fails closed with a 503 on protected routes if authentication is off.
hostedAUTH_ENABLED=1, AUTH_SECRET, AUTH_URLOne 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:

Operator command
npm run user:add -- --email person@example.com --name "Person" --role owner

Roles 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.

Verification
npm run typechecknpm run lintnpm testnpm run check:test-exit-codenpm run buildnpm run check:licenses

check: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.
Self-host from source | Analytical Ledger