Skip to main content

Certification track: Associate Cloud Engineer (ACE)

Listmonk GKE Module — Configuration Guide

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 AreaApp GKE.md SectionListmonk-Specific Notes
Project & Identity§2 IAM & Access ControlIdentical.
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 ConfigurationLISTMONK_ADMIN_PASSWORD secret auto-generated by Listmonk Common; see Group 5: Environment Variables & Secrets.
Networking & Network Policies§3.D Networking & Network PoliciesIdentical.
Initialization Jobs & CronJobs§3.E Initialization Jobs & CronJobsdb-init PostgreSQL job supplied automatically by Listmonk Common; see Group 8: Jobs & Scheduled Tasks.
Additional Services§3.F Additional ServicesIdentical.
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 & CronJobsIdentical.
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 WAFIdentical.
Identity-Aware Proxy§4.B Identity-Aware Proxy (IAP)Identical.
Binary Authorization§4.C Binary AuthorizationIdentical.
VPC Service Controls§4.D VPC Service ControlsIdentical.
Secrets Store CSI Driver§4.E Secrets Store CSI DriverAlways enabled — no configuration required.
Traffic & Ingress§5 Traffic & IngressIdentical.
CDN§5.B CDNIdentical.
Custom Domain & Static IP§5.C Static IP ReservationListmonk public URL must match the domain configured in application_domains; see Group 16: Custom Domain & Static IP.
Cloud Build Triggers§6.A Cloud Build TriggersIdentical.
Cloud Deploy Pipeline§6.B Cloud Deploy PipelineIdentical.
Image Mirroring§6.C Image MirroringIdentical.
Pod Disruption Budgets§7.A Pod Disruption BudgetsIdentical.
Topology Spread Constraints§7.B Topology Spread ConstraintsIdentical.
Resource Quotas§7.C Resource QuotasIdentical.
Auto Password Rotation§7.D Auto Password RotationSee Group 11: Database Configuration.
Redis Cache§8.A Redis / Memorystoreenable_redis defaults to false; Listmonk does not require Redis; see Group 15: Redis Cache.
Backup Import§8.B Backup ImportSupports 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:

  1. PostgreSQL 15 is required. Listmonk uses PostgreSQL as its only supported database backend. The database_type default is set to "POSTGRES_15" and must not be changed to MySQL or any other engine.
  2. LISTMONK_ADMIN_PASSWORD is auto-generated. Listmonk Common creates a randomly generated admin password and stores it in Secret Manager, injected into the container as LISTMONK_ADMIN_PASSWORD — you do not need to configure it manually. The corresponding admin username is hardcoded to "admin" via LISTMONK_ADMIN_USER.
  3. Listmonk runs schema migrations automatically on first startup. No manual schema initialisation is required beyond the db-init job that creates the database and user. Listmonk's own binary applies all pending migrations when it boots.
  4. A db-init job runs on first deployment. Listmonk Common supplies a default db-init Kubernetes Job using a postgres:15-alpine image that creates the database and user in Cloud SQL. Override initialization_jobs to replace it with a custom job.
  5. GCS Fuse is used for media uploads instead of NFS. enable_nfs defaults to false. Listmonk stores uploaded subscriber files, attachments, and media via GCS Fuse, which provides durable object storage without requiring a shared NFS volume.
  6. Resource defaults are sized for Listmonk. The default cpu_limit (1000m) and memory_limit (512Mi) are appropriate for small to medium mailing list operations. Scale up container_resources for campaigns with large subscriber counts or high concurrent API usage.
  7. 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.
  8. Redis is not required. Listmonk does not use a cache layer — enable_redis defaults to false. 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:

VariableDefaultDescription
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:

VariableListmonk GKE DefaultApp GKE DefaultNotes
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_applicationtruetrueSet 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:

VariableListmonk GKE DefaultApp GKE DefaultNotes
container_port90008080Listmonk's native HTTP port. Do not change unless your custom Dockerfile binds Listmonk to a different port.
min_instance_count11At 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_count33Sufficient 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_volumetruetrueThe 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:

VariableDefaultDescription
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_segmentationfalseDeploys 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_iapfalseEnables 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_domaintrueConfigures 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_iptrueReserves a Global Static IP for the load balancer.
static_ip_name""Name for the reserved IP; auto-generated if blank.
enable_cloud_armorfalseEnables 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_cdnfalseEnables Cloud CDN on the backend service. Requires the Gateway routing path (enable_custom_domain = true).
enable_vpc_scfalseEnables VPC Service Controls perimeter enforcement.
vpc_cidr_ranges[]CIDR ranges for VPC-SC egress rules.
vpc_sc_dry_runtrueRuns 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_loggingfalseEnables 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 VarPurpose
LISTMONK_ADMIN_PASSWORDRandomly 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_TOKENDeterministic 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:

VariableDefaultNotes
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_days77-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:

VariableDefaultDescription
enable_backup_importfalseWhen 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:

