Skip to content

Install and configure

Product Exec runs Spend for most agencies. Agencies that want it in their own cloud account run the same build; this page is for them and for the IT teams who review the design.

  • The container image (Node.js 22), or Node.js 22 with pnpm 10.
  • Postgres 15 or later with encryption at rest and TLS (Amazon RDS or Aurora, Azure Database for PostgreSQL, Cloud SQL).
  • An S3-compatible bucket for documents with versioning and a lifecycle rule matching the records retention schedule.
  • An OpenID Connect or SAML app registration at the identity provider.
  • A TLS certificate, or a load balancer that terminates TLS.
Terminal window
docker run -p 8080:8080 --env-file spend.env <spend image>

The server serves the web app and the API on one port. It refuses to start without TLS unless told a load balancer terminates it (TLS_TERMINATED_UPSTREAM=1).

VariableMeaning
DATABASE_URLPostgres connection string; tables are created on first start
S3_BUCKET, S3_REGION, S3_ENDPOINTDocument storage; S3_ENDPOINT for MinIO or another S3-compatible service
KMS_KEY_IDAWS KMS key that wraps each tenant’s data key; can be a customer-owned key by ARN
MASTER_KEYInstead of KMS, for small installs: 32 random bytes, base64
SSO_CONFIG_FILEEach tenant’s sign-in; see Single sign-on
PUBLIC_URLThe https:// origin users reach
TLS_CERT_FILE, TLS_KEY_FILE or TLS_TERMINATED_UPSTREAM=1TLS
BOOTSTRAP_ADMIN_EMAILFirst administrator of a new tenant
APPROVAL_LINK_SECRETTurns on signed one-tap approval links in emails
MAIL_WEBHOOK_URL, MAIL_FROMMail relay for notices
ANTHROPIC_API_KEY, or workload identity federation settingsTurns on Claude for capture and agents
SESSION_TTL_MINUTESDefault 480
LEDGER_CHECKPOINT_MINUTESHow often ledger heads are signed, default 60

Connector settings are on Oracle EBS and other systems; inbound channels on Inbound channels.

Several instances can serve one tenant set behind a load balancer when they share the database. Background jobs (approval clock, risk sweep, webhooks, agents, e-invoice responses, price indices and connector jobs) each hold a Postgres advisory lock, so one instance runs each job at a time and another takes over if it stops.

TaskHow
Back upPoint-in-time recovery on the database, versioning and cross-region replication on the bucket. Keep the master key separate from backups.
Rotate the master keyTurn on KMS automatic rotation, or re-wrap every tenant’s data key to a new key in one transaction.
Restore drillRestore to a scratch environment and run GET /api/audit/verify for each tenant.
Export a tenantGET /api/admin/export
Health checkGET /api/health
LogsJSON lines on stdout, one per request and job