Skip to main content

ActualBudget on Google Cloud Run

ActualBudget on Google Cloud Run

Actual Budget is a privacy-first, local-first personal finance application built around zero-based envelope budgeting. The actual-server component is a lightweight Node.js sync server that stores each budget as a SQLite file and synchronises it across the web UI, desktop, and mobile clients. This module deploys the Actual Budget server on Cloud Run v2 on top of the App_CloudRun foundation, which provisions and manages the shared Google Cloud infrastructure.

This guide focuses on the cloud services ActualBudget uses and how to explore and operate them from the Google Cloud Console and the command line. For the mechanics common to every Cloud Run application — service identity, ingress and load balancing, scaling and concurrency, CI/CD, Cloud Armor, IAP, Binary Authorization, VPC Service Controls, backups, and the deployment lifecycle — refer to the App_CloudRun foundation guide rather than repeating them here.


1. Overview

ActualBudget runs as a single Node.js container on Cloud Run v2. Because it manages its own SQLite storage, the deployment wires together a deliberately small set of Google Cloud services:

CapabilityGoogle Cloud serviceNotes
ComputeCloud Run v2Node.js service, 1 vCPU / 1 GiB by default, single instance (min = max = 1)
DatabaseNoneActualBudget keeps budget data in SQLite files — database_type = "NONE", no Cloud SQL
Persistent dataCloud Storage (GCS FUSE)A dedicated storage bucket mounted at /data holds the SQLite budget files and user files
Container imageArtifact Registry + Cloud BuildThin-wrapper build of actualbudget/actual-server, mirrored into your registry
SecretsSecret ManagerOptional — a pre-provisioned API token (enable_api_key), off by default
IngressCloud Run URL / Cloud Load BalancingDefaults to internal — flip to all or front with a load balancer for browser access

Sensible defaults worth knowing up front:

  • No external database. ActualBudget persists everything as SQLite files under /data; there is no Cloud SQL instance, no db-init job, no Redis (enable_redis = false), and no Cloud SQL Auth Proxy sidecar.
  • A storage GCS bucket is provisioned automatically by ActualBudget_Common and mounted at /data via GCS FUSE (enable_gcs_storage_volume = true). ACTUAL_SERVER_FILES = /data/server-files and ACTUAL_USER_FILES = /data/user-files point both persistence subtrees at that mount so nothing lands on ephemeral container disk.
  • Single instance by design. min_instance_count = 1 and max_instance_count = 1 — the server serves one shared set of SQLite files from one volume; running multiple replicas risks write conflicts.
  • Ingress defaults to internal. The service is not reachable from the public internet until you set ingress_settings = "all" (or add a load balancer) — a deliberately private default for a personal-finance workload.
  • No generated secrets by default. The server password is set interactively on the first-run onboarding screen. The only optional secret is a 32-character API token (enable_api_key = true) injected as ACTUAL_TOKEN.
  • Version pinning. The Dockerfile reads an app-specific ACTUALBUDGET_VERSION build ARG; application_version = "latest" pins the build to 25.7.1.
  • Health probes target / — the server answers its root path with HTTP 200 as soon as it is listening, no authentication required.
  • Best suited to single-user / light use on Cloud Run. SQLite over GCS FUSE does not tolerate heavy concurrent writes; for durable production storage prefer the ActualBudget_GKE variant with a block PVC.

2. Google Cloud Services & How to Explore Them

All commands assume PROJECT and REGION are set. Service and resource names are reported in the deployment Outputs.

A. Cloud Run — the ActualBudget service

ActualBudget runs as a Cloud Run v2 service pinned to a single instance. Each deployment creates an immutable revision; traffic shifts to the newest healthy revision on update.

  • Console: Cloud Run → select the service for revisions, traffic, logs, and metrics.
  • CLI:
    gcloud run services list --project "$PROJECT" --region "$REGION" \
    --filter="metadata.name~actualbudget"
    gcloud run services describe <service-name> --project "$PROJECT" --region "$REGION"
    gcloud run revisions list --service <service-name> --project "$PROJECT" --region "$REGION"

