Certification track: Associate Cloud Engineer (ACE)
Listmonk GKE Module — Configuration Guide
This guide describes every configuration variable available in the Listmonk_GKE module. Listmonk_GKE is a wrapper module that combines the generic App_GKE infrastructure module with the Listmonk_Common shared application configuration to deploy Listmonk, the self-hosted newsletter and mailing list manager, on Google Kubernetes Engine (GKE) Autopilot.
Most configuration options in Listmonk GKE map directly to the same options in App GKE. Where a variable is identical in behaviour, this guide references the App GKE guide rather than repeating the same documentation. Only the variables and defaults that are specific to Listmonk are described in full here.
Note: Variables marked as platform-managed are set and maintained by the platform. You do not normally need to change them.
Standard Configuration Reference
The following configuration areas are provided by the underlying App_GKE module. Consult the linked sections of the App_GKE Configuration Guide for full documentation.
| Configuration Area | App GKE.md Section | Listmonk-Specific Notes |
|---|---|---|
| Project & Identity | §2 IAM & Access Control | Identical. |
| Application Identity | §3.A Compute (GKE Autopilot) | Listmonk-specific defaults; see Group 2: Application Identity. |
| Runtime & Scaling | §3.A Compute (GKE Autopilot) | Listmonk-specific defaults for container_port, cpu_limit, memory_limit, and min_instance_count; see Group 3: Runtime & Scaling. |
| Environment Variables & Secrets | §3 Core Service Configuration | LISTMONK_ADMIN_PASSWORD secret auto-generated by Listmonk Common; see Group 5: Environment Variables & Secrets. |
| Networking & Network Policies | §3.D Networking & Network Policies | Identical. |
| Initialization Jobs & CronJobs | §3.E Initialization Jobs & CronJobs | db-init PostgreSQL job supplied automatically by Listmonk Common; see Group 8: Jobs & Scheduled Tasks. |
| Additional Services | §3.F Additional Services | Identical. |
| Storage — NFS | §3.C Storage (NFS / GCS / GCS Fuse) | enable_nfs defaults to false; Listmonk uses GCS Fuse for uploads; see Group 9: Storage & Filesystem — NFS. |
| Storage — GCS | §3.C Storage (NFS / GCS / GCS Fuse) | GCS bucket provisioned automatically for media uploads; see Group 10: Storage & Filesystem — GCS. |
| Database Configuration | §3.B Database (Cloud SQL) | PostgreSQL 15 required; see Group 11: Database Configuration. |
| Backup Schedule & Retention | §3.B Database (Cloud SQL) | Identical. |
| Custom SQL Scripts | §3.E Initialization Jobs & CronJobs | Identical. |
| Observability & Health Checks | §3.A Compute (GKE Autopilot) | Health endpoint is /api/health; see Group 13: Observability & Health. |
| Cloud Armor WAF | §4.A Cloud Armor WAF | Identical. |
| Identity-Aware Proxy | §4.B Identity-Aware Proxy (IAP) | Identical. |
| Binary Authorization | §4.C Binary Authorization | Identical. |
| VPC Service Controls | §4.D VPC Service Controls | Identical. |
| Secrets Store CSI Driver | §4.E Secrets Store CSI Driver | Always enabled — no configuration required. |
| Traffic & Ingress | §5 Traffic & Ingress | Identical. |
| CDN | §5.B CDN | Identical. |
| Custom Domain & Static IP | §5.C Static IP Reservation | Listmonk public URL must match the domain configured in application_domains; see Group 16: Custom Domain & Static IP. |
| Cloud Build Triggers | §6.A Cloud Build Triggers | Identical. |
| Cloud Deploy Pipeline | §6.B Cloud Deploy Pipeline | Identical. |
| Image Mirroring | §6.C Image Mirroring | Identical. |
| Pod Disruption Budgets | §7.A Pod Disruption Budgets | Identical. |
| Topology Spread Constraints | §7.B Topology Spread Constraints | Identical. |
| Resource Quotas | §7.C Resource Quotas | Identical. |
| Auto Password Rotation | §7.D Auto Password Rotation | See Group 11: Database Configuration. |
| Redis Cache | §8.A Redis / Memorystore | enable_redis defaults to false; Listmonk does not require Redis; see Group 15: Redis Cache. |
| Backup Import | §8.B Backup Import | Supports both GCS URI and module-managed backup bucket; see Group 6: Backup & Maintenance. |
| Service Mesh (ASM) | §8.C Service Mesh (ASM via Fleet) | Identical. |
| Multi-Cluster Services | §8.D Multi-Cluster Services (MCS) | Identical. |
How Listmonk GKE Relates to App GKE
Listmonk GKE passes all variables through to App GKE and adds a Listmonk Common sub-module that supplies Listmonk-specific defaults and application configuration. The main effects are:
- PostgreSQL 15 is required. Listmonk uses PostgreSQL as its only supported database backend. The
database_typedefault is set to"POSTGRES_15"and must not be changed to MySQL or any other engine. LISTMONK_ADMIN_PASSWORDis auto-generated.Listmonk Commoncreates a randomly generated admin password and stores it in Secret Manager, injected into the container asLISTMONK_ADMIN_PASSWORD— you do not need to configure it manually. The corresponding admin username is hardcoded to"admin"viaLISTMONK_ADMIN_USER.- Listmonk runs schema migrations automatically on first startup. No manual schema initialisation is required beyond the
db-initjob that creates the database and user. Listmonk's own binary applies all pending migrations when it boots. - A
db-initjob runs on first deployment.Listmonk Commonsupplies a defaultdb-initKubernetes Job using apostgres:15-alpineimage that creates the database and user in Cloud SQL. Overrideinitialization_jobsto replace it with a custom job. - GCS Fuse is used for media uploads instead of NFS.
enable_nfsdefaults tofalse. Listmonk stores uploaded subscriber files, attachments, and media via GCS Fuse, which provides durable object storage without requiring a shared NFS volume. - Resource defaults are sized for Listmonk. The default
cpu_limit(1000m) andmemory_limit(512Mi) are appropriate for small to medium mailing list operations. Scale upcontainer_resourcesfor campaigns with large subscriber counts or high concurrent API usage. - Session affinity defaults to
"ClientIP". Listmonk's campaign management interface maintains session state in-process. Without session affinity, the web UI can lose state between requests when multiple pod replicas are running. The default"ClientIP"session affinity keeps each browser session routed to the same pod. - Redis is not required. Listmonk does not use a cache layer —
enable_redisdefaults tofalse. You do not need a Memorystore instance to run Listmonk.
Group 1: Project & Identity
Identical to App_GKE. See App_GKE.
Listmonk GKE variables in this group:
| Variable | Default | Description |
|---|---|---|
project_id | (required) | GCP project ID. No default — deployment fails without this value. |
tenant_deployment_id | "demo" | Suffix appended to resource names to distinguish multiple deployments in the same project. |
support_users | [] | Users granted read access to deployment outputs (e.g. URLs, secret names). |
resource_labels | {} | Labels applied to all GCP resources created by this module. |
region | "us-central1" | GCP region for all resources. Used as the default storage bucket location and GKE node region. |
Group 2: Application Identity
These variables behave identically to App_GKE. See App_GKE for descriptions.
Listmonk-specific defaults:
| Variable | Listmonk GKE Default | App GKE Default | Notes |
|---|---|---|---|
application_name | "listmonk" | "gkeapp" | Used as the base name for all GCP and Kubernetes resources. Do not change after deployment. |
application_display_name | "Listmonk" | "App GKE Application" | Shown in the platform UI and dashboards. Can be changed freely. |
application_description | "Listmonk Newsletter Manager on GKE Autopilot" | "App GKE Custom Application…" | Descriptive label. Can be changed freely. |
application_version | "latest" | "1.0.0" | The Listmonk image tag to deploy. Using "latest" is acceptable for development; pin to a specific version (e.g. "v4.1.0") for production to ensure reproducible deployments. |
admin_username | "listmonk" | (not in App GKE) | Declared and forwarded to Listmonk_Common, but currently unused there — the seeded super-admin username is hardcoded to "admin" via LISTMONK_ADMIN_USER, regardless of this variable's value. |
deploy_application | true | true | Set to false to provision infrastructure without deploying the Listmonk workload — useful for staged rollouts or infrastructure-only refreshes. |
Group 3: Runtime & Scaling
Most variables behave identically to App_GKE. See App_GKE Group 3.
Listmonk-specific defaults and behaviour:
| Variable | Listmonk GKE Default | App GKE Default | Notes |
|---|---|---|---|
container_port | 9000 | 8080 | Listmonk's native HTTP port. Do not change unless your custom Dockerfile binds Listmonk to a different port. |
min_instance_count | 1 | 1 | At least one pod is always running — no scale-to-zero on GKE. Listmonk's campaign scheduler and subscriber import tasks require a continuously running process. |
max_instance_count | 3 | 3 | Sufficient for most mailing list workloads. Increase for very high concurrent API usage or bulk subscriber import scenarios. |
container_image | "listmonk/listmonk:latest" | (none) | Official Listmonk Docker Hub image. Override with a private Artifact Registry image when container_image_source = "custom". |
container_image_source | "custom" | "custom" | Defaults to custom build to allow environment-specific configuration via LISTMONK_* environment variables baked into the image. Set to "prebuilt" to deploy the official Docker Hub image directly. |
enable_cloudsql_volume | true | true | The Cloud SQL Auth Proxy sidecar mounts a Unix socket that Listmonk uses to connect to Cloud SQL without exposing database credentials over the network. Only disable if connecting to Cloud SQL via a direct private TCP connection. |
cloudsql_volume_mount_path | "/cloudsql" | "/cloudsql" | Path inside the container where the Cloud SQL Auth Proxy socket is mounted. |
container_resources: The variable default is { cpu_limit = "1000m", memory_limit = "512Mi" }. This is appropriate for development and small subscriber lists. For production deployments sending campaigns to tens of thousands of subscribers, increase to at least { cpu_limit = "2000m", memory_limit = "1Gi" } to prevent OOM kills during bulk campaign send operations.
The remaining runtime variables (enable_image_mirroring, container_build_config, service_annotations, service_labels) behave as described in App_GKE Group 3.
Group 4: Access & Networking
These variables behave identically to App_GKE. See App_GKE, App_GKE, and App_GKE.
The following networking variables are available in Listmonk GKE:
| Variable | Default | Description |
|---|---|---|
service_type | "LoadBalancer" | Kubernetes service type. "LoadBalancer" provisions a GCP external load balancer. Use "ClusterIP" with enable_custom_domain = true for Gateway-based routing. |
enable_network_segmentation | false | Deploys a Kubernetes NetworkPolicy restricting pod-to-pod traffic to explicitly allowed paths. |
namespace_name | "" | Kubernetes namespace for the Listmonk workload. Auto-generated from application_name and tenant_deployment_id when blank. |
network_tags | ["nfsserver"] | Firewall tags applied to GKE cluster nodes. |
enable_iap | false | Enables Identity-Aware Proxy authentication on the load balancer. |
iap_authorized_users | [] | Individual users or service accounts granted IAP access. |
iap_authorized_groups | [] | Google Groups granted IAP access. |
iap_oauth_client_id | "" | OAuth client ID for IAP configuration. |
iap_oauth_client_secret | "" | OAuth client secret for IAP configuration. |
iap_support_email | "" | Support email shown on the Google OAuth consent screen. |
enable_custom_domain | true | Configures Ingress/Gateway for custom domain routing with managed SSL certificates. Enabled by default. |
application_domains | [] | Custom domain names (e.g. ["listmonk.example.com"]). |
reserve_static_ip | true | Reserves a Global Static IP for the load balancer. |
static_ip_name | "" | Name for the reserved IP; auto-generated if blank. |
enable_cloud_armor | false | Enables a Cloud Armor WAF security policy. |
admin_ip_ranges | [] | Admin CIDR ranges permitted through Cloud Armor. |
cloud_armor_policy_name | "default-waf-policy" | Name of the Cloud Armor security policy to attach. |
enable_cdn | false | Enables Cloud CDN on the backend service. Requires the Gateway routing path (enable_custom_domain = true). |
enable_vpc_sc | false | Enables VPC Service Controls perimeter enforcement. |
vpc_cidr_ranges | [] | CIDR ranges for VPC-SC egress rules. |
vpc_sc_dry_run | true | Runs VPC-SC in dry-run (audit-only) mode before enforcing. |
organization_id | "" | Override for the GCP organization ID. Required only for folder-nested projects. |
enable_audit_logging | false | Enables Cloud Audit Logs for VPC-SC activity. |
Group 5: Environment Variables & Secrets
These variables behave identically to App_GKE. See App_GKE.
Listmonk-specific defaults:
Listmonk Common automatically injects the database connection configuration into the container via LISTMONK_* environment variables. You do not need to set database connection strings manually. The following secrets are created and injected automatically:
| Secret Env Var | Purpose |
|---|---|
LISTMONK_ADMIN_PASSWORD | Randomly generated admin password for the Listmonk web UI (username is the hardcoded admin). Retrieve from Secret Manager after deployment to log in for the first time. |
LISTMONK_API_TOKEN | Deterministic API token for the self-healing programmatic API user (LISTMONK_API_USER, default rad-api), re-asserted into the database on every start. |
Additional LISTMONK_* environment variables (e.g. SMTP configuration for transactional emails) can be added via environment_variables. Listmonk's configuration uses a double-underscore (__) hierarchy notation:
environment_variables = {
LISTMONK_smtp__host = "smtp.mailgun.org"
LISTMONK_smtp__port = "587"
LISTMONK_smtp__username = "postmaster@mg.example.com"
LISTMONK_smtp__password = "your-smtp-password"
LISTMONK_smtp__tls_type = "STARTTLS"
}
The remaining secrets variables (secret_environment_variables, secret_rotation_period, secret_propagation_delay, enable_auto_password_rotation, rotation_propagation_delay_sec) behave as described in App_GKE.
Group 6: Backup & Maintenance
These variables behave identically to App_GKE. See App_GKE.
Listmonk-specific defaults:
| Variable | Default | Notes |
|---|---|---|
backup_schedule | "0 2 * * *" | Daily at 02:00 UTC. Adjust to match your Recovery Point Objective. Listmonk databases grow steadily with subscriber and campaign records — schedule backups during low-traffic windows. |
backup_retention_days | 7 | 7-day retention. Increase to 30+ days for production deployments with active subscriber lists. |
Backup Import — Listmonk GKE supports importing an existing backup on first deployment:
| Variable | Default | Description |
|---|---|---|
enable_backup_import | false | When true, runs a one-time import job during deployment to restore the backup specified by backup_source and backup_file. Configure before enabling. |
backup_source | "gcs" | Source system for the backup file. "gcs" imports from a Cloud Storage URI; "gdrive" imports from a Google Drive file ID. |
backup_file | "backup.sql" | Filename of a backup stored in the module's automatically created backups GCS bucket. |
backup_format | "sql" | Format of the backup file. Supported values: sql, tar, gz, tgz, tar.gz, zip, auto. |
Group 7: CI/CD & GitHub Integration
Identical to App_GKE. See App_GKE.
The following CI/CD variables are available: enable_cicd_trigger, github_repository_url, github_token, github_app_installation_id, cicd_trigger_config (default { branch_pattern = "^main$" }), enable_cloud_deploy, cloud_deploy_stages (default [dev, staging, prod]), enable_binary_authorization, binauthz_evaluation_mode (default "ALWAYS_ALLOW"; options: ALWAYS_ALLOW, REQUIRE_ATTESTATION, ALWAYS_DENY).
Group 8: Jobs & Scheduled Tasks
These variables behave as described in App_GKE, with one important Listmonk-specific behaviour.
Listmonk default db-init job:
When initialization_jobs is left as the default (empty list []), Listmonk Common automatically supplies a db-init job:
| Field | Value |
|---|---|
| Job name | db-init |
| Image | postgres:15-alpine |
| Purpose | Creates the Listmonk PostgreSQL database and user in the Cloud SQL instance |
| Execute on every apply | true |
| CPU / Memory | 1000m / 512Mi |
Override initialization_jobs with a non-empty list to replace this default with your own jobs. Each custom job must specify at least one of command, args, or script_path.
Schema migrations: Unlike some applications, Listmonk does not require a separate schema initialisation job. Listmonk's own binary detects a fresh database and applies the full schema migration on first startup. The
db-initjob only needs to create the database and user — Listmonk handles the rest.
CronJobs and Additional Services:
The cron_jobs and additional_services variables are available and behave identically to App_GKE. See App_GKE for full documentation.
Note: The
cron_jobsschema inListmonk GKEuses Kubernetes CronJob fields —restart_policy,concurrency_policy,failed_jobs_history_limit,successful_jobs_history_limit,starting_deadline_seconds,suspend— rather than Cloud Run-style fields. Thesecret_env_varsfield is not available in GKE cron jobs; secrets are managed viasecret_environment_variablesat the module level.
Group 9: Storage & Filesystem — NFS
These variables behave identically to App_GKE. See App_GKE.
Listmonk-specific defaults:
| Variable | Default | Notes |
|---|---|---|
enable_nfs | false | NFS is disabled by default. Listmonk uses GCS Fuse for media and attachment storage rather than a shared NFS volume. Enable NFS only if your deployment requires a shared POSIX filesystem across all pods for reasons outside Listmonk's own storage needs. |
nfs_mount_path | "/mnt/nfs" | Path where the NFS volume is mounted if NFS is enabled. Not used by default. |
nfs_volume_name | "nfs-data-volume" | Kubernetes volume name for the NFS mount. |
nfs_instance_name | "" | Name of an existing Filestore instance to use. Auto-discovered when blank. |
nfs_instance_base_name | "app-nfs" | Base name for the Filestore instance when creating a new one. |
Group 10: Storage & Filesystem — GCS
These variables behave identically to App_GKE. See App_GKE Group 9.
Listmonk-specific defaults:
Listmonk Common automatically provisions a GCS bucket for Listmonk's media and attachment storage via GCS Fuse. This bucket is mounted into the container at a path Listmonk uses for uploaded files. You do not need to define it manually in storage_buckets.
| Bucket | name_suffix | Purpose |
|---|---|---|
| Auto-provisioned | listmonk-uploads | Listmonk uploaded media, subscriber import files, and campaign attachments via GCS Fuse CSI Driver |
Additional GCS buckets can be defined in storage_buckets — for example, a separate bucket for backup exports or custom template assets.
The create_cloud_storage, storage_buckets, gcs_volumes, manage_storage_kms_iam, enable_artifact_registry_cmek, max_images_to_retain, delete_untagged_images, and image_retention_days variables behave as described in App_GKE Group 9.
Group 11: Database Configuration
These variables behave identically to App_GKE. See App_GKE.
Listmonk-specific defaults and restrictions:
| Variable | Listmonk GKE Default | App GKE Default | Notes |
|---|---|---|---|
database_type | "POSTGRES_15" | "POSTGRES" | Listmonk requires PostgreSQL. Do not change this to a MySQL variant — Listmonk only supports PostgreSQL and will fail at startup with an unsupported database error. |
application_database_name | "listmonk" | "gkeappdb" | The PostgreSQL database name created in the Cloud SQL instance. Do not change after deployment — changing this recreates the database and destroys all Listmonk data. |
application_database_user | "listmonk" | "gkeappuser" | The PostgreSQL user that Listmonk uses to connect. Do not change after deployment — changing this recreates the user and invalidates all existing credentials. |
database_password_length | 32 | 32 | Length of the auto-generated database password. Longer passwords improve security; 32 characters is appropriate for production. |
enable_postgres_extensions | false | false | Listmonk does not require any additional PostgreSQL extensions beyond the defaults. Enable only if you are adding custom database functionality. |
Important: Listmonk auto-applies schema migrations on startup. The first boot after a fresh deployment will take slightly longer than subsequent boots as the full schema is created. The startup probe is configured with an initial delay of 30 seconds to accommodate this.
Cloud SQL instance discovery:
| Variable | Default | Description |
|---|---|---|
enable_cloudsql_volume | true | Mounts the Cloud SQL Auth Proxy Unix socket inside the container. Required for Listmonk to connect to Cloud SQL via socket path. |
cloudsql_volume_mount_path | "/cloudsql" | Path where the Auth Proxy socket is mounted inside the Listmonk container. |
Automatic password rotation is also supported:
| Variable | Default | Description |
|---|---|---|
enable_auto_password_rotation | false | Deploys an automated database password rotation job. When true, the database password is rotated on the schedule defined by secret_rotation_period and GKE pods are restarted to pick up the new credential. |
rotation_propagation_delay_sec | 90 | Seconds to wait after rotation before restarting pods, to allow Secret Manager replication to complete. |
Group 12: Custom SQL Scripts
Identical to App_GKE. See App_GKE.
| Variable | Default | Description |
|---|---|---|
enable_custom_sql_scripts | false | Runs custom SQL scripts from a GCS bucket during deployment. Useful for seeding Listmonk with initial list definitions, templates, or subscriber segments. |
custom_sql_scripts_bucket | "" | GCS bucket containing the SQL scripts to execute. |
custom_sql_scripts_path | "" | Path prefix within the bucket for the SQL script files. |
custom_sql_scripts_use_root | false | Runs custom SQL scripts as the Cloud SQL root user instead of the application user. Required for scripts that create extensions or modify system-level settings. |
Group 13: Observability & Health
These variables behave identically to App_GKE. See App_GKE.
Listmonk-specific defaults:
Listmonk exposes a dedicated /api/health HTTP endpoint, but as of Listmonk v6.1.0 it sits behind session auth and returns 403 {"message":"invalid session"} to an unauthenticated request — so an HTTP probe against it never passes. Listmonk_GKE therefore uses TCP probes on port 9000 by default (Kubernetes supports tcpSocket for both startup and liveness probes, unlike Cloud Run which forbids a TCP liveness probe). The unauthenticated /health endpoint (no /api prefix) does return 200, but is not wired as the default probe.
Health probe defaults:
| Variable | Listmonk Default | Notes |
|---|---|---|
health_check_config | { enabled = true, type = "TCP", path = "/api/health", initial_delay_seconds = 30, period_seconds = 30, failure_threshold = 3 } | Kubernetes liveness probe. TCP check on port 9000 — the path field is retained in the schema but unused for a TCP check. |
startup_probe_config | { enabled = true, type = "TCP", path = "/api/health", initial_delay_seconds = 30, period_seconds = 10, failure_threshold = 30 } | Kubernetes startup probe. failure_threshold = 30 gives Listmonk up to 300 seconds (30 × 10 s) to start before Kubernetes restarts the pod — sufficient for even cold-start schema migrations. |
uptime_check_config | { enabled = false, path = "/api/health" } | Cloud Monitoring uptime check. Disabled by default — enable explicitly if you want an alert fired to support_users when the endpoint stops responding (note /api/health requires a session, so an HTTP uptime check would need to target /health instead). |
The alert_policies variable is available and behaves as described in App_GKE.
Group 14: Reliability Policies
Identical to App_GKE. See App_GKE.
| Variable | Default | Notes |
|---|---|---|
enable_pod_disruption_budget | true | Prevents all Listmonk pods from being disrupted simultaneously during GKE node upgrades. Highly recommended for production deployments. |
pdb_min_available | "1" | At least one pod remains available during voluntary disruptions. With a single-replica deployment, this blocks node drain operations — use at least 2 replicas in production to allow rolling maintenance. |
Additional variables: enable_topology_spread, topology_spread_strict.
Group 15: Redis Cache
These variables configure optional Redis integration. Listmonk does not use Redis as part of its core architecture — enable_redis defaults to false and Redis is not required to run Listmonk.
Note: Redis is disabled by default in
Listmonk GKE. Unlike applications that use Redis for page caching, Listmonk performs all campaign scheduling and subscriber management internally without a cache layer. Only enable Redis if you have a specific integration use case, such as a custom plugin or external task queue.
| Variable | Default | Options / Format | Description & Implications |
|---|---|---|---|
enable_redis | false | true / false | Disabled by default. Listmonk does not require Redis. Enable only if a custom integration or sidecar service in your deployment consumes Redis. When enabled and redis_host is blank, the module defaults to the NFS server IP — ensure enable_nfs = true or provide an explicit redis_host. |
redis_host | "" | Hostname or IP address | The hostname or IP address of the Redis server. Leave blank to use the automatically discovered NFS server IP when Redis is enabled. Override with a dedicated host for a Google Cloud Memorystore instance. |
redis_port | "6379" | Port number string | Standard Redis port. Change only if your Redis instance is on a non-standard port. |
redis_auth | "" | String (sensitive) | Authentication password for the Redis server. Leave empty for unauthenticated Redis. For Memorystore with AUTH enabled, set this to the instance's AUTH string. |
Validating Group 15 Settings
Google Cloud Console:
- Memorystore instance (if used): Navigate to Memorystore → Redis to confirm the instance exists, its IP address, port, and AUTH status.
- Redis environment variables in the pod: Confirm the
REDIS_HOSTenvironment variable is present in the Listmonk pod's environment if Redis was enabled.
gcloud CLI / kubectl:
# List Memorystore Redis instances in the project (if using Memorystore)
gcloud redis instances list \
--region=REGION \
--project=PROJECT_ID \
--format="table(name,host,port,state,memorySizeGb,authEnabled)"
# Confirm any Redis environment variables are set in the Listmonk pod
kubectl exec -n NAMESPACE POD_NAME -- env | grep -i redis
# Test Redis connectivity from inside the Listmonk pod (if enabled)
kubectl exec -n NAMESPACE POD_NAME -- \
nc -zv REDIS_HOST 6379
Group 16: Custom Domain & Static IP
Identical to App_GKE. See App_GKE.
Listmonk base URL configuration: Listmonk must know its public-facing URL for generating unsubscribe links, confirmation emails, and campaign tracking pixels. This is not an env var —
app.root_urlis a row in Listmonk'ssettingstable, soListmonk_Common'sentrypoint.shsets it automatically on every start from the platform-injectedGKE_SERVICE_URL, after the--installstep (usingpsql, since--installonly seeds it viaINSERT ... ON CONFLICT DO NOTHING, so the entrypoint'sUPDATEalways wins on later starts). If you attach a custom domain viaapplication_domains, verifyapp.root_urlunder Settings → General in the Listmonk UI and update it manually if it still shows the GKE service URL instead of your custom domain.
Group 17: GKE Backend Configuration
Identical to App_GKE. See App_GKE.
Available variables: gke_cluster_name, namespace_name, workload_type, service_type, enable_multi_cluster_service, configure_service_mesh, enable_network_segmentation, termination_grace_period_seconds (default 30), gke_cluster_selection_mode (default "primary"; options: explicit, round-robin, primary), network_name (default ""; auto-discovered when empty).
Session affinity note:
session_affinitydefaults to"ClientIP"in Listmonk GKE. This is critical for Listmonk's campaign management interface: without session affinity, the Listmonk web UI can lose campaign editing state between page loads when requests route to different pod replicas. Keep"ClientIP"for any multi-replica Listmonk GKE deployment.
Group 18: Stateful Workloads
Identical to App_GKE. See the StatefulSet configuration described in App_GKE (workload_type = "StatefulSet") and the associated StatefulSet variables.
Available variables: stateful_pvc_enabled (default null), stateful_pvc_size (default "10Gi"), stateful_pvc_mount_path (default "/data"), stateful_pvc_storage_class (default "standard-rwo"), stateful_headless_service, stateful_pod_management_policy, stateful_update_strategy, stateful_fs_group (default 0).
StatefulSet auto-select: Setting
stateful_pvc_enabled = truewithout an explicitworkload_typeautomatically resolves to"StatefulSet". Settingworkload_type = "Deployment"alongsidestateful_pvc_enabled = truefails at plan time.
Listmonk stores all persistent state in PostgreSQL rather than on local disk, so stateful_pvc_enabled is not required for normal operation. Enable StatefulSet PVCs only if your deployment requires a stable local filesystem — for example, for custom plugin data or local queue buffers.
Module Outputs
Listmonk GKE exposes the following Terraform outputs:
| Output | Description |
|---|---|
service_name | Name of the Kubernetes service |
service_url | Service URL |
service_external_ip | External IP address of the load balancer |
project_id | GCP project ID |
deployment_id | Deployment ID suffix |
namespace | Kubernetes namespace |
database_instance_name | Name of the Cloud SQL instance |
database_name | Name of the application database |
database_user | Name of the application database user |
database_password_secret | Secret Manager secret name for the database password |
storage_buckets | Created GCS storage buckets |
container_image | Container image used for the deployment |
cicd_enabled | Whether the CI/CD pipeline is enabled |
github_repository_url | GitHub repository URL connected for CI/CD |
kubernetes_ready | true when the GKE cluster endpoint is reachable and all Kubernetes workload resources are deployed. false on the first apply of a new inline cluster — the cluster is created but the endpoint is not yet readable, so Kubernetes resources are skipped. The CI/CD pipeline must re-run apply to complete the deployment. |
Exploring with the GCP Console
Once your Listmonk GKE deployment is complete, the GCP Console provides a rich view of every layer of the deployment. This section walks through each area and explains what to look for.
GKE Workloads
Navigate to Kubernetes Engine → Workloads in the GCP Console. Select your cluster from the cluster dropdown.
- Listmonk Deployment: You should see a Deployment named
listmonk-<tenant_deployment_id>in the namespacelistmonk-<tenant_deployment_id>. The Pods column should show at least1/1pods running ifmin_instance_count = 1. Drill into the Deployment to see the pod template, environment variables (including theLISTMONK_*config), resource requests and limits, and the Cloud SQL Auth Proxy sidecar container. - db-init Job: On first deployment, a completed Job named
db-initwill appear in the same namespace under Kubernetes Engine → Workloads (filter by resource type: Job). A green checkmark indicates the PostgreSQL database and user were created successfully. If the job shows a red failure state, click into the job's pod logs to diagnose the Cloud SQL connection or credential issue. - Pods: Navigate to Kubernetes Engine → Pods and filter by namespace. Each running pod shows two containers: the Listmonk application container (
listmonk) and the Cloud SQL Auth Proxy sidecar (cloud-sql-proxy). Click a pod and select the Logs tab to stream live application logs directly from the Console. - Services & Ingress: Navigate to Kubernetes Engine → Services & Ingress. The LoadBalancer service for Listmonk will appear with an External endpoints column showing the provisioned IP address and port. If
enable_custom_domain = true, a Gateway or Ingress resource will appear alongside it.
Cloud SQL
Navigate to SQL in the GCP Console.
- Instance: The Cloud SQL instance is typically named
app-sql-<tenant_deployment_id>(or a custom name ifsql_instance_namewas set). Click the instance to verify its database version shows PostgreSQL 15. The Overview tab shows CPU and memory utilisation, active connections, and query throughput — useful for validating that Listmonk is connecting and sending queries. - Databases: Under the instance's Databases tab, confirm the
listmonkdatabase exists (or your customapplication_database_name). This is the database Listmonk's schema migration created on first startup. - Users: Under the Users tab, confirm the
listmonkuser (or yourapplication_database_user) exists. Listmonk connects as this user for all database operations. - Connections: Under the Connections tab, confirm the instance is configured for Private IP only (if using VPC-native connectivity) or that the Cloud SQL Auth Proxy is the only connection path. The Auth Proxy connection from the GKE cluster will appear in the Active connections panel.
- Backups: Under the Backups tab, confirm automated backups are enabled and the most recent backup completed successfully. The backup schedule follows
backup_schedule(default0 2 * * *).
Secret Manager
Navigate to Security → Secret Manager in the GCP Console.
- Database password: A secret named
secret-<resource_prefix>-<app>-db-password(or similar, Foundation-managed) holds the auto-generated database credential. Click the secret and navigate to Versions to confirm a currentENABLEDversion exists. Do not disable or destroy this version — it is mounted into the Listmonk pod at runtime. - Admin password: A secret named
secret-<resource_prefix>-listmonk-admin-password(injected asLISTMONK_ADMIN_PASSWORD) holds the randomly generated Listmonk admin UI password (username is the hardcodedadmin). Click into the latest version and use Access secret value to retrieve the password for your first login. Store this credential in a password manager immediately. - API token: A secret named
secret-<resource_prefix>-listmonk-api-token(injected asLISTMONK_API_TOKEN) holds the deterministic API token for the self-healing programmatic API user. - Access log: Each secret's Access log tab shows a Cloud Audit Log of every time a version was accessed, by which service account, and from which IP. Verify that only the Listmonk workload identity service account is accessing the database password secret.
Artifact Registry
Navigate to Artifact Registry → Repositories in the GCP Console.
- Repository: If
container_image_source = "custom"andenable_image_mirroring = true, a repository named after the deployment (e.g.listmonk-<tenant_deployment_id>) will contain the built Listmonk image. The repository will show the image tag (matchingapplication_version) and the digest. - Image details: Click an image tag to view the full digest, size, creation time, and vulnerability scanning results. Review the vulnerability findings tab before promoting to production — Listmonk's base image is regularly updated, and the platform's retention policy (
max_images_to_retain = 7) will automatically clean up old image versions. - Cleanup policies: Under the repository's Settings tab, confirm the cleanup policy is configured to retain the last
max_images_to_retainimages and delete untagged images afterimage_retention_daysdays.
Cloud Monitoring
Navigate to Monitoring → Dashboards or Monitoring → Uptime checks in the GCP Console.
- Uptime checks:
uptime_check_config.enableddefaults tofalse. If explicitly enabled, an uptime check targetingGET /api/healthon the Listmonk service endpoint will appear (note that path returns 403 unauthenticated — target/healthinstead for a meaningful check). A green status indicator confirms the endpoint is responding. A red or yellow status triggers an alert tosupport_users. - Alert policies: Navigate to Monitoring → Alerting. Two alert policies are provisioned automatically: one for uptime check failures and one for the check failure threshold. Click each policy to confirm the notification channels (email addresses from
support_users) are configured correctly. - Metrics Explorer: Navigate to Monitoring → Metrics Explorer and query
kubernetes.io/container/cpu/request_utilisationandkubernetes.io/container/memory/used_bytesfor thelistmonkcontainer in your namespace. Compare against the configuredcpu_limit(1000m) andmemory_limit(512Mi) to validate resource sizing during active campaign sends. - GKE cluster overview: Navigate to Kubernetes Engine → Clusters, click your cluster, and select the Observability tab to see node-level CPU and memory utilisation. GKE Autopilot provisions nodes on-demand — during a large campaign send, you may see a node provisioning event as pod resource requirements temporarily spike.
Cloud Build (if CI/CD enabled)
Navigate to Cloud Build → History in the GCP Console.
- Build history: Each push to the branch matching
cicd_trigger_config.branch_pattern(default^main$) triggers a build. Builds are listed with status, duration, and the triggering commit. Click a build to view the step-by-step log including image build, push to Artifact Registry, and optional deployment trigger. - Triggers: Navigate to Cloud Build → Triggers to confirm the trigger is connected to the correct GitHub repository and branch pattern.
Exploring with gcloud / kubectl
The following commands let you inspect every layer of a Listmonk GKE deployment from the command line. Replace PROJECT_ID, REGION, CLUSTER_NAME, NAMESPACE, and POD_NAME with your actual values. The namespace and deployment name are typically listmonk-<tenant_deployment_id>.
# ── Cluster Access ──────────────────────────────────────────────────────────
# Fetch GKE cluster credentials and set the local kubeconfig context
gcloud container clusters get-credentials CLUSTER_NAME \
--region REGION \
--project PROJECT_ID
# List all GKE clusters in the project to find the correct cluster name
gcloud container clusters list \
--project PROJECT_ID \
--format="table(name,location,status,currentNodeCount)"
# ── Namespace & Workload Overview ────────────────────────────────────────────
# List all pods in the Listmonk namespace and their status
kubectl get pods -n NAMESPACE -o wide
# Show full details of the Listmonk Deployment (replicas, strategy, conditions)
kubectl describe deployment listmonk-DEPLOYMENT_ID -n NAMESPACE
# Watch pods in real time during a rolling update or new deployment
kubectl get pods -n NAMESPACE --watch
# ── Pod Logs ─────────────────────────────────────────────────────────────────
# Tail live logs from the Listmonk application container
kubectl logs -n NAMESPACE POD_NAME -c listmonk --follow
# Show the last 100 lines from the Cloud SQL Auth Proxy sidecar container
kubectl logs -n NAMESPACE POD_NAME -c cloud-sql-proxy --tail=100
# Show logs from all Listmonk pods simultaneously (useful after a campaign send)
kubectl logs -n NAMESPACE -l app=listmonk --all-containers=true --follow
# ── Container Environment & Health ───────────────────────────────────────────
# List all environment variables inside the Listmonk container (confirm LISTMONK_* vars)
kubectl exec -n NAMESPACE POD_NAME -c listmonk -- env | grep LISTMONK
# Confirm the Cloud SQL Auth Proxy socket is present inside the container
kubectl exec -n NAMESPACE POD_NAME -c listmonk -- ls -la /cloudsql/
# Test the Listmonk health endpoint from inside the container
kubectl exec -n NAMESPACE POD_NAME -c listmonk -- \
wget -qO- http://localhost:9000/api/health
# ── Services & Networking ─────────────────────────────────────────────────────
# Show the Listmonk Kubernetes Service and its external IP
kubectl get service -n NAMESPACE
# Describe the LoadBalancer service to see provisioned IP and port mappings
kubectl describe service listmonk-DEPLOYMENT_ID -n NAMESPACE
# ── Initialization Jobs ───────────────────────────────────────────────────────
# List all Jobs in the namespace (including the db-init job)
kubectl get jobs -n NAMESPACE
# Show the logs from the db-init job pod to confirm successful DB creation
kubectl logs -n NAMESPACE -l job-name=db-init
# ── Cloud SQL ─────────────────────────────────────────────────────────────────
# List Cloud SQL instances in the project
gcloud sql instances list \
--project PROJECT_ID \
--format="table(name,databaseVersion,region,state,ipAddresses[0].ipAddress)"
# Describe the Listmonk Cloud SQL instance
gcloud sql instances describe SQL_INSTANCE_NAME \
--project PROJECT_ID \
--format="yaml(name,databaseVersion,state,settings.backupConfiguration)"
# List databases on the Cloud SQL instance
gcloud sql databases list \
--instance SQL_INSTANCE_NAME \
--project PROJECT_ID
# List users on the Cloud SQL instance
gcloud sql users list \
--instance SQL_INSTANCE_NAME \
--project PROJECT_ID
# ── Secret Manager ────────────────────────────────────────────────────────────
# List all secrets with the listmonk deployment prefix
gcloud secrets list \
--project PROJECT_ID \
--filter="name:listmonk" \
--format="table(name,createTime,replication.automatic)"
# Access the Listmonk admin password secret value (first login)
gcloud secrets versions access latest \
--secret="secret-RESOURCE_PREFIX-listmonk-admin-password" \
--project PROJECT_ID
# Check secret version status (confirm the latest version is ENABLED)
gcloud secrets versions list LISTMONK_DB_PASSWORD_SECRET_NAME \
--project PROJECT_ID \
--format="table(name,state,createTime)"
# ── GCS Storage ───────────────────────────────────────────────────────────────
# List GCS buckets created for this Listmonk deployment
gsutil ls -p PROJECT_ID | grep listmonk
# Show the contents of the Listmonk uploads bucket
gsutil ls gs://LISTMONK_UPLOADS_BUCKET_NAME/
# ── Resource Usage ────────────────────────────────────────────────────────────
# Show live CPU and memory usage for all pods in the namespace
kubectl top pods -n NAMESPACE
# Show node-level resource usage for the GKE Autopilot cluster
kubectl top nodes
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).
| Variable | Sensible Default | Risk | Consequence of Incorrect Value |
|---|---|---|---|
project_id | (required) | Critical | No default — deployment fails immediately. |
database_type | "POSTGRES_15" | Critical | Listmonk only supports PostgreSQL. Changing to a MySQL variant causes Listmonk to fail at startup with a database driver error. All credential injection and connection string assembly assumes PostgreSQL. |
application_database_name | "listmonk" | Critical | Immutable after deployment — changing this recreates the database and destroys all subscribers, campaigns, lists, and templates. |
application_database_user | "listmonk" | Critical | Immutable after deployment — changing this recreates the user, invalidates the stored credential, and breaks Listmonk's database connection. |
container_port | 9000 | Critical | Listmonk listens on port 9000. Changing this without matching the container's bound port causes all health probes to fail and the pod to enter a crash loop. |
health_check_config.type / startup_probe_config.type | "TCP" | Critical | Both default to a TCP check on port 9000, not HTTP. Switching to "HTTP" against /api/health will fail every probe — that endpoint requires an authenticated session and returns 403 to the kubelet, causing a permanent restart loop. |
startup_probe_config.failure_threshold | 30 | High | Listmonk runs schema migrations on first boot. Reducing this value below ~10 can cause Kubernetes to restart the pod before migrations complete, creating an infinite restart loop on fresh deployments. |
session_affinity | "ClientIP" | High | Listmonk's campaign management interface stores state in-memory per process. Without "ClientIP" session affinity, the web UI loses campaign editing state on every page load that routes to a different pod replica. |
container_resources.memory_limit | "512Mi" | Medium | Sufficient for small lists at rest. During bulk subscriber imports or large campaign sends, memory usage spikes. Increase to at least "1Gi" for lists above 50,000 subscribers. |
container_resources.cpu_limit | "1000m" | Medium | Sufficient for normal Listmonk operation. High-frequency campaign sends to large lists are CPU-bound — consider increasing to "2000m" for active sending workloads. |
min_instance_count | 1 | High | Setting to 0 enables scale-to-zero, which is not suitable for Listmonk. The campaign scheduler and bounce handler require a continuously running process. Cold starts during scale-up delay pending campaign sends. |
enable_nfs | false | Low | Listmonk uses GCS Fuse for storage, not NFS. Enabling NFS unnecessarily provisions a Filestore instance and incurs additional cost without benefit for Listmonk. |
enable_redis | false | Low | Listmonk does not use Redis. Enabling it without a valid redis_host may cause the module to attempt to connect to the NFS server IP on port 6379, generating connection errors if no Redis process is running there. |
application_version | "latest" | Medium | Using "latest" for production is not recommended — it makes deployments non-reproducible and can introduce breaking changes when the upstream image is updated. Pin to a specific version tag (e.g. "v4.1.0") for production. |
backup_retention_days | 7 | Medium | Too short for active mailing lists. A corrupted subscriber import or accidental list deletion may not be discovered within 7 days. Increase to 30+ days for production. |
enable_pod_disruption_budget | true | Medium | Already enabled. Disabling allows all Listmonk pods to be terminated simultaneously during GKE node upgrades, causing a service interruption for in-progress campaign sends. |
pdb_min_available | "1" | Medium | With a single replica, PDB prevents voluntary node drain indefinitely. Use at least 2 replicas in production to allow rolling maintenance without blocking cluster upgrades. |
quota_memory_requests / quota_memory_limits | "" | Critical (GKE-specific) | Must use binary suffixes (Gi, Mi) when set. Bare integers are treated as bytes by Kubernetes and prevent all pods from being scheduled in the namespace. |
termination_grace_period_seconds | 30 | Medium | Listmonk processes in-flight requests and may have active campaign send goroutines at shutdown. If Listmonk is mid-send on a large campaign batch, 30 seconds may not be enough to flush the queue. Consider increasing to 60 or 120 for large-volume sending workloads. |
enable_auto_password_rotation | false | Low | Disabled by default. When enabled, rotation restarts all Listmonk pods. Schedule rotations during maintenance windows to avoid interrupting active campaign sends. |
enable_cloud_armor | false | Medium | Without Cloud Armor, the Listmonk admin UI (/) is protected only by Listmonk's own authentication. Enable Cloud Armor and configure admin_ip_ranges for any publicly accessible production deployment. |