Actual Budget

Local-first envelope budgeting with a self-hosted sync server.

ProductivitybeginnerProduction-ready defaults1 services

The stack

Generated output

docker-compose.yml
services:
  actual-budget:
    image: docker.io/actualbudget/actual-server:26.5.2@sha256:1aeeb3985db55556e716dec25e08f6ce09308c2571b65cddbc6746ee6d5e0d45
    restart: unless-stopped
    environment:
      TZ: ${TZ}
      ACTUAL_PORT: "5006"
    volumes:
      - actual-data:/data
volumes:
  actual-data: {}

Replace budget.example.com with your domain. Generate secrets below.

Environment

.env with generated secrets

.env
# Timezone for server logs and scheduled tasks (IANA tz name).

Secrets are generated in your browser via crypto.getRandomValues. Nothing is sent anywhere.

Server-rendered .env template
# Timezone for server logs and scheduled tasks (IANA tz name).
TZ=UTC

About

What is Actual Budget?

Actual Budget is a local-first personal finance app built around envelope (zero-sum) budgeting: every unit of money you hold is assigned a job before the month begins, and the budget is reconciled against real account balances. The app runs entirely in your browser against a local copy of your data, and this stack adds the piece that makes it practical across devices — the Actual sync server. The server keeps a copy of each budget file and streams changes between your laptop, phone, and any other client, so you get multi-device sync without handing your finances to a third party. It is a single Node container with one persistent volume at `/data`, which holds both the server metadata (`server-files`) and your uploaded budget files (`user-files`). There is no external database: Actual uses a SQLite file per budget, so the footprint is tiny and a Raspberry Pi runs it comfortably. Access is gated by a single server password that you set in the browser on first visit — there are no per-user accounts out of the box, so anyone with the password and the URL can open every budget on the instance. The compose file binds nothing to the host; the reverse proxy at `budget.example.com` is the only way in, and the Actual web and desktop clients are happy behind TLS. Back up the `/data` volume and you have backed up everything.

Requirements

Before you start

  • 256 MB RAM is plenty — Actual is light.
  • Docker 24+ and Docker Compose v2.
  • A domain with HTTPS. The Actual clients and optional end-to-end encryption expect a valid TLS certificate.
  • A persistent disk for the actual-data volume — it holds every budget file and the server password.

Deploy

How to deploy

  1. Optionally set `TZ` in `.env` so server logs use your local timezone (it defaults to UTC).
  2. Start the stack: `docker compose up -d`.
  3. Open `https://budget.example.com` and set a server password when prompted — this single password gates the whole instance.
  4. Create a new budget file, or import a YNAB or Actual export; it syncs to the server automatically.
  5. Install the desktop or mobile client, point its server URL at `https://budget.example.com`, and sign in with the server password.

Errors

Common errors & fixes

The web app loads but every change reports "Failed to sync" or stays local.

The browser client cannot reach the sync server through the proxy. Make sure requests to `budget.example.com` reach the container on port 5006 over valid TLS — Actual refuses to sync against an untrusted certificate.

After a redeploy, all budgets and the server password are gone.

The actual-data volume was not persisted, or a bind mount pointed at a path that did not survive. Everything lives under `/data`; keep that volume and back it up.

Syncing or uploading a large budget fails with a 413 or size error.

Either the reverse proxy caps the request body or the server upload limit is too low. Raise the proxy body limit (Caddy `request_body max_size`, Traefik `maxRequestBodyBytes`) and, if needed, increase the server upload size limits documented in the Actual configuration guide.

A client warns that the server version is incompatible.

The desktop or mobile client and the server image drifted apart. Update both: bump the pinned `actual-server` tag here and update the client to a matching release.

Limitations

Honest limitations

  • No per-user accounts out of the box: a single server password gates the entire instance. Real multi-user access requires wiring up an external OpenID provider.
  • Sync resolves conflicts as last-write-wins per budget. Actual is not designed for two people editing the same budget at the same moment.
  • Backups are your responsibility: snapshot the actual-data volume. There is no built-in off-site backup.
  • Bank sync (SimpleFIN or GoCardless) is not configured here; it is optional and set up per budget inside the app.

FAQ

Frequently asked

Is my financial data encrypted?+

You can enable end-to-end encryption per budget file, after which the server only ever stores ciphertext and the encryption password never leaves your devices. Without it, budgets are stored as plain SQLite on the server, protected by the server password and your TLS.

Why are there no secrets to generate for this stack?+

The Actual server password is set in the browser on first visit and stored hashed in the database, not injected through an environment variable. There is nothing to pre-generate — just choose a strong password when prompted.

Do I actually need the sync server?+

Only if you want multi-device sync or a server-side copy. Actual works fully offline in one browser, but then the data lives only in that browser storage. The sync server is what makes it durable and available on every device.

How do I back this up?+

Stop the container and copy the actual-data volume, or use the per-budget export inside the app. The volume holds `server-files` (metadata) and `user-files` (your budgets); both are needed for a full restore.

Can I import from YNAB?+

Yes. Actual includes importers for YNAB4 and the newer YNAB, plus its own format. The import runs in the client and the result syncs to the server like any other change.

Related

Related stacks