See App_CloudRun for scaling, concurrency, execution environment, and traffic splitting.

B. Cloud Storage — the persistent data tier

All ActualBudget state — the SQLite budget databases, server files, and per-file user data — lives on a dedicated Cloud Storage bucket mounted into the container at /data via GCS FUSE (gen2 execution environment required). The bucket survives revision rollouts, restarts, and re-deploys; it is the only place budget data exists, so treat it as the thing to protect and back up.

  • Console: Cloud Storage → Buckets.
  • CLI:
    gcloud storage buckets list --project "$PROJECT" --filter="name~actualbudget"
    gcloud storage ls -r gs://<storage-bucket>/ # bucket name is in the Outputs

See App_CloudRun for GCS Fuse mount behaviour and CMEK.

C. Artifact Registry & Cloud Build — the container image

ActualBudget_Common ships a thin-wrapper Dockerfile (FROM actualbudget/actual-server:${ACTUALBUDGET_VERSION}) that Cloud Build builds into your project's Artifact Registry repository — so deploys pull from your registry, not Docker Hub, and are compatible with Binary Authorization.

  • Console: Artifact Registry → Repositories; Cloud Build → History.
  • CLI:
    gcloud artifacts repositories list --project "$PROJECT" --location "$REGION"
    gcloud builds list --project "$PROJECT" --region "$REGION" --limit 5

D. Secret Manager — optional API token

By default no secrets are created. When enable_api_key = true, a 32-character random token is generated, stored in Secret Manager as secret-<prefix>-<app>-api-key, and injected into the service as the ACTUAL_TOKEN secret environment variable — useful for automations that must call the server before the UI is configured.

  • Console: Security → Secret Manager.
  • CLI:
    gcloud secrets list --project "$PROJECT" --filter="name~api-key"
    gcloud secrets versions access latest --secret=<secret-name> --project "$PROJECT"

See App_CloudRun for injection and rotation details.

E. Networking & ingress

The service defaults to ingress_settings = "internal", so only traffic from inside the project's VPC can reach it. For browser access, either set ingress_settings = "all" (the run.app URL becomes publicly reachable), or use internal-and-cloud-load-balancing with a custom domain, Cloud CDN, Cloud Armor, and optionally IAP in front.

  • Console: Cloud Run (service URL); Network services → Load balancing.
  • CLI:
    gcloud run services describe <service-name> --region "$REGION" --format='value(status.url)'
    # Tunnel to an internal-only service from your workstation:
    gcloud run services proxy <service-name> --region "$REGION" --port 8080

See App_CloudRun.

F. Cloud Logging & Monitoring

Container logs flow to Cloud Logging; Cloud Run metrics flow to Cloud Monitoring. An uptime check can be enabled via uptime_check_config (off by default — it requires a publicly reachable endpoint, which the internal ingress default does not provide).

  • Console: Logging → Logs Explorer; Monitoring → Dashboards / Alerting.
  • CLI:
    gcloud run services logs read <service-name> --project "$PROJECT" --region "$REGION" --limit 50

3. ActualBudget Application Behaviour

  • No initialization job. There is no database to bootstrap; the server creates its SQLite files under /data on first boot. Custom initialization_jobs are accepted for data loading or migration tasks but none is provided by default.
  • First-run setup. On first access the web UI shows an onboarding screen where you set the server password — there are no pre-seeded credentials to retrieve. Do this immediately after making the service reachable; until a password is set, anyone who can reach the URL can claim the server.
  • Data layout. ACTUAL_SERVER_FILES = /data/server-files (server metadata and the account database) and ACTUAL_USER_FILES = /data/user-files (the per-budget sync data). Both live on the GCS FUSE mount, so budget data survives restarts and redeploys.
  • Local-first sync model. Clients (web, desktop, mobile) keep a full local copy of the budget and use the server only to synchronise encrypted changes between devices — brief server unavailability does not block working in a client.
  • Single-writer constraint. The server assumes exclusive access to its SQLite files. Keep max_instance_count = 1; a plan-time validation enforces min_instance_count <= max_instance_count.
  • Version updates. Change application_version and re-apply — Cloud Build produces a new image and Cloud Run rolls a new revision. latest builds the pinned 25.7.1.
  • Health endpoint. Startup and liveness probes issue GET /, which returns HTTP 200 unauthenticated as soon as the HTTP server is listening.
  • Verification:
    SERVICE=$(gcloud run services list --project "$PROJECT" --region "$REGION" \
    --filter="metadata.name~actualbudget" --format="value(metadata.name)" --limit=1)
    SERVICE_URL=$(gcloud run services describe "$SERVICE" \
    --project "$PROJECT" --region "$REGION" --format="value(status.url)")
    curl -s -o /dev/null -w "%{http_code}\n" "$SERVICE_URL/" # expect 200 (403/404 while ingress=internal)

