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, 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:

  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 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.

  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 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

lazy_budget-2.2.0.tar.gz (146.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lazy_budget-2.2.0-py3-none-any.whl (188.3 kB view details)

Uploaded Python 3

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

Hashes for lazy_budget-2.2.0.tar.gz
Algorithm Hash digest
SHA256 58b3e54fe4dabd5c48d275032df3f0a75c15261ce699872a3c79e0304d4194e0
MD5 a7c0dc982944c905c63bc87c6835eabc
BLAKE2b-256 79950267c491a3c1ca73cb7472b350911ecfd894d34e58aa9a3b549943b48926

See more details on using hashes here.

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

Hashes for lazy_budget-2.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ad01e44b5b2fae134869d0656d13235fba76770731b434eae2826fc6e1575424
MD5 f34a24a643332491a290f401970e8a05
BLAKE2b-256 ba67bbe081f98c33cce6f62b77eaaf31ba6245f5de079a78a54c6ee675f10266

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

2.2.0 This release

2 files

2.1.1

2 files

2.1.0

2 files

2.0.2

2 files

2.0.1

2 files

2.0.0

2 files

1.2.0

2 files

1.1.0

2 files

1.0.0

2 files

0.18.1

2 files

0.18.0

2 files

0.17.1

1 file

0.17.0

1 file

0.16.8

1 file

0.16.7

1 file

0.16.6

1 file

0.16.5

1 file

0.16.4

1 file

0.16.3

1 file

0.16.2

1 file

0.16.1

2 files

0.16.0

1 file

0.8.0

1 file

0.7.2

2 files

0.7.1

2 files

0.7.0

2 files

0.6.0

2 files

0.5.2

2 files

0.5.1

2 files

0.5.0

2 files

0.4.2

2 files

0.4.1

2 files

0.4.0

2 files

0.3.4

2 files

0.3.3

2 files

0.3.2

2 files

0.3.1

2 files

0.2.3

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

3 files

0.1.0

1 file

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page