Deployment
Run the cost-first MVP on Cloud Run with Cloud SQL.
Cloud Run MVP
The MVP target is one Cloud Run API service and one Cloud SQL PostgreSQL instance in the same region. The API runs the durable accounting worker in-process. PostgreSQL owns queue state, leases, retries, dead letters, and the outbox, so the MVP does not need a separate worker or queue service.
Use scripts/deploy/cloud-run.sh. It builds and pushes the server image, runs the migration Cloud
Run job, deploys the service, and checks /ready. The complete non-secret environment is
deploy/cloudrun/env.yaml; Secret Manager mappings are declared in deploy/cloudrun/config.env.
Cloud SQL is reached through Cloud Run's Unix socket mount; do not expose PostgreSQL publicly.
Use ACCOUNTING_QUEUE_BACKEND=postgres, ACCOUNTING_EMBEDDED_WORKER_ENABLED=true, one minimum
instance, and the declared maximum of three instances. Wrap production data keys with the regional
Cloud KMS key declared by ACCOUNTING_KMS_KEY_NAME. The console and documentation are static
Cloudflare assets.
Required accounting configuration
Set XERO_CLIENT_ID, XERO_CLIENT_SECRET, XERO_REDIRECT_URI, XERO_WEBHOOK_KEY,
ACCOUNTING_API_KEY_PEPPER, ACCOUNTING_KMS_KEY_NAME, and CONNECT_ALLOWED_REDIRECT_ORIGINS.
Production startup rejects incomplete security-critical configuration.
For the PostgreSQL-backed MVP, the worker URL and Cloud Tasks service account are not required. They become relevant only when the Cloud Tasks backend is selected. KMS remains part of both production topologies because it protects provider credentials independently of queue delivery.
Later GCP migration
When scale justifies it, the existing GCP infrastructure can provision separate Cloud Run API and
worker services, Cloud SQL, Cloud Tasks, KMS, Artifact Registry, Secret Manager, and controlled
egress. Set ACCOUNTING_QUEUE_BACKEND=cloud-tasks and disable the embedded worker. The operation
ledger and worker execution path remain unchanged.
Terraform creates Secret Manager containers but deliberately does not place secret values in state.
For a new environment, apply the secret containers first, add at least one version for every ID in
the runtime_secret_ids output, and then apply the Cloud Run services. An empty secret cannot be
mounted as latest by Cloud Run.
GCP service identity
The API may enqueue Cloud Tasks but should not invoke arbitrary services. Cloud Tasks signs requests
for the worker audience, and each task also carries a separately generated
ACCOUNTING_WORKER_AUTH_TOKEN. The worker rejects requests without both credentials; never reuse an
API key pepper, user credential, or provider secret for this token. The worker may decrypt with the
accounting KMS key. Database identities should be distinct where practical.
Release verification
Apply migrations up, down, and up in an isolated database. Run formatting, lint, type checking, tests, unused-code analysis, infrastructure validation, and production builds. Smoke-test readiness, API-key rejection, hosted connect creation, a test Xero authorization, one cached list read, one idempotent write replay, one automation, and one signed webhook delivery before routing production traffic.