Skip to main content

Full Budget

PyPI version

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, rename, allocations, allocate, release, move, update, archive, unarchive, delete
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 savings-goal allocate 1 2 500.00
lbud savings-goal allocations 1
lbud savings-goal move 1 2 3 100.00
lbud savings-goal release 1 3 25.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.

Savings allocations reserve existing account money without creating ledger activity. savings-goal move keeps the source goal by default; pass --destination-goal ID to move the reservation to another active same-currency goal.

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:

  1. Explicit CLI overrides.
  2. Process environment.
  3. --env-file or FULL_BUDGET_ENV_FILE.
  4. --profile or FULL_BUDGET_PROFILE.
  5. Working-directory .env.
  6. ~/.config/full-budget/.env.
  7. Built-in defaults.

Export And Import

Portable format version 4 includes authoritative financial records, independent savings-allocation events, and immutable UUIDs. It excludes administrator credentials, sessions, database URLs, environment values, derived balances, and Alembic internals. Historical versions 1 through 3 remain importable; obsolete transaction-linked savings contributions are ignored with an import warning, and old goal-linked budget targets become ordinary named constraints.

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 or insufficiently backed savings allocations 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.

  1. Stop writers and create a consistent SQLite copy.
  2. Run 2.0 migrations against that copy; the command creates another backup.
  3. Verify accounts and budgets.
  4. Export portable data.
  5. Start an empty PostgreSQL database and dry-run the import.
  6. 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.json return 404.
  • Confirm the cookie uses __Host-full_budget_session, Secure, HttpOnly, SameSite=Lax, and Path=/.
  • 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 reset revoke existing sessions.

Savings-goal lifetime-target updates are not currently implemented. Goals can be renamed, archived, restored, or permanently deleted. Permanent deletion removes the goal's allocation history and releases its account reservations without changing ledger balances or transactions. 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.

Metadata

Release files for lazy-budget 2.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lazy-budget 2.5.0
File Size Uploaded
lazy_budget-2.5.0.tar.gz 162.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for lazy-budget 2.5.0
File Interpreter ABI Platform
lazy_budget-2.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 368.8 kB

Release files / lazy_budget-2.5.0.tar.gz

Download URL lazy_budget-2.5.0.tar.gz
Size 162.4 kB
Tags Source
SHA-256 checksum
How to use checksums
48c921885a965f797d097f3c0ab1f9ecc976309dbe7dd7728b67470dc64507d4
BLAKE2b-256 checksum
How to use checksums
7d0fdfee90a4a04b997b36028103e04c06890bb39b802a0ac7e03d03b8383bab
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / lazy_budget-2.5.0-py3-none-any.whl

Download URL lazy_budget-2.5.0-py3-none-any.whl
Size 206.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6d4ed4568e9db6a05746b4f6f384c2ad2a5b1e6e00aeec7e738a9a2522e5c23f
BLAKE2b-256 checksum
How to use checksums
0779d0115c321c2fc5b5fc5a60fe39626adac8053569c3dcf780b4349c39b723
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release history Release notifications | RSS feed

2.7.5

2 release files

2.7.4

2 release files

2.7.3

2 release files

2.7.2

2 release files

2.7.1

2 release files

2.7.0

2 release files

2.6.0

2 release files

This release

2.5.0 This release

2 release files

2.4.0

2 release files

2.3.0

2 release files

2.2.0

2 release files

2.1.1

2 release files

2.1.0

2 release files

2.0.2

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.18.1

2 release files

0.18.0

2 release files

0.17.1

1 release file

0.17.0

1 release file

0.16.8

1 release file

0.16.7

1 release file

0.16.6

1 release file

0.16.5

1 release file

0.16.4

1 release file

0.16.3

1 release file

0.16.2

1 release file

0.16.0

1 release file

0.8.0

1 release file

0.7.2

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

3 release files

0.1.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page