Skip to main content

Ghost on Google Cloud Run

This document provides a comprehensive reference for the modules/Ghost_CloudRun Terraform module. It covers architecture, IAM, configuration variables, Ghost-specific behaviours, and operational patterns for deploying Ghost on Google Cloud Run (v2).


1. Module Overview

Ghost is a professional open-source publishing platform for newsletters, memberships, and content sites. Ghost CloudRun is a wrapper module built on top of App CloudRun. It uses App CloudRun for all GCP infrastructure provisioning and injects Ghost-specific application configuration, database initialisation, and storage configuration via Ghost Common.

Key Capabilities:

  • Compute: Cloud Run v2 (Gen2), Node.js container, 2 vCPU / 4 Gi by default. Scale-to-zero (min_instance_count = 0) with max_instance_count = 5 — both hardcoded, not user-configurable.
  • Data Persistence: Cloud SQL MySQL 8.0 (not PostgreSQL). NFS (GCE VM or Filestore) for shared content files. GCS ghost-content bucket auto-provisioned by Ghost Common.
  • Security: Inherits Cloud Armor WAF, IAP, Binary Authorization, and VPC Service Controls from App CloudRun. No application-level secrets are auto-generated — Ghost manages its own internal keys.
  • Caching: Redis enabled by default (enable_redis = true) — Ghost Common configures Ghost's page caching backend.
  • CI/CD: Cloud Build custom image pipeline by default; Cloud Deploy progressive delivery optional.
  • Reliability: Health probes target / (Ghost's root path) with 90-second initial delay to accommodate database migrations and theme compilation on first boot.

Project & Application Identity

VariableGroupTypeDefaultDescription
project_id1stringGCP project ID. Required.
tenant_deployment_id1string'demo'Short suffix appended to all resource names.
support_users1list(string)[]Email recipients for monitoring alerts.
resource_labels1map(string){}Labels applied to all provisioned resources.
application_name2string'ghost'Base resource name. Do not change after initial deployment.
display_name2string'Ghost Publishing'Human-readable name shown in the GCP Console. Maps to application_display_name in App CloudRun.
description2string'Ghost - Professional publishing platform'Cloud Run service description. Passed to Ghost Common.
application_version2string'6.14.0'Ghost image version tag. Increment to deploy a new release.

Wrapper architecture: Ghost CloudRun calls Ghost Common to build an application_config object containing Ghost-specific environment variables, probe configuration, and the db-init job definition. Ghost CloudRun hardcodes database__client = "mysql" into the merged config to ensure Ghost 6.x connects to MySQL rather than falling back to SQLite. module_storage_buckets carries the ghost-content bucket provisioned by Ghost Common. scripts_dir is resolved to abspath("${module.ghost_app.path}/scripts") at apply time.

MySQL note: Unlike every other module in this repo, Ghost requires MySQL 8.0, not PostgreSQL. database_type = "MYSQL_8_0" is fixed by Ghost Common and cannot be overridden.


2. IAM & Access Control

Ghost_CloudRun delegates all IAM provisioning to App_CloudRun. The Cloud Run SA, Cloud Build SA, IAP service agent, and password rotation role sets are identical to those in App_CloudRun §2.

No application-level secrets: Unlike Directus or Django, Ghost Common does not auto-generate application secrets (no equivalent of SECRET_KEY or DIRECTUS_KEY). Ghost manages its own internal signing keys at runtime. The DB_PASSWORD and ROOT_PASSWORD secrets are provisioned automatically by App CloudRun and consumed by the db-init job.

Database initialisation identity: The db-init Cloud Run Job runs under the Cloud Run SA. It connects to Cloud SQL MySQL via the Auth Proxy Unix socket (since enable_cloudsql_volume = true by default), using DB_HOST (the socket path under /cloudsql), DB_USER, and ROOT_PASSWORD (from Secret Manager).

120-second IAM propagation delay: Inherited from App CloudRun — the Ghost service is not deployed until the delay completes, preventing secret-read failures on the first revision start.

For the complete role tables and IAP, password rotation, and public access details, see App_CloudRun §2.


3. Core Service Configuration

A. Compute (Cloud Run)

Ghost is a Node.js application with significant resource requirements — it runs database migrations, compiles themes, and initialises membership features on startup. Ghost CloudRun exposes cpu_limit and memory_limit as dedicated top-level variables with production-ready defaults.

Scale-to-zero is enabled (min_instance_count = 0). Ghost cold starts can take 15–30 seconds due to theme compilation and database connection setup. For production sites, set min_instance_count = 1 by editing main.tf directly (see §7.A), or accept the cold start latency. max_instance_count = 5 is hardcoded — both values are set in main.tf and are not user-configurable.

Startup CPU Boost is always enabled (hardcoded in App CloudRun).

Container image: container_image_source defaults to 'custom', meaning Cloud Build compiles a custom image using Ghost_Common's Dockerfile (extending the official ghost base image). Set container_image_source = 'prebuilt' and container_image = 'ghost:6.14.0' to skip the build and deploy the upstream image directly.

VariableGroupDefaultDescription
deploy_application3trueSet false for infrastructure-only deployment (SQL, storage, secrets).
container_image_source3'custom''custom' builds via Cloud Build (default). 'prebuilt' deploys an existing image URI.
container_image3""Override image URI. Leave empty for Cloud Build to manage the image.
cpu_limit3'2000m'CPU per instance. 2 vCPU minimum for reliable Ghost operation.
memory_limit3'4Gi'Memory per instance. 4 Gi recommended; do not set below 512 Mi.
container_port32368Ghost's native HTTP port. Change only if your custom Dockerfile binds Ghost to a different port.
execution_environment3'gen2'Gen2 required for NFS mounts and GCS Fuse.
timeout_seconds3300Max request duration. Increase for long-running newsletter sends or image processing.
enable_cloudsql_volume3trueDefault true — Ghost connects via Unix socket. Set false for TCP.
traffic_split3[]Percentage-based canary/blue-green traffic allocation. See §7.B.
service_annotations3{}Advanced Cloud Run annotations.
service_labels3{}Labels applied to the Cloud Run service.

Differences from App CloudRun defaults:

VariableApp CloudRunGhost CloudRunReason
container_port80802368Ghost's native port.
cpu_limit'1000m''2000m'Ghost requires ≥2 vCPU for theme compilation and member features.
memory_limit'512Mi''4Gi'Ghost's Node.js process + themes + membership caches require significantly more RAM.
enable_cloudsql_volumetruetrueSame — Ghost connects via Auth Proxy Unix socket.
min_instance_count00 [fixed]Scale-to-zero; hardcoded in main.tf locals merge.
max_instance_count15 [fixed]Higher ceiling for traffic spikes; hardcoded in main.tf locals merge.
enable_image_mirroringfalsetrueGhost mirrors its base image to Artifact Registry by default.

B. Database (Cloud SQL — MySQL 8.0)

Ghost requires MySQL 8.0Ghost Common fixes database_type = "MYSQL_8_0" and hardcodes database__client = "mysql" in the environment. PostgreSQL, SQL Server, and other engines are unsupported and will cause Ghost to fail at startup.

The module uses db_name and db_user in place of the application_database_name and application_database_user variables in App CloudRun.

Unix socket connection: enable_cloudsql_volume defaults to true. App CloudRun injects the Auth Proxy sidecar and sets DB_HOST to the socket path under /cloudsql.

VariableGroupDefaultDescription
db_name11'ghost'MySQL database name. Do not change after initial deployment.
db_user11'ghost'MySQL application user. Password auto-generated and stored in Secret Manager.
database_password_length1132Auto-generated password length. Range: 16–64.
enable_auto_password_rotation11falseAutomated zero-downtime password rotation. See §7.D.
rotation_propagation_delay_sec1190Seconds to wait after rotation before restarting the service.

database_type, enable_postgres_extensions, and enable_mysql_plugins are not exposed — Ghost only supports MySQL 8.0, and database setup is managed by Ghost Common's db-init.sh script. sql_instance_name and sql_instance_base_name are not exposed either; Cloud SQL discovery/inline provisioning is handled transparently by App CloudRun.

C. Storage (NFS & GCS)

NFS is enabled by default (enable_nfs = true). Ghost stores uploaded images, themes, and other content files on the NFS share so that all Cloud Run instances access a consistent filesystem. Requires execution_environment = 'gen2'.

GCS content bucket: Ghost Common automatically provisions a dedicated ghost-content GCS bucket and configures Ghost to use it for media storage via GCS Fuse. This bucket is separate from any buckets in storage_buckets.

VariableGroupDefaultDescription
enable_nfs10trueProvisions an NFS volume for shared content files. Requires gen2. Set false if using only GCS Fuse for content storage.
nfs_mount_path10'/mnt/nfs'Container path where the NFS share is mounted.
create_cloud_storage10trueSet false to skip additional bucket creation. The ghost-content bucket from Ghost Common is always provisioned.
storage_buckets10[{ name_suffix = "data" }]Additional GCS buckets beyond the auto-provisioned content bucket.
gcs_volumes10[]GCS buckets to mount via GCS Fuse (requires gen2). Each entry: name, bucket_name, mount_path, readonly, mount_options.
nfs_instance_name8""Name of an existing NFS GCE VM. Leave empty to auto-discover or use inline instance.
nfs_instance_base_name8'app-nfs'Base name for an inline NFS GCE VM when none exists. Deployment ID is appended.
manage_storage_kms_iam10falseCreates a CMEK KMS keyring/key and enables CMEK on all storage buckets.
enable_artifact_registry_cmek10falseCreates an Artifact Registry KMS key and enables at-rest CMEK encryption of container images.

D. Networking

Cloud Run uses Direct VPC Egress to reach Cloud SQL's internal IP. Because enable_cloudsql_volume = true is the default, the Auth Proxy sidecar handles the Cloud SQL connection via Unix socket.

VariableGroupDefaultDescription
ingress_settings4'all''all' — public internet; 'internal' — VPC only; 'internal-and-cloud-load-balancing' — forces traffic through the HTTPS Load Balancer.
vpc_egress_setting4'PRIVATE_RANGES_ONLY''PRIVATE_RANGES_ONLY' routes only RFC 1918 traffic via VPC. 'ALL_TRAFFIC' routes all egress via VPC.

network_name is not exposed. The module auto-discovers the Services GCP VPC network.

E. Initialization & Bootstrap

A db-init Cloud Run Job is automatically provisioned by Ghost Common when initialization_jobs is left as the default empty list ([]). It uses the mysql:8.0-debian image and executes Ghost_Common/scripts/db-init.sh, which performs the following idempotent operations:

  1. Connects to Cloud SQL MySQL via the Auth Proxy Unix socket.
  2. Creates the ghost database user with the password from Secret Manager.
  3. Creates the ghost database if it does not exist.
  4. Grants the ghost user full privileges on the database.

Override initialization_jobs with a non-empty list to replace this default with custom jobs. When initialization_jobs is non-empty, Ghost Common does not inject the default db-init job.

Additional recurring cron jobs can be defined via cron_jobs:

VariableGroupDefaultDescription
initialization_jobs12[]One-shot Cloud Run Jobs. Leave empty for Ghost Common to supply the default db-init job. Non-empty list replaces it entirely. Each entry: name, description, image, command, args, env_vars, secret_env_vars, cpu_limit, memory_limit, timeout_seconds, max_retries, task_count, execution_mode, mount_nfs, mount_gcs_volumes, depends_on_jobs, execute_on_apply, script_path.
cron_jobs12[]Recurring jobs triggered by Cloud Scheduler. Each entry: name, schedule (cron UTC), image, command, args, env_vars, secret_env_vars, cpu_limit, memory_limit, timeout_seconds, max_retries, task_count, parallelism, mount_nfs, mount_gcs_volumes, script_path, paused.

Backup Import: If enable_backup_import = true, a dedicated Cloud Run Job restores a backup into the MySQL database during the apply. See §8.C for all backup variables.


4. Advanced Security

A. Cloud Armor WAF

Identical behaviour to App CloudRun. When enable_cloud_armor = true, a Global HTTPS Load Balancer with a Cloud Armor WAF policy (OWASP Top 10, adaptive DDoS, 500 req/min rate limiting) is provisioned in front of Cloud Run.

VariableGroupDefaultDescription
enable_cloud_armor9falseProvisions Global HTTPS LB + Cloud Armor WAF. Required for custom domains, CDN, and DDoS protection.
admin_ip_ranges9[]CIDR ranges exempted from WAF rules (e.g., office VPN, CI/CD egress IPs).

B. Identity-Aware Proxy (IAP)

When enable_iap = true, Cloud Run's native IAP integration is enabled directly on the service. Google identity authentication is required before requests reach Ghost. Useful for staging environments or internal Ghost admin access.

VariableGroupDefaultDescription
enable_iap4falseEnables IAP natively on the Cloud Run service.
iap_authorized_users4[]Users/service accounts granted access. Format: 'user:email' or 'serviceAccount:sa@...'.
iap_authorized_groups4[]Google Groups granted access. Format: 'group:name@example.com'.

See App_CloudRun §4.B for the full IAM role details.

C. Binary Authorization

Identical to App CloudRun. When enable_binary_authorization = true, Cloud Run enforces that deployed images carry a valid cryptographic attestation.

VariableGroupDefaultDescription
enable_binary_authorization7falseEnforces image attestation. Requires a Binary Authorization policy and attestor pre-configured in the project.

D. VPC Service Controls

Identical to App CloudRun. When enable_vpc_sc = true, all GCP API calls from this module are bound within an existing VPC-SC perimeter.

VariableGroupDefaultDescription
enable_vpc_sc22falseRegisters module API calls within the project's VPC-SC perimeter. A perimeter must already exist before enabling.

E. Secret Manager Integration

Ghost application secrets are stored in Secret Manager and injected natively by Cloud Run at revision start — plaintext is never written to Terraform state.

Unlike Directus or Django, Ghost Common does not auto-generate application-level secrets. The DB_PASSWORD and ROOT_PASSWORD secrets are provisioned automatically by App CloudRun. User-defined secrets can be added via secret_environment_variables.

VariableGroupDefaultDescription
secret_environment_variables5{}Map of env var name → Secret Manager secret ID. Resolved at runtime; never stored in state. (e.g., { SMTP_PASSWORD = "ghost-smtp-password" })
secret_rotation_period5'2592000s'Frequency at which Secret Manager emits rotation notifications. Default: 30 days.
secret_propagation_delay530Seconds to wait after secret creation before dependent resources proceed.

5. Traffic & Ingress

A. HTTPS Load Balancer

Identical to App CloudRun. When enable_cloud_armor = true, a Global HTTPS Load Balancer backed by a Serverless NEG is provisioned. Traffic flows: Internet → Cloud Armor → Global HTTPS LB → Serverless NEG → Cloud Run.

Setting ingress_settings = 'internal-and-cloud-load-balancing' forces all Ghost traffic through the LB, preventing direct *.run.app URL access.

See App_CloudRun §5.A for full architecture details.

B. Cloud CDN

When enable_cdn = true (requires enable_cloud_armor = true), Cloud CDN is attached to the HTTPS Load Balancer backend.

Ghost consideration: Ghost serves a mix of public cached pages and authenticated member-only content. Cloud CDN is well-suited for Ghost's public pages and static assets (theme CSS/JS, images). Ghost's built-in caching layer (backed by Redis) already handles page-level caching — CDN adds an additional edge layer for public content. Ensure member-gated content responses include appropriate Cache-Control or Vary headers before enabling.

VariableGroupDefaultDescription
enable_cdn9falseEnables Cloud CDN on the HTTPS LB backend. Only effective when enable_cloud_armor = true.
max_images_to_retain97Maximum number of recent container images to keep in Artifact Registry. Set 0 to disable.
delete_untagged_images9trueAutomatically deletes untagged (dangling) images from Artifact Registry.
image_retention_days930Days after which images are eligible for deletion. Set 0 to disable age-based deletion.

C. Custom Domains

Custom domains are attached to the Global HTTPS Load Balancer via application_domains. Google-managed SSL certificates are provisioned automatically. DNS must point to the load balancer IP after apply.

VariableGroupDefaultDescription
application_domains9[]Custom domain names for the HTTPS LB. Google-managed SSL certificates provisioned per domain. (e.g., ['ghost.example.com'])

After the first apply, retrieve the LB IP from the Terraform output load_balancer_ip and create an A record. SSL certificate provisioning takes 10–30 minutes after DNS propagation.


6. CI/CD & Delivery

A. Cloud Build Triggers

Identical to App CloudRun. When enable_cicd_trigger = true, a Cloud Build GitHub connection and push trigger are provisioned. The trigger builds and deploys a custom Ghost image when code is pushed to the configured branch.

Typical use case: The default container_image_source = 'custom' already uses Cloud Build to build a Ghost image with Ghost_Common's Dockerfile. Enabling a CI/CD trigger automates this pipeline on repository push — useful when Ghost themes, plugins, or configuration are maintained in source control.

VariableGroupDefaultDescription
enable_cicd_trigger7falseProvisions a Cloud Build GitHub trigger. Requires github_repository_url and credentials.
github_repository_url7""Full HTTPS URL of the GitHub repository.
github_token7""GitHub PAT (repo, admin:repo_hook scopes). Required on first apply. Sensitive.
github_app_installation_id7""GitHub App installation ID (preferred for organisation repos).
cicd_trigger_config7{ branch_pattern = "^main$" }Advanced trigger config: branch_pattern, included_files, ignored_files, trigger_name, substitutions.

B. Cloud Deploy Pipeline

When enable_cloud_deploy = true (requires enable_cicd_trigger = true), the CI/CD pipeline is upgraded to a managed Cloud Deploy delivery pipeline with sequential promotion stages.

VariableGroupDefaultDescription
enable_cloud_deploy7falseProvisions a Cloud Deploy pipeline. Requires enable_cicd_trigger = true.
cloud_deploy_stages7[dev, staging, prod(approval)]Ordered promotion stages. Each: name, target_name, service_name, require_approval, auto_promote.

See App_CloudRun §6.B for the approval workflow and multi-project deployment details.


7. Reliability & Scheduling

A. Scaling & Concurrency

min_instance_count = 0 and max_instance_count = 5 are hardcoded in main.tf and are not user-configurable. Ghost cold starts take 15–30 seconds due to theme compilation and database connection setup. The max_instance_count = 5 ceiling accommodates traffic spikes from newsletter sends and content publication events.

Ghost uses Redis-backed page caching, so multiple instances can serve requests without cache inconsistency. Session management uses database-backed or cookie-based sessions, making horizontal scaling safe.

To change instance counts, you must modify main.tf directly (the merge block in the locals section) or deploy via App CloudRun with a custom application_config.

B. Traffic Splitting

Traffic splitting is supported. Ghost's page cache is backed by Redis (shared across instances), making canary deployments safe — cached pages are served consistently regardless of which instance handles the request.

VariableGroupDefaultDescription
traffic_split3[]Percentage-based traffic allocation across named revisions. All entries must sum to 100. Empty sends 100% to the latest revision.

See App_CloudRun §7.B for the full configuration syntax.

C. Health Probes & Uptime Monitoring

Ghost does not expose a dedicated health endpoint. Both the startup and liveness probes target / (Ghost's root path), which returns HTTP 200 when the application is fully initialised.

Ghost 6.x performs database migrations and theme compilation on first boot. The startup probe defaults allow 90 seconds of initial delay plus up to 10 retry periods of 10 seconds each — giving Ghost up to 190 seconds of total startup tolerance on cold deployments.

VariableGroupDefaultDescription
startup_probe13{ enabled=true, type="HTTP", path="/", initial_delay_seconds=90, timeout_seconds=10, period_seconds=10, failure_threshold=10 }Startup readiness probe. Container receives no traffic until this succeeds.
liveness_probe13{ enabled=true, type="HTTP", path="/", initial_delay_seconds=60, timeout_seconds=5, period_seconds=30, failure_threshold=3 }Liveness probe. Container is restarted after failure_threshold consecutive failures.
uptime_check_config13{ enabled=true, path="/" }Cloud Monitoring uptime check. Alerts notify support_users if unreachable.
alert_policies13[]Cloud Monitoring metric alert policies. Each: name, metric_type, comparison, threshold_value, duration_seconds.

Differences from App CloudRun probe defaults:

FieldApp CloudRunGhost CloudRunReason
path/healthz/Ghost has no /healthz endpoint; root returns HTTP 200 when ready.
Startup initial_delay_seconds1090Ghost runs DB migrations + theme compilation before accepting traffic.
Startup timeout_seconds510Root path can be slow during first-boot schema setup.
Liveness initial_delay_seconds1560Prevents premature restarts before startup completes.

D. Auto Password Rotation

When enable_auto_password_rotation = true, a zero-downtime password rotation pipeline is provisioned identically to App CloudRun:

  1. Secret Manager emits a rotation notification at every secret_rotation_period interval.
  2. Eventarc fires a Cloud Run rotation Job.
  3. The job generates a new password, updates the Cloud SQL MySQL user, writes a new secret version.
  4. After rotation_propagation_delay_sec seconds, the job restarts the Ghost service.

Ghost re-establishes its database connection on restart and reads the updated DB_PASSWORD from Secret Manager. No manual intervention is required.

VariableGroupDefaultDescription
enable_auto_password_rotation11falseEnables automated password rotation.
rotation_propagation_delay_sec1190Seconds to wait after writing the new secret before restarting the service.
secret_rotation_period5'2592000s'Rotation frequency. Default: 30 days.

8. Integrations

A. Redis Cache

Redis is enabled by default (enable_redis = true). Ghost Common configures Ghost's caching backend to use Redis, significantly reducing database query load and improving page delivery speed under concurrent traffic.

When enable_redis = true and redis_host is not provided, the module defaults to using the NFS server IP as the Redis host (a lightweight Redis instance co-located on the NFS GCE VM). For production deployments, point redis_host at a dedicated Google Cloud Memorystore for Redis instance.

VariableGroupDefaultDescription
enable_redis21trueEnables Redis for Ghost page caching. Recommended for all deployments.
redis_host21""Redis server hostname or IP. Leave blank to use the NFS server IP. Override with a Memorystore instance for production.
redis_port21'6379'Redis server TCP port (string).
redis_auth21""Redis AUTH password. Leave empty if the Redis instance does not require authentication. Sensitive — never stored in state.

Note: Redis is in group 21 in Ghost CloudRun (vs group 10 in App CloudRun).

B. Email (SMTP)

Ghost uses SMTP for transactional email: member sign-up confirmations, password resets, newsletter sends, and comment notifications. The environment_variables variable includes Ghost-specific SMTP defaults.

Default environment_variables:

environment_variables = {
SMTP_HOST = ""
SMTP_PORT = "25"
SMTP_USER = ""
SMTP_PASSWORD = ""
SMTP_SSL = "false"
EMAIL_FROM = "ghost@example.com"
}

Configure these before going live. Use secret_environment_variables for SMTP_PASSWORD:

environment_variables = {
SMTP_HOST = "smtp.mailgun.org"
SMTP_PORT = "587"
SMTP_USER = "postmaster@mg.example.com"
SMTP_SSL = "true"
EMAIL_FROM = "noreply@example.com"
}

secret_environment_variables = {
SMTP_PASSWORD = "ghost-smtp-password"
}

The database__client = "mysql" variable is injected automatically by main.tf — do not set it manually in environment_variables.

VariableGroupDefaultDescription
environment_variables5SMTP defaults (see above)Plain-text env vars. Do not include database__client here.
secret_environment_variables5{}Secret Manager references. Use for SMTP_PASSWORD and other sensitive values.

C. Backup Import & Recovery

When enable_backup_import = true, a dedicated Cloud Run Job restores an existing database backup into the provisioned Cloud SQL MySQL instance. This runs after the db-init job and before the Ghost service is deployed.

Ghost uses backup_uri (like Directus, not backup_file as in Django). backup_uri accepts a full GCS object URI or Google Drive file ID.

VariableGroupDefaultDescription
backup_schedule6'0 2 * * *'Cron expression (UTC) for automated daily backups.
backup_retention_days67Days to retain backup files in GCS.
enable_backup_import6falseTriggers a one-time restore on apply. Set false after a successful import.
backup_source6'gcs''gcs' (full GCS URI) or 'gdrive' (Drive file ID).
backup_uri6""Full GCS URI (e.g., 'gs://my-bucket/ghost-2024-01.sql') or Google Drive file ID. Maps to backup_file in App CloudRun.
backup_format6'sql'Backup file format. Options: sql, tar, gz, tgz, tar.gz, zip.

Warning: If the database already contains data, the import may produce errors. Test in a non-production environment before importing into production.

D. Observability & Alerting

Observability is identical to App CloudRun. A Cloud Monitoring uptime check polls the Ghost endpoint from multiple global locations. Custom alert policies can monitor Cloud Run metrics (latency, error rate, instance count) and notify support_users.

VariableGroupDefaultDescription
uptime_check_config13{ enabled=true, path="/" }Uptime check: enabled, path, check_interval (e.g., "60s"), timeout (e.g., "10s").
alert_policies13[]Metric alert policies. Each: name, metric_type, comparison, threshold_value, duration_seconds, aggregation_period.
support_users1[]Email addresses notified by uptime and alert policy triggers.

9. Platform-Managed Behaviours

The following behaviours are applied automatically by Ghost CloudRun regardless of variable values. They cannot be overridden via tfvars.

BehaviourImplementationDetail
MySQL 8.0 requireddatabase_type = "MYSQL_8_0" fixed by Ghost CommonGhost 6.x only supports MySQL. PostgreSQL is not supported.
MySQL client forceddatabase__client = "mysql" hardcoded in main.tfWithout this, Ghost 6.x silently falls back to SQLite even when all other database connection variables are present.
Scale limits fixedmin_instance_count = 0, max_instance_count = 5 hardcoded in main.tfNot user-configurable via tfvars. Modify main.tf directly (the locals merge block) if different values are required.
GCS content bucketghost-content bucket provisioned by Ghost Common via module_storage_bucketsA dedicated GCS bucket for Ghost content is provisioned separately from storage_buckets.
Unix socket by defaultenable_cloudsql_volume = true defaultGhost connects to Cloud SQL via the Auth Proxy Unix socket. Set false for TCP.
NFS enabled by defaultenable_nfs = true defaultNFS shared storage is provisioned for Ghost content files. Requires execution_environment = 'gen2'.
Redis enabled by defaultenable_redis = true defaultUnlike Django (opt-in), Ghost's Redis integration is on by default. When redis_host is blank, the NFS server IP is used.
Default db-init jobSupplied by Ghost Common when initialization_jobs = []MySQL database and user are created automatically. Override with a non-empty initialization_jobs list to replace this behaviour.
No auto-generated app secretsmodule_secret_env_vars = {}Ghost manages its own internal signing keys. No SECRET_KEY or equivalent is created.
Scripts directoryscripts_dir = abspath("${module.ghost_app.path}/scripts")Initialization scripts are sourced from Ghost Common, not from the deployment directory.

Inline infrastructure (when no Services_GCP stack is present) is identical to App_CloudRun §9 — App_CloudRun provisions an inline VPC, Cloud NAT, Cloud SQL instance, service accounts, and GCP APIs as required. See App_CloudRun §9 for the full inline resource inventory and teardown notes.


10. Variable Reference

All user-configurable variables exposed by Ghost CloudRun, sorted by UI group then order. Group 0 variables are reserved for platform metadata — leave them at their defaults for standard deployments.

Variables marked [fixed] are hardcoded by the module and cannot be overridden.

VariableGroupDefaultDescription
module_description0(Ghost platform text)Platform metadata: module description.
module_documentation0(docs URL)Platform metadata: documentation URL.
module_dependency0['Services GCP']Platform metadata: required modules.
module_services0(GCP service list)Platform metadata: GCP services consumed.
credit_cost050Platform metadata: deployment credit cost.
require_credit_purchases0falsePlatform metadata: enforces credit balance check.
enable_purge0truePermits full deletion of module resources on destroy.
public_access0truePlatform catalogue visibility.
deployment_id0""Deployment ID suffix. Auto-generated if empty.
resource_creator_identity0(platform SA)Service account used by Terraform to manage resources.
project_id1GCP project ID. Required.
tenant_deployment_id1'demo'Short suffix appended to all resource names.
support_users1[]Email addresses for monitoring alerts.
resource_labels1{}Labels applied to all provisioned resources.
application_name2'ghost'Base resource name. Do not change after initial deployment.
display_name2'Ghost Publishing'Human-readable name. Maps to application_display_name in App CloudRun.
description2'Ghost - Professional publishing platform'Service description. Passed to Ghost Common.
application_version2'6.14.0'Ghost container image tag.
deploy_application3trueSet false for infrastructure-only deployment.
container_image_source3'custom''custom' (Cloud Build) or 'prebuilt' (existing image).
container_image3""Container image URI. Leave empty for Cloud Build to manage.
cpu_limit3'2000m'CPU per instance. 2 vCPU minimum for Ghost.
memory_limit3'4Gi'Memory per instance. 4 Gi recommended for production.
container_port32368Ghost's native port.
execution_environment3'gen2'Gen2 required for NFS mounts and GCS Fuse.
timeout_seconds3300Max request duration. Increase for newsletter sends.
enable_cloudsql_volume3trueDefault true — Ghost connects via Unix socket.
cloudsql_volume_mount_path3'/cloudsql'Container path for the Auth Proxy Unix socket.
container_protocol3'http1''http1' or 'h2c'.
enable_image_mirroring3trueMirrors the Ghost image into Artifact Registry.
traffic_split3[]Canary/blue-green traffic allocation.
max_revisions_to_retain37Maximum number of Cloud Run revisions to keep. Revisions serving traffic are never deleted. Set 0 to disable.
service_annotations3{}Advanced Cloud Run annotations.
service_labels3{}Labels applied to the Cloud Run service.
min_instance_count0[fixed] Hardcoded in main.tf.
max_instance_count5[fixed] Hardcoded in main.tf.
ingress_settings4'all''all', 'internal', or 'internal-and-cloud-load-balancing'.
vpc_egress_setting4'PRIVATE_RANGES_ONLY''PRIVATE_RANGES_ONLY' or 'ALL_TRAFFIC'.
enable_iap4falseEnables IAP natively on the Cloud Run service.
iap_authorized_users4[]Users/SAs granted IAP access.
iap_authorized_groups4[]Google Groups granted IAP access.
environment_variables5SMTP defaultsPlain-text env vars. database__client is injected automatically — do not set it here.
secret_environment_variables5{}Secret Manager references (e.g., { SMTP_PASSWORD = "ghost-smtp-password" }).
secret_propagation_delay530Seconds to wait after secret creation.
secret_rotation_period5'2592000s'Secret Manager rotation notification frequency.
backup_schedule6'0 2 * * *'Cron expression (UTC) for automated backups.
backup_retention_days67Days to retain backup files in GCS.
enable_backup_import6falseTriggers a one-time restore on apply.
backup_source6'gcs''gcs' (full URI) or 'gdrive' (file ID).
backup_uri6""Full GCS URI or Google Drive file ID. Maps to backup_file in App CloudRun.
backup_format6'sql'Backup format. Options: sql, tar, gz, tgz, tar.gz, zip.
enable_cicd_trigger7falseProvisions a Cloud Build GitHub trigger.
github_repository_url7""Full HTTPS URL of the GitHub repository.
github_token7""GitHub PAT. Required on first apply. Sensitive.
github_app_installation_id7""GitHub App installation ID.
cicd_trigger_config7{ branch_pattern = "^main$" }Advanced Cloud Build trigger config.
enable_cloud_deploy7falseProvisions a Cloud Deploy progressive delivery pipeline.
cloud_deploy_stages7[dev, staging, prod(approval)]Ordered Cloud Deploy promotion stages.
enable_binary_authorization7falseEnforces image attestation on deployment.
enable_custom_sql_scripts8falseRuns SQL scripts from GCS after provisioning.
custom_sql_scripts_bucket8""GCS bucket containing SQL scripts.
custom_sql_scripts_path8""Path prefix within the bucket.
custom_sql_scripts_use_root8falseRun scripts as the root DB user.
nfs_instance_name8""Name of an existing NFS GCE VM. Leave empty to auto-discover.
nfs_instance_base_name8'app-nfs'Base name for inline NFS VM. Deployment ID is appended.
enable_cloud_armor9falseProvisions Global HTTPS LB + Cloud Armor WAF.
admin_ip_ranges9[]CIDR ranges exempted from WAF rules.
application_domains9[]Custom domains with Google-managed SSL certificates.
enable_cdn9falseEnables Cloud CDN on the HTTPS LB backend.
max_images_to_retain97Maximum number of recent container images to keep in Artifact Registry. Set 0 to disable.
delete_untagged_images9trueAutomatically deletes untagged (dangling) images from Artifact Registry.
image_retention_days930Days after which images are eligible for deletion. Set 0 to disable age-based deletion.
create_cloud_storage10trueSet false to skip GCS bucket creation.
storage_buckets10[{ name_suffix = "data" }]Additional GCS buckets to provision.
enable_nfs10trueProvisions NFS shared storage for Ghost content. Requires gen2.
nfs_mount_path10'/mnt/nfs'Container path where NFS is mounted.
gcs_volumes10[]GCS buckets to mount via GCS Fuse (requires gen2).
manage_storage_kms_iam10falseCreates CMEK KMS key and enables CMEK on all storage buckets.
enable_artifact_registry_cmek10falseCreates Artifact Registry KMS key for at-rest image encryption.
db_name11'ghost'MySQL database name. Do not change after initial deployment.
db_user11'ghost'MySQL application user.
database_password_length1132Auto-generated password length. Range: 16–64.
enable_auto_password_rotation11falseAutomated zero-downtime password rotation.
rotation_propagation_delay_sec1190Seconds to wait after rotation before restarting the service.
initialization_jobs12[]One-shot Cloud Run Jobs. Leave empty for Ghost Common to supply the default db-init job. Each entry: name, description, image, command, args, env_vars, secret_env_vars, cpu_limit, memory_limit, timeout_seconds, max_retries, task_count, execution_mode, mount_nfs, mount_gcs_volumes, depends_on_jobs, execute_on_apply, script_path.
cron_jobs12[]Recurring scheduled Cloud Run Jobs. Each entry includes parallelism, mount_nfs, mount_gcs_volumes, and script_path fields in addition to the standard scheduling fields.
startup_probe13{ path="/", initial_delay_seconds=90, failure_threshold=10, ... }Startup probe. Long initial delay for Ghost DB migrations.
liveness_probe13{ path="/", initial_delay_seconds=60, failure_threshold=3, ... }Liveness probe.
uptime_check_config13{ enabled=true, path="/" }Cloud Monitoring uptime check.
alert_policies13[]Cloud Monitoring metric alert policies.
enable_redis21trueEnabled by default. Redis for Ghost page caching.
redis_host21""Redis hostname/IP. Defaults to NFS server IP when empty.
redis_port21'6379'Redis TCP port (string).
redis_auth21""Redis AUTH password. Sensitive.
enable_vpc_sc22falseRegisters API calls within the project's VPC-SC perimeter.
vpc_cidr_ranges22[]VPC subnet CIDR ranges for VPC-SC network access level. Auto-discovered when empty.
vpc_sc_dry_run22trueLogs VPC-SC violations without blocking. Set false to enforce.
organization_id22""GCP Organization ID for VPC-SC. Auto-discovered from project when empty.
enable_audit_logging22falseEnables detailed Cloud Audit Logs (DATA_READ, DATA_WRITE, ADMIN_READ).

11. Outputs

OutputDescription
service_nameName of the Cloud Run service.
service_urlPublic URL of the Cloud Run service.
service_locationGCP region where the Cloud Run service is deployed.
project_idGCP project ID.
deployment_idDeployment ID suffix used in resource names.
database_instance_nameName of the Cloud SQL MySQL instance.
database_nameName of the application database.
database_userName of the application database user.
database_password_secretSecret Manager secret name for the database password.
storage_bucketsCreated GCS storage buckets.
nfs_server_ipNFS server internal IP (sensitive).
nfs_mount_pathNFS mount path inside containers.
container_imageContainer image used for the deployment.
cicd_enabledWhether the CI/CD pipeline is enabled.
github_repository_urlGitHub repository URL connected for CI/CD.

Configuration Pitfalls & Sensible Defaults

Risk levels: Critical (data loss, full outage, security breach) — High (service unavailable or significant degradation) — Medium (degraded function or increased cost) — Low (minor impact).

VariableSensible DefaultRiskConsequence of Incorrect Value
project_id(required)CriticalNo default — deployment fails immediately.
database_type"MYSQL_8_0"CriticalGhost requires MySQL exclusively. Setting to POSTGRES or NONE will cause Ghost to fail at startup with a database connection error — Ghost's knex adapter in this module targets MySQL only.
enable_redistrueHighRedis is enabled by default. When no redis_host is provided the module uses the NFS server IP — if enable_nfs = false as well, Ghost will fail to connect to Redis at startup. Disable Redis (enable_redis = false) only when a dedicated Redis is not available and NFS is also off.
redis_host"" (auto-resolves to NFS IP)HighRelies on NFS server IP when blank. If enable_nfs = false and redis_host remains empty with enable_redis = true, Ghost cannot initialise its caching layer and will crash on startup.
enable_nfstrueCriticalGhost stores all content (themes, images, routes, settings) under /var/lib/ghost/content. Without NFS, this directory is ephemeral — all uploaded images and theme customisations are lost on every new Cloud Run revision. Multiple instances will serve inconsistent content.
container_image_source"custom"HighGhost requires a custom build to inject GCP configuration (database socket path, content path symlinks). Using "prebuilt" with the upstream ghost Docker Hub image may work for local testing but will fail to connect to Cloud SQL via Unix socket in Cloud Run.
container_port2368CriticalGhost listens on 2368 by default. Changing this without matching the Ghost url configuration causes Cloud Run health probes to fail and the service to be marked unhealthy.
memory_limit"4Gi"HighGhost 6.x with image processing, newsletter rendering, and theme compilation requires significant memory. Reducing below 1Gi causes Node.js OOM crashes under moderate load. The documented minimum is 512Mi — this is only viable for low-traffic or dev deployments.
min_instance_count1MediumScale-to-zero (0) causes cold starts of 10–30 seconds. Ghost performs database migrations on first boot — cold starts can cause request timeouts for impatient visitors. Set to 1 for any production publication.
ingress_settings"all"Medium"all" exposes Ghost's admin panel (/ghost) to the public internet. For admin-only access, consider enable_iap = true or restricting the /ghost path via Cloud Armor.
execution_environment"gen2"HighNFS mounts require gen2. Switching to "gen1" while enable_nfs = true causes the Cloud Run revision to fail to start.
db_name"ghost"CriticalImmutable after first deployment — changing this causes Terraform to recreate the database, destroying all Ghost content, posts, and members data.
db_user"ghost"CriticalImmutable after first deployment — changing this recreates the Cloud SQL user and invalidates all stored credentials.
database_password_length32MediumMinimum is 16 (enforced). Short passwords increase database brute-force risk.
environment_variables (SMTP){ SMTP_HOST = "", SMTP_PORT = "25", ... }HighGhost uses SMTP for member welcome emails, newsletters, and password resets. Leaving SMTP_HOST empty disables email entirely — members cannot receive newsletters and cannot reset passwords. Configure a real SMTP provider (SendGrid, Mailgun, SES) before going live.
backup_retention_days7MediumSeven days is insufficient for active publications. Losing more than 7 days of posts and member data is a serious content loss. Increase to 30+ days for any production Ghost blog.
enable_cloud_armorfalseMediumWithout Cloud Armor, the Ghost admin panel is protected only by Ghost's own auth. Bot traffic and credential stuffing attacks against /ghost are common. Recommended for any publicly visible publication.
enable_backup_importfalseCriticalRequires backup_uri to be a valid, accessible GCS or Drive path. Enabling with an empty backup_uri causes the restore Cloud Run job to fail during tofu apply.
startup_probe initial_delay_seconds90HighGhost runs database migrations on first boot which can take 60–120 seconds. Reducing initial_delay_seconds below 60 causes Cloud Run to kill the container before Ghost is ready, creating a crash-restart loop.
enable_iapfalseMediumWith IAP disabled and ingress_settings = "all", anyone can reach the Ghost admin panel URL. Consider enabling IAP for staff-only Ghost installations.
enable_cdnfalseMediumWithout CDN, all requests hit Cloud Run directly. For publications with significant static asset traffic (images, JS), CDN significantly reduces cost and latency.
vpc_egress_setting"PRIVATE_RANGES_ONLY"MediumGhost needs to reach external SMTP servers and the Unsplash API for stock images. PRIVATE_RANGES_ONLY permits direct public egress. Changing to "ALL_TRAFFIC" with a restrictive VPC firewall will block these outbound connections.
secret_propagation_delay30LowOccasionally insufficient in multi-region setups. Increase to 60–90s if ghost secrets are not found during apply.

Destroying Resources

Known Deletion Issue: Serverless IPv4 Address Release

When destroying a Cloud Run deployment, you may encounter an error similar to:

Error: Error waiting for Subnetwork to be deleted: The following serverless IPv4 address(es) on subnet ... are still in use.

Cause: GCP holds serverless IPv4 addresses on the VPC subnet asynchronously after a Cloud Run service is deleted. These addresses are released by GCP approximately 20–30 minutes after the Cloud Run service is removed. Terraform/OpenTofu cannot complete the subnet or VPC deletion until they are fully released.

Resolution: Wait 20–30 minutes after the initial destroy attempt, then re-run the destroy command:

tofu destroy

The second run will succeed once GCP has released the reserved addresses.