4. Configuration Variables

Variables are grouped exactly as they appear on the deployment platform. Only settings specific to or notable for ActualBudget are listed; every other input is inherited from App_CloudRun with its standard behaviour.

Group 1 — Project & Identity

VariableDefaultDescription
project_id(required)Target Google Cloud project.
regionus-central1Region for the service and regional resources.

Group 2 — Deployment Environment

VariableDefaultDescription
tenant_deployment_iddemoShort suffix that makes resource names unique per environment.
support_users[]Emails granted project access and monitoring alerts.

All other inputs follow standard App_CloudRun behaviour.

Group 3 — Application Identity

VariableDefaultDescription
application_nameactualbudgetBase name for resources. Do not change after first deploy.
application_versionlatestImage version tag; latest builds the pinned 25.7.1. Increment to trigger a new build and revision.
enable_api_keyfalseGenerate a 32-char API token in Secret Manager and inject it as ACTUAL_TOKEN. Recommended when the service is reachable outside the VPC.

All other inputs follow standard App_CloudRun behaviour.

Group 4 — Runtime & Scaling

VariableDefaultDescription
cpu_limit1000mactual-server is a lightweight Node.js process; 1 vCPU suffices.
memory_limit1GiModest memory is enough for typical budget files.
min_instance_count1Keeps the single instance warm. Set 0 for scale-to-zero if a cold start on first request is acceptable.
max_instance_count1Keep at 1 — one shared SQLite volume, one writer.
container_port5006actual-server's native HTTP port.
execution_environmentgen2Required for the GCS FUSE /data mount.
enable_cloudsql_volumefalseNo Cloud SQL — leave false.

All other inputs follow standard App_CloudRun behaviour.

Group 5 — Access & Networking

VariableDefaultDescription
ingress_settingsinternalPrivate by default. Set all for public browser access, or internal-and-cloud-load-balancing behind an HTTPS load balancer.
enable_iapfalseAdd Google-identity authentication in front of the UI (load-balancer path).

All other inputs follow standard App_CloudRun behaviour.

Group 6 — Environment Variables & Secrets

VariableDefaultDescription
environment_variables{}Extra plain env vars. ACTUAL_PORT, ACTUAL_SERVER_FILES, and ACTUAL_USER_FILES are injected automatically.
secret_environment_variables{}Extra Secret Manager references. The ACTUAL_TOKEN secret is wired automatically when enable_api_key = true.

All other inputs follow standard App_CloudRun behaviour.

Groups 7–10 — Backup, CI/CD, Custom SQL, Domain & CDN

Standard App_CloudRun behaviour — see App_CloudRun. Note that the Custom SQL inputs (group 9) are inert for ActualBudget since there is no Cloud SQL instance.

Group 11 — Cloud Storage & Filesystem

VariableDefaultDescription
create_cloud_storagetrueThe storage data bucket is always declared by ActualBudget_Common.
gcs_volumes[]Additional GCS FUSE mounts; the /data storage mount is added automatically.
enable_nfsfalseNot needed — persistence is on the GCS bucket.

All other inputs follow standard App_CloudRun behaviour.

Group 12 — Database Backend

VariableDefaultDescription
database_typeNONEFixed to NONE by ActualBudget_Common — ActualBudget has no SQL database.

All other inputs in this group are forwarded for compatibility but not referenced.

Group 13 — Jobs & Scheduled Tasks

