Skip to main content

Certification track: Associate Cloud Engineer (ACE)

Listmonk on Google Cloud Run

Listmonk on Google Cloud Run

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


1. Module Overview

Listmonk is a high-performance, self-hosted newsletter and mailing list manager written in Go. Listmonk CloudRun is a wrapper module built on top of App CloudRun. It uses App CloudRun for all GCP infrastructure provisioning and injects Listmonk-specific application configuration, database initialisation, and storage configuration via Listmonk Common.

Key Capabilities:

  • Compute: Cloud Run v2 (Gen2), Go binary container, 1 vCPU / 512Mi by default. Scale-to-zero by default (min_instance_count = 0) with max_instance_count = 1; cpu_always_allocated = true keeps CPU allocated on a woken instance so the async campaign sender can finish before it scales back down.
  • Data Persistence: Cloud SQL PostgreSQL 15. Optional GCS Fuse volume for media uploads at /listmonk/uploads. Optional NFS for additional shared storage.
  • Security: Inherits Cloud Armor WAF, IAP, Binary Authorization, and VPC Service Controls from App CloudRun. The admin password is auto-generated and stored in Secret Manager.
  • Caching: Redis disabled by default (enable_redis = false). Listmonk is a stateless Go binary and does not require Redis for normal operation.
  • CI/CD: Cloud Build custom image pipeline by default; Cloud Deploy progressive delivery optional.
  • Reliability: Startup probe is a TCP port check (30-second initial delay, 30 retries) — /api/health requires session auth and 403s an HTTP probe, so Cloud Run checks that the container is listening instead. Liveness probe is disabled by default (Cloud Run has no TCP liveness probe, and /api/health also 403s). Listmonk auto-runs PostgreSQL schema migrations on first startup.

Project & Application Identity

VariableGroupTypeDefaultDescription
project_id1stringGCP project ID. Required.
tenant_deployment_id2string'demo'Short suffix appended to all resource names.
support_users2list(string)[]Email recipients for monitoring alerts.
resource_labels2map(string){}Labels applied to all provisioned resources.
application_name3string'listmonk'Base resource name. Do not change after initial deployment.
display_name3string'Listmonk'Human-readable name shown in the GCP Console.
description3string'Listmonk is a high-performance, self-hosted newsletter and mailing list manager'Cloud Run service description.
application_version3string'latest'Listmonk image version tag. Increment to deploy a new release.

Wrapper architecture: Listmonk CloudRun calls Listmonk Common to build an application_config object containing Listmonk-specific environment variables, probe configuration, and the db-init job definition. module_storage_buckets carries any uploads bucket provisioned by Listmonk Common. scripts_dir is resolved to the Listmonk_Common/scripts directory at apply time.

PostgreSQL note: Listmonk requires PostgreSQL 15. database_type = "POSTGRES_15" is the default and Listmonk will not start against any other database engine.


2. IAM & Access Control

Listmonk_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.

Application secrets: Listmonk Common auto-generates one application-level secret:

  • LISTMONK_app__admin_password — the Listmonk admin user password, stored in Secret Manager and injected at runtime.

The database password is stored in a separate secret (database_password_secret) provisioned automatically by App CloudRun. Neither value is written to Terraform state.

Database password env var name: Listmonk reads LISTMONK_db__password directly. The db_password_env_var_name variable is pre-set to this value so that App CloudRun injects the secret under the correct name.

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

Admin interface: Listmonk's full UI, including subscriber management, campaigns, templates, and settings, is available at the root path /. The admin username and auto-generated password are the only credentials required.


3. Core Service Configuration

A. Compute (Cloud Run)

Listmonk is a compiled Go binary. It is memory-efficient and starts quickly compared to interpreted-language applications. The default resource allocation (1 vCPU / 512Mi) is sufficient for moderate list sizes and campaign workloads.

Scale-to-zero by default (min_instance_count = 0) — the old min = 1 default existed only to keep the per-instance in-memory API-user cache warm. That reason is gone: Listmonk_Common's entrypoint UPSERTs a deterministic, secret-backed API user into the database on every start, so a cold-started instance always reloads a valid credential. cpu_always_allocated = true (default) ensures a woken instance keeps CPU allocated long enough for the async campaign sender to complete before scaling back down. Raise min_instance_count to 1 only if Listmonk must stay warm for heavy interactive admin use.

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 Listmonk_Common's Dockerfile. Set container_image_source = 'prebuilt' and provide a container_image URI to skip the build and deploy directly.

