Configure the server
Finances App reads configuration from environment variables and the optional Notion TOML file. Docker Compose passes the important variables from .env into the container.
Use .env for deployment settings
Start from the example:
cp .env.example .envKeep .env private. It can contain passwords, setup tokens, and Notion API keys.
First-owner bootstrap variables
Use these variables only while the database is empty:
| Variable | Purpose |
|---|---|
INVOICER_SETUP_TOKEN | Protects browser first-run setup whenever the immediate client connection is not loopback, including normal Docker/reverse-proxy traffic. |
INVOICER_BOOTSTRAP_WORKSPACE_NAME | Name of the first workspace created by unattended bootstrap. |
INVOICER_BOOTSTRAP_OWNER_NAME | Display name for the first owner. |
INVOICER_BOOTSTRAP_OWNER_EMAIL | Login email for the first owner. |
INVOICER_BOOTSTRAP_OWNER_PASSWORD | Login password for the first owner. |
If any INVOICER_BOOTSTRAP_* value is set, all four are required. Once at least one user exists, bootstrap variables are ignored.
Runtime storage variables
| Variable | Default in Docker | Purpose |
|---|---|---|
INVOICER_DB_PATH | /data/invoicer.sqlite3 | SQLite database path. |
INVOICER_FILE_STORAGE | /data/files | Directory for generated PDFs and uploaded/generated files. |
INVOICER_BACKUP_DIR | /data/backups | Managed backup repository. If unset, the app uses a backups folder next to the DB. |
INVOICER_WEB_DIST | /app/web-dist | Built React app served by FastAPI. |
INVOICER_HOST_PORT | 8000 | Host port used by Compose. The binding remains on 127.0.0.1. |
INVOICER_COOKIE_SECURE | 0 | Set to 1 when serving behind HTTPS so session cookies are marked secure. |
INVOICER_ALLOWED_HOSTS | local hosts | Comma-separated hostnames accepted by the HTTP Host boundary. Public deployments must add their exact hostname. |
INVOICER_ALLOWED_ORIGINS | unset | Comma-separated exact browser origins, including scheme and non-default port, accepted for cookie-authenticated writes. The request's own exact origin is always accepted. |
INVOICER_TRUSTED_PROXY_IPS | unset | Comma-separated immediate proxy peer addresses allowed to supply the authentication client address via X-Forwarded-For. Do not trust arbitrary peers. |
INVOICER_ALLOW_UNAUTHENTICATED_LOCAL | 0 | Development-only compatibility mode that permits unconfigured finance APIs for a direct loopback peer. Leave disabled normally. |
The default Compose file mounts the invoicer-data volume at /data. That volume contains the SQLite database, generated files, and backups. The application process uses UID/GID 10001:10001; a pre-existing volume from an older root-running image needs a backed-up, explicit ownership preflight before this runtime is deployed.
Before first-owner setup, health, setup, authentication-state, and web routes remain available, but finance and administrative APIs fail closed. Browser writes authenticated by the session cookie must send an exact allowed Origin or Referer. Bearer API keys are not ambient browser credentials and do not require an Origin header.
Notion connector variables
Notion is optional. SQLite remains the source of truth.
| Variable | Purpose |
|---|---|
NOTION_TOKEN | Notion integration token. Use a test workspace first. |
INVOICER_CONFIG_HOST_PATH | Host path to the TOML config mounted into Docker. Defaults to ./invoicer.toml. |
INVOICER_CONTAINER_CONFIG_PATH | Container path for the mounted TOML config. Defaults to /home/invoicer/.config/invoicer/config.toml. |
INVOICER_CONFIG_PATH | Runtime config path read by the API inside the container. Compose sets this to the container config path. |
Create a local TOML file:
cp examples/invoicer.example.toml invoicer.tomlKeep database IDs in invoicer.toml, but keep secrets in .env:
[profiles.test]
token_env = "NOTION_TOKEN"The Settings page also has a write-only Notion API key field. The API never returns the saved key.
Users, workspaces, and roles
The app separates people from finance containers:
- A user logs in with email and password.
- A workspace owns finance records and settings.
- A membership gives a user a role in one workspace.
Roles:
| Role | Access |
|---|---|
| Owner | Full workspace access, including user management and owner/admin changes. |
| Admin | Daily finance access and most workspace administration. Admins cannot manage owners. |
| Member | Daily finance access. Blocked from workspace administration, backup restore/configuration mutations, and Notion apply/configuration actions. |
After setup, create more users and workspaces from Settings → Workspaces and users.
Adding a new email creates a pending global account and shows a one-time activation link for 72 hours. The recipient chooses the global password. Adding an email that already exists only adds workspace membership and never changes that user's display name or password. Workspace owners and administrators cannot reset global passwords.
Signed-in users change their own password in Settings. If a user loses access, an operator with local database access can perform global recovery without granting that power to a workspace administrator:
uv run invoicer app reset-user-password user@example.com --db .data/invoicer.sqlite3The command prompts securely, revokes existing browser sessions, and never prints the new password. Login, invitation acceptance, and setup routes apply bounded per-client/account throttling. Proxy client addresses are trusted only from explicitly configured proxy peers.