Full Budget
Full Budget is a local-first personal finance application with a database-backed CLI and web interface. It tracks accounts, immutable ledger transactions, transfers, budgets, spending funds, savings goals, recurring plans, forecasts, and budget pacing. SQLite and PostgreSQL use the same accounting services and Alembic history.
Version 2.0 replaces the legacy YAML/CSV CLI. The distribution remains
lazy-budget and the executable remains lbud, but commands now operate on a
SQLite or PostgreSQL database. Legacy plotting and file-editing commands were
removed.
Python 3.14 or newer is required.
Install
Install the published application as an isolated tool:
uv tool install lazy-budget
lbud --version
Upgrade an existing installation:
uv tool upgrade lazy-budget
Install the current checkout for development:
uv sync
uv run lbud --version
Direct Setup
SQLite
Copy the public settings, choose a timezone, and keep the default SQLite URL:
cp .env.example .env
Relevant local settings:
FULL_BUDGET_DATABASE_URL=sqlite:///~/.local/share/full-budget/full-budget.sqlite3
FULL_BUDGET_TIMEZONE=Europe/Warsaw
FULL_BUDGET_HOST=127.0.0.1
FULL_BUDGET_PORT=8000
FULL_BUDGET_LOG_LEVEL=INFO
FULL_BUDGET_ACCESS_LOG=true
FULL_BUDGET_PUBLIC_ORIGIN=http://127.0.0.1:8000
FULL_BUDGET_TRUSTED_HOSTS=127.0.0.1,localhost,[::1]
FULL_BUDGET_SECURE_COOKIE=false
FULL_BUDGET_COOKIE_NAME=full_budget_session
FULL_BUDGET_ENABLE_DOCS=true
Initialize the schema and sole administrator, then run the web application:
lbud database migrate
lbud admin bootstrap
lbud web
When running from a checkout, prefix commands with uv run. Open
http://127.0.0.1:8000.
SQLite databases are backed up automatically before an online schema upgrade. Create an additional physical backup with:
lbud database backup
PostgreSQL Without Docker
Create an empty PostgreSQL database and set a SQLAlchemy URL using the Psycopg 3 driver:
FULL_BUDGET_DATABASE_URL=postgresql+psycopg://full_budget:password@127.0.0.1/full_budget
Then use the same application workflow:
lbud database migrate
lbud admin bootstrap
lbud web
Use PostgreSQL-native dumps or snapshots for backups. The lbud database backup
command intentionally supports only file-backed SQLite databases.
CLI
Global options must appear before the command:
lbud [--profile NAME] [--env-file PATH] [--database-url URL]
[--timezone ZONE] [--format table|json] [--no-color] [--yes]
COMMAND ...
Examples:
lbud --profile local --format json account list
lbud --profile production --yes transaction delete 42
Money values are decimal strings and JSON output keeps them as strings. Dates use
YYYY-MM-DD; instants accept ISO 8601 values. Destructive commands identify the
resolved profile and require interactive confirmation unless global --yes is
provided.
Command Groups
| Group | Commands |
|---|---|
dashboard |
Show balances and the active budget summary |
account |
create, list, show, balance, history, rename, archive, unarchive |
transaction |
add, list, show, activity, breakdown, categories, delete |
transfer |
add, list, show, delete |
budget |
create, list, show, update, stats, delete, account membership and target management |
fund |
create, list, show, fund, spend, return, release, close, reopen, unlink |
plan |
create, list, update, archive, unarchive, forecast, delete |
occurrence |
list, post, skip |
savings-goal |
create, list, show, update, archive, unarchive |
database |
status, migrate, backup, export, import |
profile |
create, list, show, delete |
admin |
bootstrap, reset |
web |
Run the web application |
add, ls, cats, and bd are short aliases for transaction entry, the
dashboard, category listing, and active-budget burndown data.
Use lbud COMMAND --help and lbud COMMAND SUBCOMMAND --help for every argument.
Common Workflow
Create accounts and record signed activity:
lbud account create Checking PLN --opening-balance 2500.00 --opened-on 2026-08-01
lbud account create Savings PLN --opening-balance 500.00 --opened-on 2026-08-01
lbud transaction add -18.50 --account 1 --category Food --description Lunch
lbud transaction add 4000.00 --account 1 --category Salary --description Payroll
lbud transfer add 300.00 --source 1 --destination 2 --description "Monthly saving"
lbud transaction activity
Create and inspect a budget:
lbud budget create August PLN 2026-08-01 2026-08-31 \
--account 1 --available 2000.00
lbud budget create "From payday" PLN 2026-08-31 \
--opening-at 2026-08-01T14:30:00+02:00 --account 1 --available 2000.00
lbud budget stats 1 --as-of 2026-08-15
lbud bd 1 --as-of 2026-08-15
The legacy START_DATE END_DATE form opens at local midnight. --opening-at accepts
an ISO date-time and takes only END_DATE; omitted opening values in the API default
to the current instant. Budget end dates and statistics --as-of values remain
inclusive, date-only controls.
Create a savings goal and recurring expense, then explicitly post an occurrence:
lbud savings-goal create "Emergency fund" PLN --target 12000.00
lbud plan create expense Rent 1800.00 2026-08-01 \
--account 1 --recurrence monthly --budget-treatment committed
lbud occurrence list --through 2026-09-01
lbud occurrence post 1 --amount 1800.00
Plans are forecasts until an occurrence is explicitly posted. Posting creates the authoritative ledger transaction atomically.
Use a spending fund when an expense is paid from savings rather than ordinary day-to-day money:
lbud fund create 1 Conference
lbud fund fund 1 1 500.00 --source 2 --destination 1
lbud fund spend 1 1 -320.00 --account 1 --category Travel
lbud fund return 1 1 180.00 --source 1 --destination 2
lbud --yes fund close 1 1
Funding and returns are atomic ledger transfers. Funded expense portions are reported
separately and do not reduce ordinary budget pacing. fund release instead makes
unused reserve available as ordinary budget money without creating a ledger transfer.
Unlinking keeps ledger transactions but removes their fund classification.
Profiles
Profiles are permission-restricted environment files under
~/.config/full-budget/profiles. They can hold database, timezone, server, and
security settings:
lbud profile create local \
--database-path ~/.local/share/full-budget/local.sqlite3 \
--timezone Europe/Warsaw
lbud profile create server \
--database-url postgresql+psycopg://full_budget:password@db/full_budget \
--timezone UTC
lbud profile list
lbud profile show server
lbud --profile local account list
profile show redacts database passwords. Profile switching happens between
processes; the application does not hot-swap database engines.
Configuration precedence, highest first, is:
- Explicit CLI overrides.
- Process environment.
--env-fileorFULL_BUDGET_ENV_FILE.--profileorFULL_BUDGET_PROFILE.- Working-directory
.env. ~/.config/full-budget/.env.- Built-in defaults.
Export And Import
Portable exports include authoritative financial records and immutable UUIDs, but exclude administrator credentials, sessions, database URLs, environment values, derived balances, and Alembic internals. Format version 2 preserves spending fund definitions, transaction links, releases, and closed state; historical version 1 exports remain importable but cannot contain those relationships.
lbud database export full-budget-export.json
lbud database import --dry-run full-budget-export.json
lbud --yes database import full-budget-export.json
Merge imports are atomic. New portable IDs are inserted, identical records are unchanged, and conflicting records abort the entire import.
Upgrade A Pre-2.0 SQLite Database To PostgreSQL
This applies to an earlier SQL/Alembic full_budget database, not the removed
legacy YAML/CSV application.
- Stop writers and create a consistent SQLite copy.
- Run 2.0 migrations against that copy; the command creates another backup.
- Verify accounts and budgets.
- Export portable data.
- Start an empty PostgreSQL database and dry-run the import.
- Import, verify, and retain all migration artifacts until restore is rehearsed.
sqlite3 /path/to/original.sqlite3 \
".backup '/path/to/migration.sqlite3'"
lbud --database-url sqlite:////path/to/migration.sqlite3 database migrate
lbud --database-url sqlite:////path/to/migration.sqlite3 account list --all
lbud --database-url sqlite:////path/to/migration.sqlite3 budget list
lbud --database-url sqlite:////path/to/migration.sqlite3 \
database export /path/to/full-budget-export.json
Migration 0010 assigns portable UUIDs. Preserve the migrated copy: repeatedly
migrating fresh copies of the same pre-0010 database creates different UUIDs.
Docker Compose
The Compose stack builds a non-root Python 3.14 application image and uses the official digest-pinned PostgreSQL 18 Alpine image. PostgreSQL is private to the Compose network. Only the application is published, on host loopback by default.
Local Docker
cp .env.example .env
Replace POSTGRES_PASSWORD with a long URL-safe random value. Ensure the browser
origin exactly matches FULL_BUDGET_DOCKER_PUBLIC_ORIGIN. Also replace
FULL_BUDGET_POSTGRES_PASSWORD with a different random value; the preparation
service creates a non-superuser application role. localhost and 127.0.0.1 are
different origins.
docker compose build --pull
docker compose up -d db
docker compose run --rm app lbud admin bootstrap
docker compose up -d
docker compose ps
Migrations run automatically at application startup. The default endpoint is
http://127.0.0.1:8000. PostgreSQL data persists in the
full-budget_postgres_data named volume across restarts, image updates,
container recreation, and docker compose down.
Application and access records are labeled APP and ACCESS. Successful
/api/v1/health requests are omitted from access logs, while failed health checks
remain visible. Compose rotates application and database container logs at 10m
with three files by default.
Do not run docker compose down --volumes unless you intend to permanently remove
the PostgreSQL data.
Common operations:
docker compose logs -f app db
docker compose up -d --build
docker compose down
Import an export without mounting it into the container:
docker compose run --rm -T app \
lbud --format json database import --dry-run - \
< /path/to/full-budget-export.json
docker compose run --rm -T app \
lbud --yes --format json database import - \
< /path/to/full-budget-export.json
Public HTTPS Deployment
Keep FULL_BUDGET_BIND_ADDRESS=127.0.0.1 and terminate HTTPS at an existing NGINX
server. Configure production security before starting the app:
FULL_BUDGET_DOCKER_PUBLIC_ORIGIN=https://finance.example.com
FULL_BUDGET_DOCKER_TRUSTED_HOSTS=finance.example.com
FULL_BUDGET_DOCKER_SECURE_COOKIE=true
FULL_BUDGET_DOCKER_COOKIE_NAME=__Host-full_budget_session
FULL_BUDGET_ENABLE_DOCS=false
The application rejects insecure cookie settings for a non-loopback origin and rejects API documentation with secure cookies.
Example NGINX configuration:
limit_req_zone $binary_remote_addr zone=full_budget_login:10m rate=5r/m;
server {
listen 443 ssl http2;
server_name finance.example.com;
location = /api/v1/auth/login {
limit_req zone=full_budget_login burst=3 nodelay;
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
NGINX terminates TLS. The application deliberately does not derive security
decisions from forwarded headers: it validates the browser Origin and Host
against explicit settings and independently sets secure cookies.
Before enabling public DNS:
- Bootstrap the administrator offline.
- Confirm
/docs,/redoc, and/openapi.jsonreturn404. - Confirm the cookie uses
__Host-full_budget_session,Secure,HttpOnly,SameSite=Lax, andPath=/. - Run the PostgreSQL test and rehearse export/import and backup/restore.
- Keep the application port bound only to host loopback.
PostgreSQL Backup And Restore
Create a dump and copy it off the Docker host:
docker compose exec -T db \
sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB"' \
> full-budget.sql
Restore into an empty database:
docker compose exec -T db \
sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"' \
< full-budget.sql
Environment Variables
Boolean values accept common forms such as true and false. Path values expand
~. FULL_BUDGET_DATABASE_URL takes precedence over the path fallback.
Application
| Variable | Default | Description |
|---|---|---|
FULL_BUDGET_DATABASE_URL |
unset | Preferred SQLAlchemy URL. Supports sqlite and postgresql+psycopg. |
FULL_BUDGET_DATABASE_PATH |
~/.local/share/full-budget/full-budget.sqlite3 |
SQLite fallback used when the URL is unset. |
FULL_BUDGET_TIMEZONE |
UTC |
IANA timezone used for local dates and recurrence boundaries. |
FULL_BUDGET_HOST |
127.0.0.1 |
Server bind host. Non-loopback hosting only accepts 0.0.0.0 with the explicit gate. |
FULL_BUDGET_PORT |
8000 |
Server port; in Compose this is also the host-published port. |
FULL_BUDGET_RELOAD |
false |
Uvicorn reload setting for direct server runs. |
FULL_BUDGET_LOG_LEVEL |
INFO |
Application and Uvicorn log level: DEBUG, INFO, WARNING, ERROR, or CRITICAL. |
FULL_BUDGET_ACCESS_LOG |
true |
Enable HTTP access logs. Successful health-check access records are suppressed. |
FULL_BUDGET_ALLOW_NON_LOOPBACK |
false |
Must be true with FULL_BUDGET_HOST=0.0.0.0. |
FULL_BUDGET_PUBLIC_ORIGIN |
http://127.0.0.1:8000 |
Exact browser origin: scheme, hostname, and optional port, without path or trailing slash. |
FULL_BUDGET_TRUSTED_HOSTS |
127.0.0.1,localhost,[::1],testserver |
Comma-separated accepted Host values. Wildcards are rejected. |
FULL_BUDGET_SECURE_COOKIE |
false |
Require HTTPS and the secure cookie name. Insecure cookies are loopback-only. |
FULL_BUDGET_COOKIE_NAME |
full_budget_session |
Session cookie name. Secure mode requires __Host-full_budget_session. |
FULL_BUDGET_SESSION_IDLE_SECONDS |
1800 |
Sliding idle expiry, from 300 to 86400 seconds. |
FULL_BUDGET_SESSION_ABSOLUTE_SECONDS |
86400 |
Absolute expiry, from 900 to 2592000 seconds; cannot be shorter than idle expiry. |
FULL_BUDGET_ENABLE_DOCS |
false |
Enable /docs, /redoc, and /openapi.json. Rejected with secure cookies. |
FULL_BUDGET_PROFILE |
unset | Select a named profile when --profile is absent. |
FULL_BUDGET_ENV_FILE |
unset | Load an additional explicit env file when --env-file is absent. |
Docker Compose
Compose maps these values into application settings where noted. The application ignores Docker-only names when run directly.
| Variable | Default | Description |
|---|---|---|
POSTGRES_PASSWORD |
required | PostgreSQL password. Keep it URL-safe because Compose interpolates it into the application URL. |
POSTGRES_DB |
full_budget |
PostgreSQL database name. |
POSTGRES_USER |
full_budget |
PostgreSQL role name. |
FULL_BUDGET_POSTGRES_USER |
full_budget_app |
Non-superuser role used by the web application and migrations. |
FULL_BUDGET_POSTGRES_PASSWORD |
required | Password for the application role; use a value different from POSTGRES_PASSWORD. |
FULL_BUDGET_BIND_ADDRESS |
127.0.0.1 |
Host address that publishes the app container port. Keep loopback in production. |
FULL_BUDGET_IMAGE |
full-budget:local |
Built application image name/tag. |
FULL_BUDGET_DOCKER_LOG_MAX_SIZE |
10m |
Maximum Docker json-file log size before rotation for app and database containers. |
FULL_BUDGET_DOCKER_LOG_MAX_FILE |
3 |
Number of rotated Docker log files retained for app and database containers. |
FULL_BUDGET_DOCKER_PUBLIC_ORIGIN |
http://127.0.0.1:8000 |
Becomes FULL_BUDGET_PUBLIC_ORIGIN inside the app container. |
FULL_BUDGET_DOCKER_TRUSTED_HOSTS |
127.0.0.1,localhost |
Becomes the container trusted-host list; health-check loopback hosts are appended. |
FULL_BUDGET_DOCKER_SECURE_COOKIE |
false |
Becomes FULL_BUDGET_SECURE_COOKIE inside the app container. |
FULL_BUDGET_DOCKER_COOKIE_NAME |
full_budget_session |
Becomes FULL_BUDGET_COOKIE_NAME inside the app container. |
Compose also passes FULL_BUDGET_TIMEZONE, FULL_BUDGET_PORT,
FULL_BUDGET_LOG_LEVEL, FULL_BUDGET_ACCESS_LOG,
FULL_BUDGET_SESSION_IDLE_SECONDS, FULL_BUDGET_SESSION_ABSOLUTE_SECONDS, and
FULL_BUDGET_ENABLE_DOCS. It overrides the container database URL, host,
non-loopback gate, and reload mode for the private container network.
Authentication And Security
- There is exactly one administrator per database.
- Bootstrap and password recovery are offline CLI operations; no registration API exists.
- Passwords use Argon2id.
- Browser sessions are opaque, stored server-side by token hash, and have idle and absolute expiry.
- Unsafe API methods require an exact configured origin and per-session CSRF token.
- Login attempts are throttled and production API documentation is disabled.
- Password change and
lbud admin resetrevoke existing sessions.
Savings-goal rename, lifetime-target update, and deletion are not currently implemented. Administrative session listing and selective revocation are also not available; offline password reset revokes all sessions.
Development
uv sync
uv run ruff format --check .
uv run ruff check .
uv run pytest
uv build --clear
Run the optional PostgreSQL smoke test with:
FULL_BUDGET_TEST_POSTGRES_URL=postgresql+psycopg://user:password@host/database \
uv run pytest tests_web/test_postgresql.py
Accounting rules are documented in specs/ACCOUNTING_PLAN.md; architecture and
2.0 milestones are in specs/WEB_APP_PLAN.md and specs/ROADMAP_V2.md.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lazy_budget-2.2.0.tar.gz.
File metadata
- Download URL: lazy_budget-2.2.0.tar.gz
- Upload date:
- Size: 146.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58b3e54fe4dabd5c48d275032df3f0a75c15261ce699872a3c79e0304d4194e0
|
|
| MD5 |
a7c0dc982944c905c63bc87c6835eabc
|
|
| BLAKE2b-256 |
79950267c491a3c1ca73cb7472b350911ecfd894d34e58aa9a3b549943b48926
|
File details
Details for the file lazy_budget-2.2.0-py3-none-any.whl.
File metadata
- Download URL: lazy_budget-2.2.0-py3-none-any.whl
- Upload date:
- Size: 188.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ad01e44b5b2fae134869d0656d13235fba76770731b434eae2826fc6e1575424
|
|
| MD5 |
f34a24a643332491a290f401970e8a05
|
|
| BLAKE2b-256 |
ba67bbe081f98c33cce6f62b77eaaf31ba6245f5de079a78a54c6ee675f10266
|