Actual Budget¶
Overview¶
Actual is a local-first personal finance app built around envelope budgeting (every dollar gets a job). The server is small: it serves the web app and stores a sync copy of each budget, while the real work happens in the browser or desktop app, which keeps a full local copy. That means it stays usable if the server is briefly down, and syncs when it's back.
Compose¶
compose.yaml
services:
actual-budget:
image: actualbudget/actual-server:26.10.0
container_name: actual-budget
restart: unless-stopped
ports:
- "5006:5006"
volumes:
- /mnt/appdata/actual-budget/data:/data
# Official script, but at /app/src/scripts/ in this image - the build/
# path shown in some upstream examples doesn't exist here
healthcheck:
test: ["CMD", "node", "/app/src/scripts/health-check.js"]
interval: 60s
timeout: 10s
retries: 3
start_period: 20s
No .env needed. Open it through your reverse proxy (e.g. https://budget.<YOUR_LOCAL_DOMAIN>, see HTTPS below), set the server password, then create or import a budget
(Actual can import from YNAB4, nYNAB and its own exports).
Notes¶
- Pin the version. Upstream's example uses
latest, but this holds real financial data. Actual uses calendar versions (YY.MM.patch), and Renovate proposes each new release as a PR, so an upgrade never happens just because a redeploy pulled a new image. - Healthcheck path. The image ships
health-check.jsunder/app/src/scripts/; examples pointing at abuild/path fail and leave the container permanently unhealthy. Check withdocker exec actual-budget ls /app/src/scripts/if a future image moves it again. - HTTPS. The web app needs a secure context (HTTPS, or
localhost) to run in the browser. Put it behind your reverse proxy with a certificate rather than using the raw port from other machines. - Backups.
/dataholdsserver-files(accounts, settings) anduser-files(the budget sync files). Back up the whole directory. Actual's own Export (Settings > Export data) is a good extra copy before upgrades. - Bank sync (GoCardless/SimpleFIN) is optional and configured in the app, not in compose.