A self-hosted budgeting app built around funds (envelopes) instead of monthly line items. It's a personal replacement for EveryDollar/YNAB that keeps the parts of zero-based budgeting that work, drops the monthly-reset busywork, and layers on bank sync and an AI assistant you can literally talk to about your money.
Runs entirely on your own machine with Docker — or on a private server you control, reachable only over your own network. See deploy/.
Spin it up in three commands:
Heads up: this is a single-user app with no login. Run it locally. See SECURITY.md before you expose it anywhere.
The whole app is one idea: every dollar you keep gets a job, and jobs persist. If you've used EveryDollar or YNAB, this will feel familiar — with one deliberate difference (funds roll over; they don't reset each month).
A fund is a named pot of money with a balance that rolls over month to month. "Groceries," "Car insurance," "Vacation" are all funds. Unlike EveryDollar — where every category resets to zero on the 1st — a fund's leftover (or overspend) carries forward. If you underspend groceries in March, that money is still in the groceries fund in April. This is the single biggest departure from the tool it replaces, and the reason the accounting is built around running balances rather than monthly buckets.
Funds come in two kinds:
- Operational — ordinary spending categories (rent, food, gas).
- Goal — money you're accumulating or paying down. Three flavors:
- savings — hit a target balance (e.g. $5,000 emergency fund).
- contribution — hit a total contributed in a period (Roth IRA, HSA, 401k).
- debt — a balance you pay down toward zero; supports a real lender
min_paymentso the app shows your actual bill, not a principal estimate.
Each month has a planned income figure — what you expect to bring in. You then assign that money into funds. The headline number, Unassigned, is:
Unassigned = (cumulative planned income) − (cumulative money assigned to funds)
Assigning against the plan (not against actual paychecks) means a deposit landing mid-month doesn't jerk your numbers around — it was already accounted for. Zero-based budgeting works when Unassigned reaches $0: every planned dollar has a fund.
Accounts are where money physically lives — checking, savings, credit cards, investments, and a carved-out emergency fund. Funds are how you've labeled your spendable cash. They're independent on purpose:
- Spendable cash = live checking + savings balances. Your emergency-fund and investment accounts are excluded — that money isn't for spending.
- Net worth = every account netted together (assets minus credit/debt), captured as snapshots over time so you can watch the trend.
- Credit cards are pass-through. A swipe is recorded as an expense from a fund, the moment it happens — not as new debt to reconcile later. The card balance is tracked separately as money owed; paying it off moves cash, it doesn't touch your budget.
Bank transactions (via Plaid) land in an inbox. For each one the app suggests a fund — using vector similarity against how you categorized similar merchants before, plus an optional LLM pass. You approve (or fix) it, and it becomes a real transaction against that fund. Only posted transactions are ingested; pending ones are skipped until they settle, so nothing gets double-counted when the amount changes.
You can also skip Plaid entirely and add transactions by hand.
| Layer | Tech |
|---|---|
| Backend | FastAPI + SQLAlchemy + Alembic |
| Frontend | React + Vite (installable PWA) |
| Database | Postgres 16 + pgvector |
| Embeddings | Ollama (mxbai-embed-large) on the host |
| Categorization | Anthropic API (optional) |
| Bank sync | Plaid (optional; sandbox → production) |
| AI assistant | MCP server wrapping the API ("talk to your money") |
Everything except Ollama runs in Docker. Ollama runs on the host so the container can reach your GPU/CPU without wrestling with passthrough.
- Docker + Docker Compose.
- Ollama running on the host (only needed for the smart fund suggestions). Optional — the app runs without it, you just don't get similarity-based suggestions.
- API keys are all optional. With none of them you get a fully functional manual budgeting app; add them to unlock sync and AI.
cp .env.example .envFill in whatever you want (all optional):
ANTHROPIC_API_KEY— LLM-assisted categorization.PLAID_CLIENT_ID/PLAID_SECRET/PLAID_ENV— bank sync. Start withPLAID_ENV=sandbox.MCP_AUTH_TOKEN— bearer token for the MCP server (openssl rand -hex 32).
ollama pull mxbai-embed-largedocker compose up -d
docker compose exec backend alembic upgrade head # run DB migrationsThat's it. Services:
| Service | URL | Notes |
|---|---|---|
| Frontend | http://localhost:5174 | the app (installable PWA) |
| API | http://localhost:8080 | FastAPI + /docs for the OpenAPI UI |
| Postgres | localhost:5433 | user budget, db budget |
| MCP server | http://localhost:9000/mcp | AI assistant endpoint |
Open the frontend, set your planned income for the month, create a few funds, and assign your income until Unassigned hits $0. If you wired up Plaid, use Settings → connect a bank to link an institution; new transactions show up in the Inbox for you to approve.
Linking a bank exchanges a Plaid public token for an access token (stored in the
plaid_items table) and pulls accounts + transactions. A background job syncs
daily at 6am; you can also trigger POST /plaid/sync anytime. Notes:
- Only posted transactions are ingested (pending ones are skipped until they settle — this avoids duplicates when a pending amount is replaced on posting).
PLAID_SYNC_FLOOR_DATE(inconfig.py) drops transactions older than a cutoff so a first sync doesn't backfill years of history you've already accounted for.- Investment accounts are balance-tracked only — their buys/sells/dividends are intentionally not dropped into your spending inbox.
The MCP server wraps the API so an AI client can read your finances and (with
confirmation) make changes — assign to funds, record transactions, mark goal
contributions, project retirement, and more. It's gated by MCP_AUTH_TOKEN.
See mcp/README.md for connecting Claude Code, claude.ai, or
litellm.
backend/ FastAPI app, services (accounting lives in services/), Alembic migrations
frontend/ Vite + React PWA
mcp/ MCP server that wraps the backend API
deploy/ scripts and docs for running it on a private server
docker-compose.yml development stack
docker-compose.prod.yml production stack: built assets, one origin, nothing published publicly
All balance and accounting math lives in backend/app/services/ so the API and
the MCP server share exactly one source of truth.
Single-user, no authentication, meant for localhost. Do not expose it to the internet without putting your own auth in front. Read SECURITY.md.
If you want it on your phone, put it on a private network rather than on the
public internet — deploy/ documents running it behind Tailscale,
where the network is the access control. An unguessable URL is not a substitute:
URLs leak through history, sync and Referer headers, and a port that is open
for an hour is found in minutes.
Issues and PRs welcome — see CONTRIBUTING.md.
MIT © 2026 Connor Robinson