VariableGroupDefaultDescription
deploy_application4trueSet false for infrastructure-only deployment (SQL, storage, secrets).
cpu_limit4'1000m'CPU per instance. 1 vCPU is sufficient for Listmonk's Go binary.
memory_limit4'512Mi'Memory per instance. Increase to 1–2 Gi for large subscriber lists or heavy campaign processing.
min_instance_count40Minimum running instances. Scale-to-zero; the self-healing DB-seeded API user makes cold starts safe.
max_instance_count41Maximum running instances. Kept at 1 — Listmonk has no coordination for concurrent campaign processors.
cpu_always_allocated4trueKeeps CPU allocated for the whole lifetime of a running instance so the in-process async campaign sender can complete after the triggering HTTP request returns.
container_port49000Listmonk's native HTTP port. Change only if your custom Dockerfile binds to a different port.
execution_environment4'gen2'Gen2 required for GCS Fuse mounts.
timeout_seconds4300Max request duration. Listmonk's API endpoints typically respond within seconds.
enable_cloudsql_volume4trueInjects the Cloud SQL Auth Proxy sidecar for Unix socket connections to PostgreSQL.
container_protocol4'http1''http1' or 'h2c'.
enable_image_mirroring4trueMirrors the Listmonk image into Artifact Registry.
traffic_split4[]Percentage-based canary/blue-green traffic allocation.
max_revisions_to_retain47Maximum Cloud Run revisions to keep. Set 0 to disable.
service_annotations4{}Advanced Cloud Run annotations.
service_labels4{}Labels applied to the Cloud Run service.

Differences from App CloudRun defaults:

VariableApp CloudRunListmonk CloudRunReason
container_port80809000Listmonk's native port.
cpu_always_allocatedfalsetrueThe in-process campaign sender runs after the triggering HTTP request returns; request-based billing would throttle it mid-batch.
enable_image_mirroringfalsetrueListmonk mirrors its base image to Artifact Registry by default to avoid Docker Hub rate limits.

B. Database (Cloud SQL — PostgreSQL 15)

Listmonk requires PostgreSQL 15. database_type = "POSTGRES_15" is the default and should not be changed. Listmonk handles its own schema creation and migrations on first startup — the db-init Cloud Run Job creates only the database and user; the application populates the schema.

VariableGroupDefaultDescription
database_type12'POSTGRES_15'Cloud SQL engine. Listmonk requires PostgreSQL. Do not change.
db_name12'listmonk'PostgreSQL database name. Do not change after initial deployment.
db_user12'listmonk'PostgreSQL application user. Password auto-generated and stored in Secret Manager.
db_password_env_var_name12'LISTMONK_db__password'Env var name under which the DB password is injected. Pre-set for Listmonk; do not change.
database_password_length1232Auto-generated password length. Range: 16–64.
enable_auto_password_rotation12falseAutomated zero-downtime password rotation.
rotation_propagation_delay_sec1290Seconds to wait after rotation before restarting the service.

sql_instance_name and sql_instance_base_name are not exposed; Cloud SQL discovery/inline provisioning is handled transparently by App CloudRun.

C. Storage (GCS Fuse & NFS)

NFS is disabled by default (enable_nfs = false). Listmonk is a stateless Go binary and does not require shared filesystem storage. Multiple Cloud Run instances share a single PostgreSQL database; subscriber data and campaign content are database-backed.

GCS Fuse uploads volume: For persistent media/file upload storage, mount a GCS bucket at /listmonk/uploads using gcs_volumes. Listmonk Common sets LISTMONK_upload__provider=filesystem and LISTMONK_upload__filesystem__upload_path=/listmonk/uploads — the GCS bucket mounted at that path satisfies these settings. Without a persistent volume, uploaded attachments are ephemeral and lost on revision restart.

VariableGroupDefaultDescription
gcs_volumes11[]GCS buckets to mount via GCS Fuse (requires gen2). Each entry: name, bucket_name, mount_path, readonly, mount_options. Mount the uploads bucket at /listmonk/uploads.
enable_nfs11falseProvisions a shared NFS volume. Not required for standard Listmonk deployments.
nfs_mount_path11'/mnt/nfs'Container path where the NFS share is mounted. Only relevant when enable_nfs = true.
create_cloud_storage11trueSet false to skip GCS bucket creation defined in storage_buckets.
storage_buckets11[]Additional GCS buckets to provision.
manage_storage_kms_iam11falseCreates a CMEK KMS keyring/key and enables CMEK on all storage buckets.
enable_artifact_registry_cmek11falseCreates an Artifact Registry KMS key for at-rest CMEK image encryption.

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, and the database connection does not require VPC routing.

