Configuration
Use this reference if you operate Actual Bench. A basic self-hosted configuration needs no environment
variables; persist /data so Actual Bench metadata survives restarts. See Installation
for the complete prerequisites. Set an environment variable when you need the feature it enables.
How configuration is loaded
Section titled “How configuration is loaded”Everything is an environment variable on the container. The Node server reads the plain ones. Anything
prefixed NEXT_PUBLIC_ is a build value and reaches the browser, so nothing secret belongs there. The
version comes from the build; you do not set it.
Environment variables
Section titled “Environment variables”The table lists every environment variable supported by the current application.
| Variable | Default | Purpose | Secret | Restart |
|---|---|---|---|---|
ACTUAL_BENCH_DB_PATH | /data/actual-bench.sqlite | Path to the metadata SQLite database. | No | Yes |
SYNC_VAULT_KEY | (unset) | Enables unattended server-side sync; derives the encryption key for stored credentials. | Yes | Yes |
SYNC_SCHEDULER_SECRET | (unset) | Enables the external scheduler trigger endpoint; required in its request header. | Yes | Yes |
ACTUAL_BENCH_WORKERS_MAX | 2 | How many automation runs may be going at once. | No | Yes |
ACTUAL_BENCH_WORKER_HEAP_MB | 512 | Memory limit for each automation run, in MB. | No | Yes |
ACTUAL_BENCH_AUTOMATION_EXECUTOR | worker | Where automation runs execute: worker (their own thread) or in-thread (the server’s own thread). | No | Yes |
ACTUAL_BENCH_RUNTIME_DIR | actual-runtime, next to the metadata database | Where automation runs keep the temporary copy of a budget they open themselves. | No | Yes |
LOG_LEVEL | info | Server log verbosity. | No | Yes |
NEXT_DEV_ALLOWED_ORIGINS | (unset) | Dev-only: allow the HMR WebSocket through a reverse proxy (comma-separated hostnames). | No | Yes |
The demo and analytics variables - NEXT_PUBLIC_ANALYTICS and friends - belong to the public demo
build and do nothing in a self-hosted one.
Minimum configuration
Section titled “Minimum configuration”A persistent /data volume. That is the whole requirement. Set ACTUAL_BENCH_DB_PATH only if you
want the metadata database somewhere else.
Direct mode
Section titled “Direct mode”Direct Actual Server mode and HTTP API Server mode are both always offered; there is no setting to turn either one off. Actual Bench sets the cross-origin isolation headers Direct mode needs on every response, unconditionally. If your environment genuinely can’t support Direct mode (the browser can’t reach the Actual Server, or a proxy strips the isolation headers), Bench reports that clearly at the point of connecting so you can just use HTTP API Server mode instead. See Deployment for the browser reachability and cross-origin requirements Direct mode needs.
Metadata database
Section titled “Metadata database”- Default path:
/data/actual-bench.sqlite(override withACTUAL_BENCH_DB_PATH). - Requires a writable persistent volume.
- Schema migrations run on boot; App Health shows the current and latest schema version.
- Stores Actual Bench workflow metadata only - not Actual credentials or budget data, except two explicit, encrypted opt-ins: remembered-connection secrets and unattended-sync credentials (below).
Remembered-server secrets (user passphrase)
Section titled “Remembered-server secrets (user passphrase)”When a user opts in to remembering a server,
its secret is sealed AES-256-GCM in this database, encrypted with a key derived from a passphrase
the user sets - no environment variable is involved. Credentials are server-scoped (one saved server
opens any of its budgets); budget encryption passwords are sealed separately, per budget. The passphrase
and derived key are never stored (only the ciphertext and a salt), so the server cannot decrypt on its
own; the user unlocks once per session. This is separate from the SYNC_VAULT_KEY unattended-sync
vault below - the two never share ciphertext or a key. Back up and persist /data to preserve remembered
servers across container replacement; a writable path by itself does not prove durability. A lost
passphrase means the user resets the vault and re-saves.
Unattended sync and automation settings
Section titled “Unattended sync and automation settings”These gate the whole automation engine, not just Budget File Sync — scheduled bank sync and backups depend on them too:
SYNC_VAULT_KEY- a strong operator secret. When set, the in-process scheduler runs and enrolled credentials can be stored/decrypted. When unset, the feature is fully disabled: nothing is persisted and the scheduler never runs. Enrolled credentials are stored AES-256-GCM encrypted in the metadata database; the key is derived from this variable and never stored in the database.SYNC_SCHEDULER_SECRET(optional) - enables an external trigger endpoint atPOST /api/sync/scheduler/tick, which requires the headerx-scheduler-secret. Without it, that endpoint returns403. Use this only if you want to drive the scheduler from an external cron instead of (or in addition to) the built-in one.
Generate strong values with, for example:
openssl rand -base64 48See Budget Sync → Automation for how flows opt in, and
the repository’s docs/UNATTENDED_SYNC.md for the operator walkthrough.
Automation workers
Section titled “Automation workers”By default, every automation run gets a worker thread of its own inside the Bench server, with its own memory limit. A run that hangs can be stopped, and a run that reaches its memory limit or crashes is normally ended alone while the rest of Bench carries on. The limit covers the run’s own working memory, not everything the server process uses, so the container still needs enough memory overall. Automations explains what you see when that happens. The defaults suit most installs.
ACTUAL_BENCH_WORKERS_MAX(default2) - how many runs may be going at once. A scheduled run that finds no free slot waits for the next minute without counting as a failure; Run now says Bench is busy.ACTUAL_BENCH_WORKER_HEAP_MB(default512) - the memory limit for each run. Raise it if a large budget’s runs stop with “Ran out of memory”. Give the container room for this much per run that can go at once; as a guide, a container running automations on budgets of around 30,000 transactions wants at least 1 GB.ACTUAL_BENCH_AUTOMATION_EXECUTOR(defaultworker) - set toin-threadonly if your platform cannot start worker threads. Runs then execute in the server’s own thread, as they did before: a time limit can only ask a run to stop, and a run that runs out of memory takes the whole server with it.ACTUAL_BENCH_RUNTIME_DIR(default: anactual-runtimefolder next to the metadata database) - where a run keeps the copy of a budget it downloads to work on. Each run gets a private folder, readable only by the Bench process, that is deleted when the run ends. A folder left behind by a crash is removed automatically once it has gone 15 minutes untouched. There is nothing here to back up. Put it on fast local disk with room for a copy of each budget being worked on at once.
At startup Bench checks that a worker thread can start. App Health → Automations → Workers shows the result. If the check fails, automations do not run and the reason is shown there; they never quietly fall back to the server’s own thread.
Logging
Section titled “Logging”LOG_LEVEL defaults to info. Drop it to warn or error for a quiet production log, raise it when
you are chasing something.
Secret handling
Section titled “Secret handling”- A
.envwith real secrets never goes into version control. - Use your container platform’s secret management for
SYNC_VAULT_KEYandSYNC_SCHEDULER_SECRET. - Nothing secret goes in a
NEXT_PUBLIC_*variable. Those are shipped to every browser. - Redact logs before you share them. Rotate anything you even suspect leaked.
Configuration examples
Section titled “Configuration examples”Minimal (Direct or HTTP mode, no unattended sync):
environment: ACTUAL_BENCH_DB_PATH: /data/actual-bench.sqliteWith unattended server-side sync:
environment: ACTUAL_BENCH_DB_PATH: /data/actual-bench.sqlite SYNC_VAULT_KEY: ${SYNC_VAULT_KEY} # provided via your secret managerPlaceholders in the file, real secrets from your platform. Never the other way round.
Validation
Section titled “Validation”Restart, then open App Health. The database should be writable, the schema current, and - if you configured it - the scheduler should report the vault state you expect.
