APP_BINDDocker only: which host interface the published app port binds to. Defaults to 127.0.0.1 so `docker compose up` serves this host only. Set to 0.0.0.0 to reach the app from other machines — do that only behind a TLS proxy (docs/tls-proxy-setup.md). (Native installs use APP_HOST above instead.)
Docker only: which host interface the published app port binds to. Defaults to 127.0.0.1 so `docker compose up` serves this host only. Set to 0.0.0.0 to reach the app from other machines — do that only behind a TLS proxy (docs/tls-proxy-setup.md). (Native installs use APP_HOST above instead.)
APP_DEBUGSet APP_DEBUG=true ONLY for local development. In production it disables the fail-hard startup checks that protect against accidentally shipping without TLS, without an encryption secret, etc.
Set APP_DEBUG=true ONLY for local development. In production it disables the fail-hard startup checks that protect against accidentally shipping without TLS, without an encryption secret, etc.
APP_HOST=0.0.0.0
APP_PORT=3001
COMPANY_ADDRESS
COMPANY_EMAIL
COMPANY_FILE_ENCRYPTION_SECRETOPTIONAL: the key for an encrypted company file (Settings -> Company File Encryption, #290). Set it where SlowBooks Pro starts, the same way as PAYROLL_ENCRYPTION_SECRET, before turning encryption on; from then on the app opens the file only with it, and so do its backups. A different secret from the payroll one is fine, and so is the same: the two keys are derived under different salts. Generate
OPTIONAL: the key for an encrypted company file (Settings -> Company File Encryption, #290). Set it where SlowBooks Pro starts, the same way as PAYROLL_ENCRYPTION_SECRET, before turning encryption on; from then on the app opens the file only with it, and so do its backups. A different secret from the payroll one is fine, and so is the same: the two keys are derived under different salts. Generate
COMPANY_NAME=My Company
COMPANY_PHONE
CORS_ALLOW_ORIGINSBrowser origins allowed to call the API. Leave unset to default to loopback only. For a separate UI host:
Browser origins allowed to call the API. Leave unset to default to loopback only. For a separate UI host:
DATABASE_URLNative Linux install:
Native Linux install:
DEFAULT_TAX_RATE=0.0
DEFAULT_TERMS=Net 30
EMPLOYER_EINEmployer EIN (XX-XXXXXXX). Required for W-2/W-3/940/941 generation.
Employer EIN (XX-XXXXXXX). Required for W-2/W-3/940/941 generation.
EMPLOYER_STATE=WATwo-letter state code for the employer's primary location. Drives the default state-tax engine.
Two-letter state code for the employer's primary location. Drives the default state-tax engine.
FORCE_HTTPS=trueDefault true in production (APP_DEBUG=false). Enables: - HTTPSRedirectMiddleware (308 plain HTTP -> HTTPS at the app layer) - Strict-Transport-Security header (HSTS) - `Secure` flag on the session cookie Must stay true in production. The app refuses to boot with FORCE_HTTPS=false while APP_DEBUG=false (see _run_startup_security_checks), and it does not need to be turned off behind a TLS-terminatin
Default true in production (APP_DEBUG=false). Enables: - HTTPSRedirectMiddleware (308 plain HTTP -> HTTPS at the app layer) - Strict-Transport-Security header (HSTS) - `Secure` flag on the session cookie Must stay true in production. The app refuses to boot with FORCE_HTTPS=false while APP_DEBUG=false (see _run_startup_security_checks), and it does not need to be turned off behind a TLS-terminatin
FORWARDED_ALLOW_IPSReverse-proxy client-IP trust. REQUIRED if anything terminates TLS in front of the app (nginx, Traefik, Caddy, an ALB, an ingress controller). uvicorn parses X-Forwarded-For but trusts it only from the addresses listed here — default 127.0.0.1, which a proxy in another container or pod is not. Leave it unset behind a proxy and every request looks like it came from the proxy: the login rate limiter
Reverse-proxy client-IP trust. REQUIRED if anything terminates TLS in front of the app (nginx, Traefik, Caddy, an ALB, an ingress controller). uvicorn parses X-Forwarded-For but trusts it only from the addresses listed here — default 127.0.0.1, which a proxy in another container or pod is not. Leave it unset behind a proxy and every request looks like it came from the proxy: the login rate limiter
HSTS_MAX_AGE=63072000HSTS max-age in seconds. Default 63072000 = 2 years (the preload-list minimum). Browsers cache the directive for this long.
HSTS max-age in seconds. Default 63072000 = 2 years (the preload-list minimum). Browsers cache the directive for this long.
PAYROLL_ENCRYPTION_SECRETSymmetric key used to encrypt employee bank routing/account numbers and other PII at rest (Fernet, PBKDF2-SHA256 480k iterations). Generate a strong value: openssl rand -base64 32 Startup will refuse to boot in production with the dev default.
Symmetric key used to encrypt employee bank routing/account numbers and other PII at rest (Fernet, PBKDF2-SHA256 480k iterations). Generate a strong value: openssl rand -base64 32 Startup will refuse to boot in production with the dev default.
PAYROLL_ENCRYPTION_SECRET_PREVOPTIONAL: previous secret, used during key rotation. The app decrypts legacy ciphertext with PREV when current fails. Rotate via: 1. Move the current value into PAYROLL_ENCRYPTION_SECRET_PREV 2. Set a new PAYROLL_ENCRYPTION_SECRET 3. Bounce the app 4. Run the rewrap migration (see docs/security-hardening.md) 5. Drop PAYROLL_ENCRYPTION_SECRET_PREV
OPTIONAL: previous secret, used during key rotation. The app decrypts legacy ciphertext with PREV when current fails. Rotate via: 1. Move the current value into PAYROLL_ENCRYPTION_SECRET_PREV 2. Set a new PAYROLL_ENCRYPTION_SECRET 3. Bounce the app 4. Run the rewrap migration (see docs/security-hardening.md) 5. Drop PAYROLL_ENCRYPTION_SECRET_PREV
POSTGRES_BIND
POSTGRES_DB=bookkeeper
POSTGRES_PASSWORD
POSTGRES_PORTDocker only: the published Postgres port is bound to 127.0.0.1 by default, so this host's dev database (with the default `bookkeeper` password) is not exposed on the LAN. POSTGRES_PORT sets the host port. Only set POSTGRES_BIND=0.0.0.0 if you must reach Postgres from another machine — and then change POSTGRES_PASSWORD to a real secret first.
Docker only: the published Postgres port is bound to 127.0.0.1 by default, so this host's dev database (with the default `bookkeeper` password) is not exposed on the LAN. POSTGRES_PORT sets the host port. Only set POSTGRES_BIND=0.0.0.0 if you must reach Postgres from another machine — and then change POSTGRES_PASSWORD to a real secret first.
POSTGRES_USER=bookkeeperDocker install — these are picked up by docker-compose.yml:
Docker install — these are picked up by docker-compose.yml:
RATE_LIMIT_ENABLED=1Rate limiting (login + portal endpoints). Set to 0 only in test environments or load-testing.
Rate limiting (login + portal endpoints). Set to 0 only in test environments or load-testing.
RATE_LIMIT_STORAGE_URIShared storage for rate-limit counters. Unset means in-process counters, which are per-worker and per-replica: with APP_WORKERS=4 a "5 per minute" limit is really 20 per minute, and more across replicas. Required for any multi-worker or multi-replica deploy; the in-memory default is correct only for a single worker (and the desktop app). The redis client ships with the app, and startup refuses to
Shared storage for rate-limit counters. Unset means in-process counters, which are per-worker and per-replica: with APP_WORKERS=4 a "5 per minute" limit is really 20 per minute, and more across replicas. Required for any multi-worker or multi-replica deploy; the in-memory default is correct only for a single worker (and the desktop app). The redis client ships with the app, and startup refuses to
SESSION_IDLE_TIMEOUT_SECONDS=14400Idle session cap. Sliding window — every authenticated request rolls the clock forward. 0 disables the idle check. Default 14400s = 4 hours.
Idle session cap. Sliding window — every authenticated request rolls the clock forward. 0 disables the idle check. Default 14400s = 4 hours.
SESSION_SECRET_KEYCookie-signing secret. If unset, the app auto-generates one to .slowbooks-session.key on first boot. For production, set this yourself so re-deploys don't invalidate every active session: openssl rand -hex 32
Cookie-signing secret. If unset, the app auto-generates one to .slowbooks-session.key on first boot. For production, set this yourself so re-deploys don't invalidate every active session: openssl rand -hex 32
SETTINGS_ENCRYPTION_KEYOPTIONAL: key for saved passwords and API keys (SMTP, payment providers, QuickBooks Online, the bank feed). Left empty, the app uses its .slowbooks-master.key file if there is one, else derives a key from PAYROLL_ENCRYPTION_SECRET (so a Docker container can be recreated). Docker, upgrading from 2.17 or earlier with saved passwords: copy the old key out of the running container first and set it her
OPTIONAL: key for saved passwords and API keys (SMTP, payment providers, QuickBooks Online, the bank feed). Left empty, the app uses its .slowbooks-master.key file if there is one, else derives a key from PAYROLL_ENCRYPTION_SECRET (so a Docker container can be recreated). Docker, upgrading from 2.17 or earlier with saved passwords: copy the old key out of the running container first and set it her
SLOWBOOKS_UPDATE_CHECKDesktop update notice: on by default. Opening the app lets the backend request https://dl.slowbookspro.com/latest.json, which tells that host the install's IP address and version. Set to 0 to turn the check off.
Desktop update notice: on by default. Opening the app lets the backend request https://dl.slowbookspro.com/latest.json, which tells that host the install's IP address and version. Set to 0 to turn the check off.
SUTA_RATE=0.012State unemployment tax (SUTA) experience rate as a decimal. WA average is around 1.2% — verify with your state agency.
State unemployment tax (SUTA) experience rate as a decimal. WA average is around 1.2% — verify with your state agency.
Accounting data should live on your hardware, not someone else's server. Your financial records shouldn't be subject to another company's pricing decisions or breach history.
Is it "open source"?
The source is public on GitHub where you can read and modify it. Technically the license is source-available rather than OSI-certified open source due to the No-Commercial-Resale clause.
Is SlowBooks really free? What's the catch?
No catch. The license is source-available, permitting free use for personal, educational, and internal business purposes, including commercial enterprises. You cannot resell it as your own paid product.
0 comments