Listmonk connects to external SMTP/Mailgun/SES/Postmark providers for campaign delivery. Ensure vpc_egress_setting = 'PRIVATE_RANGES_ONLY' (default) so that direct public egress is available for SMTP port 587/465/25.

VariableGroupDefaultDescription
ingress_settings5'all''all' — public internet; 'internal' — VPC only; 'internal-and-cloud-load-balancing' — forces traffic through the HTTPS Load Balancer.
vpc_egress_setting5'PRIVATE_RANGES_ONLY''PRIVATE_RANGES_ONLY' routes only RFC 1918 traffic via VPC. Set 'ALL_TRAFFIC' only if all outbound traffic (including SMTP) must transit the VPC.

E. Initialization & Bootstrap

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

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

Listmonk itself runs --install on first boot to populate the schema and create the admin user using LISTMONK_app__admin_username and LISTMONK_app__admin_password from the environment.

VariableGroupDefaultDescription
initialization_jobs13[]One-shot Cloud Run Jobs. Leave empty for Listmonk Common to supply the default db-init job. Non-empty list replaces it entirely.
cron_jobs13[]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.

4. Advanced Security

A. Cloud Armor WAF

When enable_cloud_armor = true, a Global HTTPS Load Balancer with a Cloud Armor WAF policy (OWASP Top 10, adaptive DDoS, rate limiting) is provisioned in front of Cloud Run.

For production Listmonk deployments processing subscriber data and sending campaigns, Cloud Armor is strongly recommended to protect against credential stuffing on the admin login and API abuse.

VariableGroupDefaultDescription
enable_cloud_armor10falseProvisions Global HTTPS LB + Cloud Armor WAF. Required for custom domains, CDN, and DDoS protection.
admin_ip_ranges10[]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 Listmonk. Recommended for internal newsletter deployments where access should be restricted to organisational Google accounts.

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

C. Binary Authorization

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

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

D. VPC Service Controls

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.
vpc_sc_dry_run22trueLogs VPC-SC violations without blocking. Set false to enforce.
enable_audit_logging22falseEnables detailed Cloud Audit Logs (DATA_READ, DATA_WRITE, ADMIN_READ).

E. Secret Manager Integration

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

Listmonk Common auto-generates LISTMONK_app__admin_password. The database password (LISTMONK_db__password) is provisioned automatically by App CloudRun. Additional secrets can be added via secret_environment_variables.

VariableGroupDefaultDescription
secret_environment_variables6{}Map of env var name → Secret Manager secret ID. Resolved at runtime; never stored in state.
secret_rotation_period6'2592000s'Frequency at which Secret Manager emits rotation notifications. Default: 30 days.
secret_propagation_delay630Seconds to wait after secret creation before dependent resources proceed.

5. Traffic & Ingress

A. HTTPS Load Balancer

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 Listmonk traffic through the LB, preventing direct *.run.app URL access.

B. Cloud CDN

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

