Replace the spreadsheet. Keep your data on your own hardware. Let a local LLM read your month back to you.
Most finance apps want your bank login, a monthly subscription, and a copy of every transaction you've ever made — on their servers. Pocketbook wants none of that.
It's a single-user, self-hosted money tracker that replaces a manual budgeting spreadsheet with something fast, dense, and trustworthy. You run it on your own homelab, log in with your own credentials, and your financial history never leaves your machine. Every month, a local Ollama model reads your numbers and writes you a short, plain-English note about where your money actually went — no cloud, no API keys, no data sold.
Numbers lead. Surfaces recede. A single calm blue is reserved for what matters.
- 🔒 Yours alone — one user, seeded from your env file. No multi-tenant logic, no sign-up flow, no telemetry. Self-hosted with Docker Compose.
- 🧠 Private AI insights — monthly commentary streamed token-by-token from your own Ollama instance. It never calls out to a third party, and the app works fine if the model is offline.
- 💱 Multi-currency, done right — HUF-first with USD/EUR/GBP support, ECB rates auto-synced daily, triangulated conversion, and honest handling of amounts it can't convert.
- 🔁 Recurring & installments — subscriptions, rent, and installment plans tracked with idempotent auto-logging and reconciled counters.
- 📅 Renewal radar — a cash-out timeline that tells you what's leaving your account in the next 30/60/90 days.
- ⚡ Fast and honest UI — optimistic writes, skeletons instead of spinners, tabular numerics on every figure, dark-mode-first, and a real
⌘Ksearch. - 📥 One-way CSV import — bootstrap from your old spreadsheet in one shot.
- 📱 Installable PWA — add to home screen and run in a standalone window.
Group by date, filter by type and category, and add or edit inline with optimistic updates that roll back cleanly if the server says no.
Press N to log a transaction without leaving the page. Type, amount, currency, category, and an optional link to a recurring rule.
Every subscription, rent payment, and installment plan in one place — with monthly/annual outflow totals and progress on multi-payment plans.
A timeline strip plus week/month grouping so nothing surprises you on the statement.
CRUD your categories, give each a colour, and see spend and frequency at a glance.
Real streaming tokens from your Ollama model, saved to the database with a feedback control and a running history of previous notes.
Pick your anchor currency, manage tracked FX rates (dynamic or manual), change your password, and choose which Ollama model writes your insights.
| Layer | Choice |
|---|---|
| Framework | Next.js 16.2.6 (App Router) · React 19.2.6 · TypeScript 5 |
| Styling | Tailwind CSS 4.3 via CSS-first @theme tokens in app/globals.css |
| Components | shadcn v4 registry style · local Base UI-backed primitives · custom finance charts/SVGs |
| Database | PostgreSQL 16 + Prisma 7.8 |
| Auth | Auth.js v5 (next-auth@beta, credentials provider, single user) |
| Forms | react-hook-form + zod |
| AI | Local Ollama — PB_OLLAMA_BASE_URL in deploy env, passed to the app as OLLAMA_BASE_URL |
| FX rates | frankfurter.dev (ECB feed), auto-synced daily |
| Runtime / deploy | Node 24 Alpine · pnpm 10.33.0 in Docker · Docker Compose · GHCR image releases |
Architecture in one line: server components read from Prisma directly → pass props → Server Actions mutate → revalidatePath refreshes. The only REST routes are for Auth.js, SSE insights streaming, and secret-authenticated cron endpoints (FX sync, monthly insights, recurring sync). No client-side data fetching for initial renders.
Pocketbook ships as a Docker image. You need Docker + Docker Compose and a reachable Ollama instance (optional — the app runs without it).
# 1. Clone and copy the env template
git clone https://github.com/traejiik/pocketbook.git
cd pocketbook
cp .env.example .env
# 2. Fill in .env (see the table below). Generate secrets with:
# openssl rand -base64 32 # PB_AUTH_SECRET
# openssl rand -hex 32 # PB_FX_SYNC_SECRET
# 3. Start
docker-compose up -d
# 4. Open http://localhost:3000 and sign in with
# PB_SEED_USER_EMAIL / PB_SEED_USER_PASSWORDOn boot, the pocketbook-web container builds PB_DATABASE_URL from the PB_POSTGRES_* variables, runs prisma migrate deploy, runs the idempotent seed, and then backfills any transaction FX locks that are still missing before starting Next.js. Prisma 7 reads its datasource URL from prisma.config.ts, so the runner image ships that root config alongside prisma/; the bundled backfill runs inside the same entrypoint environment.
After every successful boot the validated configuration is persisted to .env-cache on the /data volume (chmod 600); if a later redeploy arrives without its environment variables (a known Portainer panel quirk), the entrypoint restores the missing values from that cache instead of boot-looping — environment values always win, and a Discord/webhook warning is posted whenever the cache had to be used. See DEPLOY.md → Resilience for details.
| Variable | Required | Description |
|---|---|---|
PB_POSTGRES_PASSWORD |
✅ | Postgres password (used to build the datasource URL). |
PB_AUTH_SECRET |
✅ | Auth.js session secret — openssl rand -base64 32. |
PB_AUTH_URL |
✅ | Full public URL of the instance (mapped to AUTH_URL). |
PB_SEED_USER_EMAIL |
✅ | Login email for the single seeded user. |
PB_SEED_USER_PASSWORD |
✅ | Login password for the seeded user. |
PB_FX_SYNC_SECRET |
✅ | Shared secret for the /api/fx/sync cron endpoint — openssl rand -hex 32. |
PB_OLLAMA_BASE_URL |
– | Your Ollama base URL, e.g. http://homelab.local:11434. |
PB_USER_DISPLAY_NAME |
– | Name shown in the sidebar header and login page. |
PB_INSTANCE_NAME |
– | Optional label in the login footer (e.g. home, work). |
AUTH_TRUST_HOST |
– | Set true for custom hostnames like pocketbook.home. |
See .env.example for the full annotated template and DEPLOY.md for homelab deployment notes.
Pocketbook connects to an existing Ollama instance — it does not run Ollama itself. Point PB_OLLAMA_BASE_URL at your container or machine; Docker passes it into the app as OLLAMA_BASE_URL:
PB_OLLAMA_BASE_URL=http://homelab.local:11434
Recommended models to pull on your Ollama host: llama3.1:8b, mistral:7b, qwen2.5:14b.
If Ollama is unreachable the app still works — the Insights screen shows "Unreachable" and the Generate button is disabled. Nothing about your finances ever leaves your network.
Bootstrap transactions from a spreadsheet export by placing a file at seed/transactions.csv before running docker-compose up (or pnpm prisma db seed).
date,description,amount,currency,type,category_id,recurring_rule_name
2026-01-05,Salary,450000,HUF,INCOME,salary,
2026-01-06,Spar,-8900,HUF,EXPENSE,food,
2026-01-20,Apple Music,-1990,HUF,EXPENSE,subs,Apple Musicdate— ISO 8601 (YYYY-MM-DD)amount— signed; negative = expense, positive = income/savingscurrency— uppercase 3-letter code (HUF,USD,EUR,GBP)type—INCOME,EXPENSE, orSAVINGScategory_id— must match an existing categoryidfrom the seedrecurring_rule_name— optional; links the transaction to a rule by name
The importer is idempotent: re-running it skips rows that already exist by (date, description, amount). For ad-hoc imports after first boot: pnpm tsx scripts/csv-import.ts.
pnpm install
cp .env.example .env # set PB_DATABASE_URL for local dev
pnpm prisma migrate dev
pnpm prisma db seed
pnpm dev # http://localhost:3000Useful scripts: pnpm test (Vitest), pnpm lint, pnpm db:generate, pnpm db:migrate, pnpm db:seed, and pnpm db:backfill-fx (local FX-lock diagnostics).
app/ Next.js App Router — pages and layouts
components/ UI primitives and finance-specific components
server-actions/ All mutations (Server Actions, no REST)
lib/ fx.ts, auth helpers, Prisma client
prisma/ Schema, migrations, seed
prisma.config.ts Prisma 7 CLI config; required by runtime migrations
seed/ CSV bootstrap data (optional)
scripts/ Ad-hoc import and maintenance scripts
other/ Design reference, mockups, handoff notes
Deployment specifics live in DEPLOY.md.
main is always deployable and protected — work happens on short-lived feat/·fix/·chore/
branches that PR into main (PR checks must pass to merge). To ship, bump the version in
package.json as part of the PR. On merge, CI tags vX.Y.Z, publishes a GitHub release, and
builds + pushes the ghcr.io/traejiik/pocketbook-web image automatically — no manual tagging or
releasing. PRs that don't change the version merge without cutting a release.
MIT © Pocketbook