VariableDefaultDescription
initialization_jobs[]No default job. Supply your own only for custom data loading/migration.
cron_jobs[]Recurring jobs triggered by Cloud Scheduler.

Group 14 — Observability & Health

VariableDefaultDescription
startup_probeHTTP /, 15 s initial delay, 10 failuresPasses as soon as the Node server is listening.
liveness_probeHTTP /, 30 s initial delay, period 30 sRoot path, unauthenticated.
uptime_check_configdisabledEnable only once the endpoint is publicly reachable (ingress all or a load balancer).

All other inputs follow standard App_CloudRun behaviour.

Group 23 — VPC Service Controls & Audit Logging

enable_vpc_sc, vpc_cidr_ranges, vpc_sc_dry_run, organization_id, enable_audit_logging — standard App_CloudRun behaviour; see App_CloudRun.


5. Outputs

Returned on a successful deployment — the quickest way to locate and explore the running resources.

OutputDescription
service_nameCloud Run service name.
actualbudget_urlThe service's run.app URL. Reachable only from inside the VPC while ingress_settings = "internal".
service_locationRegion the service runs in.
stage_servicesStage-specific service details (Cloud Deploy).
load_balancer_ip / load_balancer_urlExternal HTTPS load balancer IP / URL (when enabled).
storage_bucketsCreated Cloud Storage buckets (includes the /data storage bucket).
network_name / network_exists / regionsVPC network, presence, regions.
container_image / container_registryDeployed image and Artifact Registry repo.
monitoring_enabled / monitoring_notification_channels / uptime_check_namesMonitoring status, channels, uptime checks.
initialization_jobsNames of any custom setup jobs (empty by default).
deployment_id / tenant_id / resource_prefixNaming identifiers.
project_id / project_numberProject identifiers.
cicd_enabled / github_repository_url / github_repository_owner / github_repository_name / cicd_configurationCI/CD status and details.
artifact_registry_repository / cloudbuild_trigger_name / cloudbuild_trigger_idRegistry and build trigger.
vpc_sc_enabled / vpc_sc_perimeter_name / vpc_sc_dry_run_modeVPC-SC status.
audit_logging_enabled / artifact_registry_cmek_enabledAudit logging and CMEK status.

6. Configuration Pitfalls & Sensible Defaults

The module ships plan-time validation for the most damaging misconfigurations (for example min_instance_count <= max_instance_count), but several settings deserve explicit care:

Risk: Critical (data loss / outage / security) — High (service degraded) — Medium (cost or partial degradation) — Low (minor).

SettingSensible valueRiskConsequence if wrong
max_instance_count1CriticalMultiple instances write the same SQLite files on one shared volume — corruption/conflict risk.
First-run server passwordset immediatelyCriticalUntil a password is set, anyone who can reach the URL can claim the server and its budget data.
Storage bucket contentsnever delete manuallyCritical/data on the GCS bucket is the only copy of the budget databases; deleting the bucket erases all budgets.
container_port5006Criticalactual-server's native port; mismatching it causes all health probes to fail.
execution_environmentgen2HighGCS FUSE volume mounts require gen2; gen1 leaves /data unmounted and data on ephemeral disk.
ingress_settingsinternal until hardenedHighFlipping to all before setting the server password exposes an unclaimed server publicly.
Heavy multi-user write loadmove to ActualBudget_GKE (block PVC)HighSQLite does not tolerate GCS FUSE under heavy concurrent write; Cloud Run suits single-user / light use.
enable_api_keytrue for automation on a public endpointMediumWithout ACTUAL_TOKEN, programmatic API access relies solely on the server password.
min_instance_count1 (or 0 to save cost)Medium0 adds a cold start to the first request after idle; data is safe either way (state is on GCS).
uptime_check_configenable once publicLowWhile ingress is internal the check cannot reach the service and will always fail.

For the foundation behaviour referenced throughout — service identity, scaling and concurrency, ingress and load balancing, CI/CD, Cloud Armor, IAP, Binary Authorization, VPC-SC, backups, and image mirroring — see App_CloudRun. ActualBudget-specific application configuration shared with the GKE variant is described in ActualBudget_Common.