Listmonk consideration: Listmonk serves a mix of API endpoints and static assets. CDN is most beneficial for the static UI assets (HTML, JS, CSS). Listmonk API responses (/api/*) are not cacheable and should be excluded from CDN caching via Cache-Control headers or URL pattern rules.

VariableGroupDefaultDescription
enable_cdn10falseEnables Cloud CDN on the HTTPS LB backend. Only effective when enable_cloud_armor = true.
max_images_to_retain107Maximum number of recent container images to keep in Artifact Registry. Set 0 to disable.
delete_untagged_images10trueAutomatically deletes untagged (dangling) images from Artifact Registry.
image_retention_days1030Days 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_domains10[]Custom domain names for the HTTPS LB. Google-managed SSL certificates provisioned per domain. (e.g., ['newsletters.example.com'])

6. CI/CD & Delivery

A. Cloud Build Triggers

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

VariableGroupDefaultDescription
enable_cicd_trigger8falseProvisions a Cloud Build GitHub trigger. Requires github_repository_url and credentials.
github_repository_url8""Full HTTPS URL of the GitHub repository.
github_token8""GitHub PAT (repo, admin:repo_hook scopes). Required on first apply. Sensitive.
github_app_installation_id8""GitHub App installation ID (preferred for organisation repos).
cicd_trigger_config8{ 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_deploy8falseProvisions a Cloud Deploy pipeline. Requires enable_cicd_trigger = true.
cloud_deploy_stages8[dev, staging, prod(approval)]Ordered promotion stages. Each: name, target_name, service_name, require_approval, auto_promote.

7. Reliability & Scheduling

A. Scaling & Concurrency

Listmonk is a stateless Go binary — multiple Cloud Run instances can serve requests simultaneously without shared session state issues. The default min_instance_count = 0 scales to zero between sends; the self-healing DB-seeded API user means a cold-started instance is immediately usable. max_instance_count defaults to 1 because Listmonk has no coordination for concurrent campaign processors — raise it only if interactive/API read load requires it.

B. Traffic Splitting

Traffic splitting is supported. Listmonk's stateless design makes canary deployments safe — all requests share the same PostgreSQL backend regardless of which instance handles them.

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

C. Health Probes & Uptime Monitoring

Listmonk exposes a dedicated /api/health endpoint, but as of Listmonk v6.1.0 that endpoint sits behind session auth and returns 403 {"message":"invalid session"} to an unauthenticated caller — so an HTTP probe against it never passes. The startup probe therefore defaults to a TCP port-listening check instead (confirms the server is bound to the container port), and the liveness probe defaults to disabled (Cloud Run has no TCP liveness probe, and the HTTP form would 403 the same way). The TCP startup probe gates readiness, and Cloud Run restarts the container if it exits.

VariableGroupDefaultDescription
startup_probe14{ enabled=true, type="TCP", path="/api/health", initial_delay_seconds=30, timeout_seconds=5, period_seconds=10, failure_threshold=30 }Startup readiness probe (TCP). Container receives no traffic until this succeeds.
liveness_probe14{ enabled=false, type="HTTP", path="/api/health", initial_delay_seconds=30, timeout_seconds=5, period_seconds=30, failure_threshold=3 }Liveness probe. Disabled by default; the TCP startup probe covers readiness.
uptime_check_config14{ enabled=false, path="/api/health" }Cloud Monitoring uptime check. Disabled by default — enable and alerts notify support_users if unreachable.
alert_policies14[]Cloud Monitoring metric alert policies.

Startup probe behaviour: The failure_threshold=30 combined with period_seconds=10 gives Listmonk up to 300 seconds (plus the 30-second initial delay) to complete schema installation on first boot. This is generous — Listmonk schema installation typically completes in under 30 seconds on a fresh PostgreSQL instance.

D. Auto Password Rotation

When enable_auto_password_rotation = true, a zero-downtime password rotation pipeline is provisioned:

  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 PostgreSQL user, writes a new secret version.
  4. After rotation_propagation_delay_sec seconds, the job restarts the Listmonk service.

Listmonk re-establishes its database connection on restart and reads the updated password from Secret Manager.

VariableGroupDefaultDescription
enable_auto_password_rotation12falseEnables automated password rotation.
rotation_propagation_delay_sec1290Seconds to wait after writing the new secret before restarting the service.

8. Integrations

A. Mail Sending (SMTP / API)

Listmonk connects to external mail providers for sending campaigns and transactional messages. Configure the mail provider via the Listmonk admin UI at Settings → SMTP after deployment. Common providers include Mailgun, Amazon SES, Postmark, and SendGrid.

Listmonk supports both SMTP and native API integrations (Mailgun, SES, Postmark, Sparkpost). For high-volume sending, API integrations are preferred over SMTP.

Configuration approach: Mail provider credentials are not set via module variables. After deployment, navigate to the Listmonk admin interface and configure:

  1. Settings → SMTP for SMTP-based sending.
  2. Settings → Sending settings for rate limiting (messages per minute), concurrency, and retry settings.

To persist these settings across redeployments, export the Listmonk settings as a SQL dump and import on restore.

B. Backup Import & Recovery

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

VariableGroupDefaultDescription
backup_schedule7'0 2 * * *'Cron expression (UTC) for automated daily backups.
backup_retention_days77Days to retain backup files in GCS.
enable_backup_import7falseTriggers a one-time restore on apply. Set false after a successful import.
backup_source7'gcs''gcs' (full GCS URI) or 'gdrive' (Drive file ID).
backup_uri7""Full GCS URI (e.g., 'gs://my-bucket/listmonk-backup.sql') or Google Drive file ID.
backup_format7'sql'Backup file format. Options: sql, tar, gz, tgz, tar.gz, zip.

C. Observability & Alerting

A Cloud Monitoring uptime check can poll the /api/health endpoint from multiple global locations; it is disabled by default. Custom alert policies can monitor Cloud Run metrics and notify support_users.

VariableGroupDefaultDescription
uptime_check_config14{ enabled=false, path="/api/health" }Uptime check: enabled, path, check_interval, timeout. Disabled by default.
alert_policies14[]Metric alert policies. Each: name, metric_type, comparison, threshold_value, duration_seconds, aggregation_period.
support_users2[]Email addresses notified by uptime and alert policy triggers.

9. Exploring with the GCP Console

After a successful deployment, use the GCP Console to verify and explore the Listmonk deployment.

Cloud Run Service

Navigate to Cloud Run in the console and select the listmonk service (named app<listmonk><tenant><id>). Key areas to review:

  • Revisions tab: Shows all deployed container revisions, traffic allocation percentages, and container image digests. The active revision should show the latest tag or a specific version.
  • Logs tab: Streams Cloud Run container logs in real time. Filter for severity=ERROR to surface Listmonk startup or database connection issues. On first boot, look for Listmonk's schema installation log lines (Running install..., Admin user created).
  • Metrics tab: Displays request count, request latency (P50/P95/P99), instance count, and container startup latency. Spike in instance count during a large campaign send is expected.
  • YAML tab: Shows the full Cloud Run service specification including environment variable names (not values), volume mounts, health probe configuration, and resource limits.

Secret Manager

Navigate to Secret Manager and filter by the deployment name. Two secrets are relevant:

  • app<listmonk><tenant><id>-admin-password or similar — the LISTMONK_app__admin_password secret. Click View secret versions to confirm a version exists. Do not click View secret value in production.
  • app<listmonk><tenant><id>-db-password — the PostgreSQL password secret. Used by both the Cloud Run service and the db-init job.

Select either secret and review the Replication tab to confirm the secret is replicated to the deployment region.

Cloud SQL

Navigate to SQL and open the Listmonk Cloud SQL instance. Key areas:

  • Overview: Confirms PostgreSQL 15 engine, the instance tier, and storage size.
  • Databases tab: The listmonk database should be listed. After first deployment, Listmonk's schema tables will be present.
  • Users tab: The listmonk application user should be listed.
  • Connections tab: Review Authorized networks and confirm that only the Cloud SQL Auth Proxy (via the Cloud Run SA) has access. No public IP should be required.
  • Backups tab: Confirms automated backup schedule and lists recent backup files.
  • Operations tab: Shows recent operations including the db-init job's connection history.

Cloud Build

Navigate to Cloud Build → History to review recent build runs. The initial deployment triggers a build to package the Listmonk container image. Each build entry shows the image tag, build duration, and any step-level logs. Click a build to see the Dockerfile steps and the final docker push to Artifact Registry.

Artifact Registry

Navigate to Artifact Registry and open the listmonk repository. The repository lists all pushed image tags with their digests, push timestamps, and sizes. Confirm the latest image is present.

Cloud Monitoring

Navigate to Monitoring → Uptime checks and find the Listmonk uptime check. The check polls /api/health from multiple global locations. A green status confirms the service is reachable and healthy from GCP's global network.

Navigate to Monitoring → Alerting to see any active alert policies created by the module. support_users are automatically added as notification channel recipients.


10. Exploring with gcloud

The following gcloud commands provide operational visibility into the Listmonk Cloud Run deployment. Replace PROJECT_ID, REGION, and SERVICE_NAME with your deployment values. The service name follows the pattern app<listmonk><tenant><id> — retrieve it from the service_name Terraform output.

Retrieve the service URL and configuration:

# Get the Cloud Run service URL
gcloud run services describe SERVICE_NAME \
--region=REGION \
--project=PROJECT_ID \
--format="value(status.url)"

# Describe the full service configuration
gcloud run services describe SERVICE_NAME \
--region=REGION \
--project=PROJECT_ID \
--format=yaml

Check service health and revisions:

# List all revisions and their traffic allocations
gcloud run revisions list \
--service=SERVICE_NAME \
--region=REGION \
--project=PROJECT_ID \
--format="table(metadata.name,status.conditions[0].status,spec.containers[0].image,status.observedGeneration)"

# Check revision traffic split
gcloud run services describe SERVICE_NAME \
--region=REGION \
--project=PROJECT_ID \
--format="value(spec.traffic[].percent,spec.traffic[].revisionName)"

Stream and filter logs:

# Tail live logs from the Listmonk service
gcloud logging read \
"resource.type=cloud_run_revision AND resource.labels.service_name=SERVICE_NAME" \
--project=PROJECT_ID \
--limit=50 \
--freshness=10m \
--format="table(timestamp,severity,textPayload)"

# Filter for errors only
gcloud logging read \
"resource.type=cloud_run_revision AND resource.labels.service_name=SERVICE_NAME AND severity>=ERROR" \
--project=PROJECT_ID \
--limit=20

# View db-init job logs
gcloud logging read \
"resource.type=cloud_run_job AND resource.labels.job_name~db-init" \
--project=PROJECT_ID \
--limit=30 \
--format="table(timestamp,severity,textPayload)"

Inspect secrets:

# List all Secret Manager secrets in the project (filter by deployment)
gcloud secrets list \
--project=PROJECT_ID \
--filter="name~listmonk" \
--format="table(name,replication.automatic,createTime)"

# Check secret versions for the admin password
gcloud secrets versions list LISTMONK_app__admin_password_SECRET_NAME \
--project=PROJECT_ID \
--format="table(name,state,createTime)"

# Access the admin password (use with caution — only in break-glass scenarios)
gcloud secrets versions access latest \
--secret=LISTMONK_ADMIN_PASSWORD_SECRET_NAME \
--project=PROJECT_ID

Cloud SQL inspection:

# List Cloud SQL instances
gcloud sql instances list \
--project=PROJECT_ID \
--format="table(name,databaseVersion,settings.tier,region,state)"

# Describe the Listmonk instance
gcloud sql instances describe SQL_INSTANCE_NAME \
--project=PROJECT_ID \
--format="yaml(databaseVersion,settings.tier,settings.ipConfiguration,state)"

# List databases on the instance
gcloud sql databases list \
--instance=SQL_INSTANCE_NAME \
--project=PROJECT_ID

# List users on the instance
gcloud sql users list \
--instance=SQL_INSTANCE_NAME \
--project=PROJECT_ID

Cloud Run Jobs (db-init):

# List all Cloud Run Jobs in the region
gcloud run jobs list \
--region=REGION \
--project=PROJECT_ID \
--format="table(metadata.name,status.conditions[0].status,metadata.creationTimestamp)"

# Describe the db-init job
gcloud run jobs describe JOB_NAME \
--region=REGION \
--project=PROJECT_ID

# Manually execute the db-init job (e.g., after a database recreation)
gcloud run jobs execute JOB_NAME \
--region=REGION \
--project=PROJECT_ID \
--wait

Uptime and monitoring:

# List uptime checks
gcloud monitoring uptime list-configs \
--project=PROJECT_ID \
--format="table(displayName,httpCheck.path,period,checkerType)"

# List Cloud Monitoring alert policies
gcloud alpha monitoring policies list \
--project=PROJECT_ID \
--format="table(displayName,enabled,conditions[0].displayName)"

Artifact Registry:

# List images in the Listmonk repository
gcloud artifacts docker images list REGION-docker.pkg.dev/PROJECT_ID/listmonk \
--project=PROJECT_ID \
--format="table(image,tags,createTime)" \
--include-tags

# List all tags for the Listmonk image
gcloud artifacts docker tags list REGION-docker.pkg.dev/PROJECT_ID/listmonk/listmonk \
--project=PROJECT_ID

11. Platform-Managed Behaviours

The following behaviours are applied automatically by Listmonk CloudRun regardless of variable values.

BehaviourDetail
PostgreSQL 15 requireddatabase_type = "POSTGRES_15" default. Listmonk does not support MySQL or other engines.
Admin password auto-generatedLISTMONK_app__admin_password is generated by Listmonk Common and stored in Secret Manager. Never stored in state.
DB password env var pre-setdb_password_env_var_name = "LISTMONK_db__password" is pre-configured so App CloudRun injects the database password under the correct name for Listmonk's config.
GCS Fuse upload pathLISTMONK_upload__provider=filesystem and LISTMONK_upload__filesystem__upload_path=/listmonk/uploads are injected automatically. Mount a GCS bucket at /listmonk/uploads via gcs_volumes for persistent uploads.
Default db-init jobSupplied by Listmonk Common when initialization_jobs = []. PostgreSQL database and user are created automatically.
Image mirroring enabledenable_image_mirroring = true by default. The Listmonk base image is mirrored into Artifact Registry before deployment.
Redis disabled by defaultenable_redis = false. Listmonk does not require Redis for standard operation.
Scripts directoryscripts_dir is resolved to Listmonk_Common/scripts at apply time.

12. Variable Reference

All user-configurable variables exposed by Listmonk CloudRun, sorted by UI group then order.

VariableGroupDefaultDescription
project_id1GCP project ID. Required.
region1'us-central1'GCP region for all resources.
tenant_deployment_id2'demo'Short suffix appended to all resource names.
support_users2[]Email addresses for monitoring alerts.
resource_labels2{}Labels applied to all provisioned resources.
application_name3'listmonk'Base resource name. Do not change after initial deployment.
display_name3'Listmonk'Human-readable name.
description3(Listmonk description)Service description.
application_version3'latest'Listmonk container image tag.
admin_username3'listmonk'Initial admin username created on first boot. Can be changed via the UI after deployment.
deploy_application4trueSet false for infrastructure-only deployment.
cpu_limit4'1000m'CPU per instance.
memory_limit4'512Mi'Memory per instance.
min_instance_count40Minimum running instances. Scale-to-zero by default.
max_instance_count41Maximum running instances.
cpu_always_allocated4trueKeeps CPU allocated on a running instance so the async campaign sender can finish after the triggering request returns.
execution_environment4'gen2'Gen2 required for GCS Fuse mounts.
timeout_seconds4300Max request duration in seconds.
enable_cloudsql_volume4trueAuth Proxy sidecar for Unix socket connections to PostgreSQL.
cloudsql_volume_mount_path4'/cloudsql'Container path for the Auth Proxy Unix socket.
container_port49000Listmonk's native HTTP port.
container_protocol4'http1''http1' or 'h2c'.
enable_image_mirroring4trueMirrors the Listmonk image into Artifact Registry.
traffic_split4[]Canary/blue-green traffic allocation.
max_revisions_to_retain47Maximum Cloud Run revisions to keep.
service_annotations4{}Advanced Cloud Run annotations.
service_labels4{}Labels applied to the Cloud Run service.
ingress_settings5'all''all', 'internal', or 'internal-and-cloud-load-balancing'.
vpc_egress_setting5'PRIVATE_RANGES_ONLY''PRIVATE_RANGES_ONLY' or 'ALL_TRAFFIC'.
enable_iap5falseEnables IAP natively on the Cloud Run service.
iap_authorized_users5[]Users/SAs granted IAP access.
iap_authorized_groups5[]Google Groups granted IAP access.
environment_variables6{}Plain-text env vars.
secret_environment_variables6{}Secret Manager references.
secret_propagation_delay630Seconds to wait after secret creation.
secret_rotation_period6'2592000s'Secret Manager rotation notification frequency.
backup_schedule7'0 2 * * *'Cron expression (UTC) for automated backups.
backup_retention_days77Days to retain backup files in GCS.
enable_backup_import7falseTriggers a one-time restore on apply.
backup_source7'gcs''gcs' (full URI) or 'gdrive' (file ID).
backup_uri7""Full GCS URI or Google Drive file ID.
backup_format7'sql'Backup format. Options: sql, tar, gz, tgz, tar.gz, zip.
enable_cicd_trigger8falseProvisions a Cloud Build GitHub trigger.
github_repository_url8""Full HTTPS URL of the GitHub repository.
github_token8""GitHub PAT. Required on first apply. Sensitive.
github_app_installation_id8""GitHub App installation ID.
cicd_trigger_config8{ branch_pattern = "^main$" }Advanced Cloud Build trigger config.
enable_cloud_deploy8falseProvisions a Cloud Deploy progressive delivery pipeline.
cloud_deploy_stages8[dev, staging, prod(approval)]Ordered Cloud Deploy promotion stages.
enable_binary_authorization8falseEnforces image attestation on deployment.
enable_custom_sql_scripts9falseRuns SQL scripts from GCS after provisioning.
custom_sql_scripts_bucket9""GCS bucket containing SQL scripts.
custom_sql_scripts_path9""Path prefix within the bucket.
custom_sql_scripts_use_root9falseRun scripts as the root DB user.
nfs_instance_name9""Name of an existing NFS GCE VM. Leave empty to auto-discover.
nfs_instance_base_name9'app-nfs'Base name for inline NFS VM. Deployment ID is appended.
enable_cloud_armor10falseProvisions Global HTTPS LB + Cloud Armor WAF.
admin_ip_ranges10[]CIDR ranges exempted from WAF rules.
application_domains10[]Custom domains with Google-managed SSL certificates.
enable_cdn10falseEnables Cloud CDN on the HTTPS LB backend.
max_images_to_retain107Maximum container images to keep in Artifact Registry.
delete_untagged_images10trueAutomatically deletes untagged images from Artifact Registry.
image_retention_days1030Days after which images are eligible for deletion.
create_cloud_storage11trueSet false to skip GCS bucket creation.
storage_buckets11[]Additional GCS buckets to provision.
enable_nfs11falseProvisions NFS shared storage. Not required for standard Listmonk deployments.
nfs_mount_path11'/mnt/nfs'Container path where NFS is mounted.
gcs_volumes11[]GCS buckets to mount via GCS Fuse. Mount uploads bucket at /listmonk/uploads.
manage_storage_kms_iam11falseCreates CMEK KMS key and enables CMEK on all storage buckets.
enable_artifact_registry_cmek11falseCreates Artifact Registry KMS key for at-rest image encryption.
database_type12'POSTGRES_15'Cloud SQL engine. Do not change — Listmonk requires PostgreSQL.
db_name12'listmonk'PostgreSQL database name. Do not change after initial deployment.
db_user12'listmonk'PostgreSQL application user.
db_password_env_var_name12'LISTMONK_db__password'Env var name for DB password injection. Do not change.
database_password_length1232Auto-generated password length. Range: 16–64.
enable_auto_password_rotation12falseAutomated zero-downtime password rotation.
rotation_propagation_delay_sec1290Seconds to wait after rotation before restarting the service.
initialization_jobs13[]One-shot Cloud Run Jobs. Leave empty for default db-init job.
cron_jobs13[]Recurring scheduled Cloud Run Jobs.
startup_probe14{ enabled=true, type="TCP", path="/api/health", initial_delay_seconds=30, failure_threshold=30, ... }Startup probe. TCP — /api/health requires session auth.
liveness_probe14{ enabled=false, type="HTTP", path="/api/health", initial_delay_seconds=30, failure_threshold=3, ... }Liveness probe. Disabled by default.
uptime_check_config14{ enabled=false, path="/api/health" }Cloud Monitoring uptime check. Disabled by default.
alert_policies14[]Cloud Monitoring metric alert policies.
enable_redis21falseRedis integration. Not required for Listmonk.
redis_host21""Redis hostname/IP.
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.
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.

13. 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 PostgreSQL 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.
container_imageContainer image used for the deployment.
cicd_enabledWhether the CI/CD pipeline is enabled.
github_repository_urlGitHub repository URL connected for CI/CD.

14. 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"POSTGRES_15"CriticalListmonk exclusively supports PostgreSQL. Changing to MySQL or SQL Server causes Listmonk to fail at startup.
db_name"listmonk"CriticalImmutable after first deployment — changing this causes Terraform to recreate the database, destroying all subscribers, campaigns, and settings.
db_user"listmonk"CriticalImmutable after first deployment — changing this recreates the Cloud SQL user and invalidates all stored credentials.
db_password_env_var_name"LISTMONK_db__password"CriticalListmonk reads this exact env var name for its database password. Changing it causes Listmonk to fail to connect to PostgreSQL at startup.
min_instance_count0LowScale-to-zero is the default and is safe: the self-healing DB-seeded API user means a cold-started instance always reloads a valid credential, and cpu_always_allocated = true keeps the woken instance's async campaign sender running to completion. Raise to 1 only for heavy interactive admin use where cold starts are undesirable.
memory_limit"512Mi"MediumSufficient for small lists. Lists with millions of subscribers or concurrent campaign processing can exhaust 512Mi under load. Increase to 1–2 Gi for production deployments with large subscriber counts.
enable_cloudsql_volumetrueCriticalListmonk connects to PostgreSQL via the Auth Proxy Unix socket by default. Disabling the volume without providing a TCP connection path causes all database operations to fail.
gcs_volumes[]MediumWithout a GCS Fuse volume at /listmonk/uploads, media file uploads are stored ephemerally on the container filesystem and lost when the revision is replaced. Configure for any deployment that accepts file attachments or media uploads.
ingress_settings"all"Medium"all" exposes Listmonk's admin interface to the public internet. For internal newsletter deployments, consider enable_iap = true or ingress_settings = "internal".
enable_cloud_armorfalseMediumWithout Cloud Armor, the Listmonk API and admin interface are exposed without WAF protection. Large-scale subscriber lists are valuable targets for scraping. Recommended for any production deployment.
backup_retention_days7MediumSeven days is insufficient for production subscriber databases. Losing subscriber data, campaign history, and unsubscribe records is a serious compliance risk. Increase to 30+ days for any production deployment.
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 apply.
vpc_egress_setting"PRIVATE_RANGES_ONLY"MediumListmonk must reach external SMTP/API providers for campaign sending. PRIVATE_RANGES_ONLY permits direct public egress. Changing to "ALL_TRAFFIC" with a restrictive VPC firewall will block outbound SMTP/API connections and silently prevent campaign delivery.
secret_propagation_delay30LowOccasionally insufficient in multi-region setups. Increase to 60–90s if secrets are not found during apply.

15. 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.

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.