Overview
Listmonk is a high-performance, self-hosted newsletter and mailing list manager distributed as a single Go binary — an alternative to Mailchimp, Sendy, and Mautic. This template deploys the listmonk server (admin dashboard, campaign engine, public subscription and tracking pages, and the transactional-mail API) on port9000, backed by a bundled PostgreSQL store. The database schema is installed and the Super Admin account is created automatically on first boot, so a default install needs no setup wizard.
The bundled database is single-instance by default; set postgresHA.enabled: true (and postgres.enabled: false) for a three-replica Patroni cluster with automatic failover. Listmonk itself always runs as exactly one replica — see Scaling and Availability.
What Gets Created
Prerequisites
One dictionary secret must exist before you install. The Super Admin guards a login form on the public endpoint, so its credentials are a prerequisite secret rather than template values — a value would sit in plaintext in the Helm release for the life of the install. Secrets are org-level, so no GVC flag is involved.Create the admin credentials secret
username (at least 3 characters) and password (at least 8 characters) — both are upstream requirements:admin.secretName to the name you used.Read the credentials back later
-o yaml. A bare cpln secret reveal prints only a summary table, not the values:postgres.credentials.* (change the password before installing). Database backups are optional and need a bucket and provider access first — see Backing Up. SMTP is configured in listmonk’s own admin UI after installing — see First Run.
Installation
Install into an existing GVC, naming the admin secret you created and a database password of your own:UI
CLI
Terraform
Pulumi
Configuration
Application
Admin Bootstrap
username and password are read only during the first boot’s schema install, when listmonk creates the Super Admin. After that the account lives in the database: editing the secret does not update it, and an upgrade does not change your existing password. Manage users in Admin → Users.
Root URL
http://localhost:9000, which would break every one of those links. The container therefore sets it at boot, before serving:
rootUrlset — written on every boot, so the value in your values file always wins. Set it to your custom domain once you attach one.rootUrlempty andpublicAccess.enabled: true— the canonical*.cpln.appendpoint is written, but only while the setting still holds the untouchedhttp://localhost:9000default. A value you later change in Settings → General is kept.rootUrlempty andpublicAccess.enabled: false— nothing is written; set the Root URL yourself in Settings → General.
Access
publicAccess.enabled: false only for an internal-only instance; see Connecting for how to reach a private install.
Single-Instance PostgreSQL
The default backing store. Exactly one ofpostgres or postgresHA must be enabled — the chart refuses to render otherwise.
Highly Available PostgreSQL
SetpostgresHA.enabled: true for near-zero-downtime database upgrades and automatic failover: a three-replica Patroni cluster coordinated by etcd, fronted by an HAProxy leader endpoint that listmonk connects through for writes and schema migrations (the chart refuses to render if that proxy is disabled). The credentials are the same postgres.credentials.* values above, written by this template into the secret named here.
Connecting
/admin in your browser:
publicAccess.enabled: false, reach the admin UI through a tunnel instead and open http://localhost:9000/admin:
First Run
There is no setup wizard — the schema is installed and the Super Admin exists when the workload reports ready. Sign in at/admin with the credentials from your admin secret, then finish the two settings listmonk keeps in its database rather than in Helm values:
Configure SMTP
Check the Root URL
*.cpln.app endpoint (or to rootUrl if you set one); on a private install, or if the boot log shows a WARN could not set app.root_url line, set it here by hand. See Root URL for how the template manages it.Choose a media store (optional)
Operations
Backing Up
Database backups are off by default. When enabled, a scheduled backup job runs inside the backing PostgreSQL store and uploads to your bucket under the configured prefix — covering lists, subscribers, campaigns, templates, and settings, but not the media on the uploads volume set. Enable withpostgres.backup.enabled: true or postgresHA.backup.enabled: true to match your database mode, and complete the storage setup for your provider before installing. In HA mode, postgresHA.backup.mode selects logical (scheduled pg_dump) or wal-g (continuous WAL archiving).
- AWS S3
- Google Cloud Storage
- S3-compatible (MinIO)
Create a bucket
backup.aws.bucket and backup.aws.region to match.Set up a Cloud Account
backup.aws.cloudAccountName to its name.Create a bucket-scoped IAM policy
backup.aws.policyName to its name:Restoring a Backup
A restore replays the SQL dump into the backing database from a client inside the GVC that can reach both the bucket and the database — the per-provider commands are on the PostgreSQL page for single mode and the PostgreSQL Highly Available page for HA mode. For this template, substitute the hostRELEASE_NAME-postgres.GVC_NAME.cpln.local (single) or RELEASE_NAME-postgres-ha-proxy.GVC_NAME.cpln.local (HA), and the username, password and database values from postgres.credentials.*.
Upgrading from 1.0.x
Version1.1.0 moved the Super Admin login out of Helm values and into the prerequisite dictionary secret. An upgrade that still carries either removed key is refused at render, before anything is applied, with a message naming its replacement — there is no compatibility fallback.
1.0.x default (change-me-listmonk-admin), treat that password as compromised and change it now.
Create the admin credentials secret
Drop the removed keys from your values
admin.username and admin.password, and set admin.secretName instead.Upgrade
Upgrading from 1.1.0
Version1.2.0 moved the bundled database to the postgres 3.4.1 template, which no longer takes credentials as values. Listmonk absorbed that change rather than passing it on, so there is no new prerequisite — only a rename on the default (single-instance) path:
Upgrading from 1.2.0
Version1.3.0 adopted the postgres-highly-available 2.5.0 template for the HA path. It affects only installs with postgresHA.enabled: true; single-instance installs need no action.
the postgres block was REMOVED in 2.5.0. Delete the postgres block under postgresHA and set postgres.credentials.* to the same values it held — the HA database was initialised with them. This template writes them into the secret named by postgresHA.config.credentialsSecretName, so do not create that secret yourself. If you use MinIO backups on the HA path, create a dictionary secret holding accessKey and secretKey as shown in Backing Up and set postgresHA.backup.minio.credentialsSecretName to its name.
Upgrading from 1.3.1
Version1.4.0 renamed the listmonk resource limits so it is unambiguous which number is the ceiling. An upgrade that still carries the old names is refused at render:
1.5.0 then added rootUrl and the boot-time Root URL handling described under Root URL; no values change is required for it.
Scaling and Availability
- Listmonk is a single instance by design — there is no
replicasknob. Listmonk runs its campaign workers in-process, and upstream allows only one instance per database: two would send every campaign twice. The workload is pinned to one replica and rolls out without surge — the old replica stops before the new one starts — so an upgrade or restart causes a brief gap in availability rather than overlapping senders. Do not hand-scale the workload or point a second copy at the same database. - Database availability is the one knob you have. The default single-instance store restarts on upgrade and reschedules on node failure;
postgresHA.enabled: truegives a three-replica cluster with automatic failover — see Highly Available PostgreSQL. - The first
cpln helm upgradeafter an install may restart the bundled database even when nothing changed; listmonk waits for it and comes back on its own.
Troubleshooting
Workload never starts and cpln logs returns zero lines
Workload never starts and cpln logs returns zero lines
admin.secretName did not exist at install time. The container never ran, so there is no output to log.Fix: read the one place the reason appears:cpln workload force-redeployment RELEASE_NAME-listmonk --gvc GVC_NAME to pick it up immediately.Container exits immediately with a message naming the admin secret
Container exits immediately with a message naming the admin secret
username is under 3 characters or the password under 8. Upstream’s install step would otherwise fail on every attempt inside the database-wait loop, which looks like a database problem — so the container checks the lengths first and names the real cause.Fix: update the secret with compliant values, then force a redeployment:Boot log repeats waiting for database
Boot log repeats waiting for database
postgres.credentials.* was changed after the database volume was first initialized, so listmonk presents a password the database never received.Fix: check the database workload with cpln workload get-deployments RELEASE_NAME-postgres --gvc GVC_NAME -o yaml (or RELEASE_NAME-postgres-ha in HA mode). If the credentials were changed after the first boot, restore the original values; the password is applied to the database on its first boot only.Subscription, unsubscribe or tracking links point at localhost:9000
Subscription, unsubscribe or tracking links point at localhost:9000
WARN could not set app.root_url line. On a private install (publicAccess.enabled: false with no rootUrl) nothing sets it.Fix: set rootUrl to the address subscribers use (or correct it in Settings → General) — see Root URL. Check the boot log with:Campaign finishes with zero messages sent
Campaign finishes with zero messages sent
Second release in the same org is refused at install
Second release in the same org is refused at install
postgres.config.credentialsSecretName (or postgresHA.config.credentialsSecretName). Secret names are org-wide, and the first release owns the default my-listmonk-db-credentials. Nothing is shared, overwritten or deleted — the second install simply creates nothing.Fix: give the second release its own secret name and install again.Upgrade is refused at render naming a removed key
Upgrade is refused at render naming a removed key
admin.username / admin.password (1.1.0), postgres.config.username / .password / .database (1.2.0), or resources.cpu / resources.memory (1.4.0). Each guard names its replacement, and nothing is applied, so the running release is untouched.Fix: follow the matching subsection above — 1.0.x, 1.1.0, or 1.3.1 — then upgrade again.Important Notes
- Create the admin secret before installing. A reference to a secret that does not exist wedges the workload with zero log lines; see Prerequisites for the one command that shows the reason.
- The Super Admin is created on first boot only. Editing the secret later does not change the account, and upgrades never change your existing password — manage users in Admin → Users.
- Change
postgres.credentials.passwordbefore installing, not after. It seeds the database on its first boot and is used exactly as given; changing it afterwards leaves listmonk with a password the database does not know. - Give each listmonk release in an org its own
postgres.config.credentialsSecretName. Secret names are org-wide; a second release left on the default name is refused at install. - Do not scale listmonk past one replica or point a second instance at the same database — duplicate campaign sends result. See Scaling and Availability.
- No mail is delivered until SMTP is configured in Settings → SMTP. Starting a campaign before that is not an error — it finishes with nothing sent.
- Keep
publicAccessenabled for subscriber-facing pages to work, and keep the Root URL equal to the address subscribers use — see Root URL. - Use
/healthfor external health checks, not/api/health, which requires an authenticated session. - Data lives on the volume sets and survives redeploys and upgrades;
cpln helm uninstalldeletes them — all lists, subscribers, campaigns, and media go with the release. Enable backups for production data.