FieldValue
Job namedb-init
Imagepostgres:15-alpine
PurposeCreates the Listmonk PostgreSQL database and user in the Cloud SQL instance
Execute on every applytrue
CPU / Memory1000m / 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-init job 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_jobs schema in Listmonk GKE uses 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. The secret_env_vars field is not available in GKE cron jobs; secrets are managed via secret_environment_variables at the module level.


Group 9: Storage & Filesystem — NFS

These variables behave identically to App_GKE. See App_GKE.

Listmonk-specific defaults:

VariableDefaultNotes
enable_nfsfalseNFS 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.

Bucketname_suffixPurpose
Auto-provisionedlistmonk-uploadsListmonk 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:

VariableListmonk GKE DefaultApp GKE DefaultNotes
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_length3232Length of the auto-generated database password. Longer passwords improve security; 32 characters is appropriate for production.
enable_postgres_extensionsfalsefalseListmonk 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:

VariableDefaultDescription
enable_cloudsql_volumetrueMounts 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:

VariableDefaultDescription
enable_auto_password_rotationfalseDeploys 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_sec90Seconds 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.

VariableDefaultDescription
enable_custom_sql_scriptsfalseRuns 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_rootfalseRuns 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:

VariableListmonk DefaultNotes
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.

VariableDefaultNotes
enable_pod_disruption_budgettruePrevents 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.

VariableDefaultOptions / FormatDescription & Implications
enable_redisfalsetrue / falseDisabled 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 addressThe 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 stringStandard 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_HOST environment 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_url is a row in Listmonk's settings table, so Listmonk_Common's entrypoint.sh sets it automatically on every start from the platform-injected GKE_SERVICE_URL, after the --install step (using psql, since --install only seeds it via INSERT ... ON CONFLICT DO NOTHING, so the entrypoint's UPDATE always wins on later starts). If you attach a custom domain via application_domains, verify app.root_url under 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_affinity defaults 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 = true without an explicit workload_type automatically resolves to "StatefulSet". Setting workload_type = "Deployment" alongside stateful_pvc_enabled = true fails 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:

OutputDescription
service_nameName of the Kubernetes service
service_urlService URL
service_external_ipExternal IP address of the load balancer
project_idGCP project ID
deployment_idDeployment ID suffix
namespaceKubernetes namespace
database_instance_nameName of the Cloud SQL 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
kubernetes_readytrue 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 namespace listmonk-<tenant_deployment_id>. The Pods column should show at least 1/1 pods running if min_instance_count = 1. Drill into the Deployment to see the pod template, environment variables (including the LISTMONK_* config), resource requests and limits, and the Cloud SQL Auth Proxy sidecar container.
  • db-init Job: On first deployment, a completed Job named db-init will 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 if sql_instance_name was 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 listmonk database exists (or your custom application_database_name). This is the database Listmonk's schema migration created on first startup.
  • Users: Under the Users tab, confirm the listmonk user (or your application_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 (default 0 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 current ENABLED version 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 as LISTMONK_ADMIN_PASSWORD) holds the randomly generated Listmonk admin UI password (username is the hardcoded admin). 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 as LISTMONK_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" and enable_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 (matching application_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_retain images and delete untagged images after image_retention_days days.

Cloud Monitoring

Navigate to Monitoring → Dashboards or Monitoring → Uptime checks in the GCP Console.

  • Uptime checks: uptime_check_config.enabled defaults to false. If explicitly enabled, an uptime check targeting GET /api/health on the Listmonk service endpoint will appear (note that path returns 403 unauthenticated — target /health instead for a meaningful check). A green status indicator confirms the endpoint is responding. A red or yellow status triggers an alert to support_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_utilisation and kubernetes.io/container/memory/used_bytes for the listmonk container in your namespace. Compare against the configured cpu_limit (1000m) and memory_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).

VariableSensible DefaultRiskConsequence of Incorrect Value
project_id(required)CriticalNo default — deployment fails immediately.
database_type"POSTGRES_15"CriticalListmonk 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"CriticalImmutable after deployment — changing this recreates the database and destroys all subscribers, campaigns, lists, and templates.
application_database_user"listmonk"CriticalImmutable after deployment — changing this recreates the user, invalidates the stored credential, and breaks Listmonk's database connection.
container_port9000CriticalListmonk 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"CriticalBoth 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_threshold30HighListmonk 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"HighListmonk'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"MediumSufficient 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"MediumSufficient for normal Listmonk operation. High-frequency campaign sends to large lists are CPU-bound — consider increasing to "2000m" for active sending workloads.
min_instance_count1HighSetting 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_nfsfalseLowListmonk uses GCS Fuse for storage, not NFS. Enabling NFS unnecessarily provisions a Filestore instance and incurs additional cost without benefit for Listmonk.
enable_redisfalseLowListmonk 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"MediumUsing "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_days7MediumToo 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_budgettrueMediumAlready 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"MediumWith 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_seconds30MediumListmonk 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_rotationfalseLowDisabled by default. When enabled, rotation restarts all Listmonk pods. Schedule rotations during maintenance windows to avoid interrupting active campaign sends.
enable_cloud_armorfalseMediumWithout 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.