Self-hosted personal finance manager. Multi-currency, equity-aware, with an AI advisor that has full read/write access to your books. Stack the truths — every account, every flow, every grant — and plan against the floor.
A cairn is a stack of stones marking the path you've climbed. Each stone is a truth: a cash balance, a loan, a recurring expense, a vested grant, a savings goal. None of them is the whole picture; together they're the only honest picture.
A founder's personal balance sheet is unusually messy: cash across NGN / USD / EUR, brokerage in two jurisdictions, crypto, real estate, retirement, debt, day-to-day spending — plus a slab of private equity that may or may not ever pay out. Most personal finance tools either ignore equity or take one paper number too seriously, and lock the useful features behind a premium tier.
Cairn is the un-premium version: free, self-hosted, founder-aware, opinionated, and honest about uncertainty.
- Multi-currency net worth across NGN, USD, EUR, GBP, CAD, CHF, JPY, with daily FX from a free provider.
- Three-scenario equity for every grant — Floor (zero), Liquid (current FMV, post-tax), Expected (target exit, post-tax) — charted side-by-side. Decisions are anchored to the floor.
- Accounts with point-in-time snapshots; current value = latest snapshot + signed transactions since. Cross-currency transactions FX-convert into the account's currency before summing.
- Transactions with CSV import, AI receipt scan (image upload), and voice input. Click any row for a drill-down sheet showing the source flow, full notes, and audit dates.
- Budgets per category with monthly limits, optional account scoping, and over-budget alerts.
- Recurring cash flows (income/expense) with weekly / monthly / yearly cadence and an explicit next-due date — auto-accrued into real transactions so balances stay honest without manual logging.
- Savings goals in four kinds: savings target, net-worth target, FIRE (configurable multiplier), debt payoff (linked to a loan account, drives balance to zero).
- Predictions as a chat-driven canvas. Ask anything ("can I hit my emergency fund in 18 months if I get a 30% raise?"), the advisor reads your real balance sheet and proposes both projection scenarios and concrete edits to your budgets / goals / flows that you can apply with one click.
- Proactive alerts — runway critical, budgets over cap, goals off-pace — surface in a sidebar bell + a
/alertspage, with a banner on the dashboard for criticals. - AI advisor chat seeded with your real accounts, decisions, runway, transactions, budgets, and goals. Bring your own Anthropic / OpenAI / Google key. Has tool access to log transactions, create budgets, and adjust flows on your behalf.
- Month filter that scopes the entire app — accounts, net worth, cash flow, alerts, transactions — to any past month, so you can see what your balance was at the end of March without losing today's view.
- Screen lock with idle timeout + manual
⌘⇧Lshortcut, plus panic mode (⌘⇧P) for one-click sign-out + redirect when someone walks up. - Single-admin auth with an optional viewer password for read-only sharing (spouse, accountant, co-founder). HMAC-signed cookies, gated via
proxy.ts. No multi-tenant, no OAuth.
The repo publishes a multi-arch image (linux/amd64, linux/arm64) to GHCR on every push to main and on tagged releases.
docker run -d \
--name cairn \
-p 3000:3000 \
-v cairn-data:/data \
-e ADMIN_PASSWORD=change-me \
-e SESSION_SECRET="$(openssl rand -hex 32)" \
ghcr.io/9trocode/fmanager:latestOpen http://localhost:3000 and sign in with ADMIN_PASSWORD.
cp .env.example .env
# edit .env — at minimum set ADMIN_PASSWORD and SESSION_SECRET
docker compose up -dData persists in the named volume. Back up by copying /data/app.db out of the volume on a cron — see Backup.
pnpm install
pnpm db:push # apply schema to ./data/app.db
pnpm dev # http://localhost:3000Without ADMIN_PASSWORD set, auth is disabled — handy for hacking on the UI.
All config is via env vars. Cookie auth is mandatory in any non-local deploy.
| Var | Required | Default | Notes |
|---|---|---|---|
ADMIN_PASSWORD |
prod | (auth disabled) | Single admin password. Without this, anyone can sign in. |
VIEWER_PASSWORD |
no | (viewer disabled) | Optional read-only password for sharing. |
SESSION_SECRET |
prod | falls back to ADMIN_PASSWORD |
HMAC secret for session cookies. Set explicitly in prod. |
DATABASE_URL |
no | ./data/app.db (Docker: /data/app.db) |
SQLite file path. |
ANTHROPIC_API_KEY |
no | (unset) | Optional fallback advisor key. Settings → Advisor takes precedence. |
OPENAI_API_KEY |
no | (unset) | Same idea — optional fallback for OpenAI provider. |
GOOGLE_API_KEY |
no | (unset) | Same idea — optional fallback for Google provider. |
APP_URL |
no | http://localhost:3000 |
Used for absolute redirects from API routes. |
- Sign in at
/loginwith yourADMIN_PASSWORD. - Settings → General — pick your base currency, hit "Refresh FX rates" once.
- Settings → Advisor — paste an API key for whichever provider you prefer.
- Settings → Decisions — add the 2-3 active personal financial decisions you're working through.
- Settings → Security — pick a screen-lock idle timeout and (optionally) a panic redirect URL.
- Accounts — add cash / brokerage / crypto / retirement / loans. Each one starts with an opening snapshot.
- Equity — add each grant. Strike, vested shares, FMV, expected exit price.
- Cash flow — log recurring income (salary) and expenses (rent, subscriptions). Pick an explicit next-due date for paydays.
Set VIEWER_PASSWORD to a second password and hand it to whoever needs visibility — spouse, accountant, co-founder. They sign in with the viewer password (same form as admin) and see every page exactly the way you do, but every "Add" / "Edit" / "Delete" affordance is hidden, and the underlying server actions reject the call. Leave VIEWER_PASSWORD unset to disable sharing entirely.
- Next.js 16 (App Router), TypeScript, Tailwind v4
- shadcn/ui (radix base, new-york style, dark by default), Geist
- Drizzle ORM + better-sqlite3 (single-file SQLite, WAL mode)
- AI SDK v6 with provider adapters for Anthropic, OpenAI, and Google
- Auth: HMAC-signed cookie, single env var password, gated via
proxy.ts
Releases are driven by release-please on every push to main. Conventional-commit prefixes (feat:, fix:, perf:, chore:, BREAKING CHANGE:) are converted into a versioned CHANGELOG.md entry; the bot maintains a single open "release PR" with the version bump and accumulated changelog, and merging that PR tags vX.Y.Z + creates a GitHub Release.
The Docker workflow (.github/workflows/docker.yml) fires on release: published and on v* tag pushes, so a merged release PR ends with a freshly published ghcr.io/9trocode/fmanager:vX.Y.Z (and :latest from the same SHA on main).
By default release-please uses GITHUB_TOKEN, which (intentionally) does not cascade workflows. To get end-to-end auto-publishing — release PR merge → tag → Docker build — create a fine-grained PAT with contents: write + actions: write, save it as the repo secret RELEASE_PLEASE_TOKEN, and the workflow picks it up automatically.
This is a self-hosted single-file SQLite app. You are the SRE. Pick one:
- Cron
cp /data/app.db /backups/app-$(date +%Y%m%d).db - Restic, Borg, or Litestream for continuous backup
- Drop the volume directory into Dropbox / Syncthing
- Or use Settings → Data Tools → Export to grab the whole DB as JSON
If you skip this and your VPS dies, your data is gone. There is no cloud version waving at you from the corner.
v1.1 — feature-complete enough to run a real founder's books. Predictions are agentic, balance math is FX-aware, alerts are proactive, the month filter scopes the entire app retroactively. Polish, mobile UX, and bank integrations come next. See ONE-PAGER.md for the longer product brief.
MIT.