Overview
Listmonk is a high-performance, self-hosted newsletter and mailing list manager distributed as a single Go binary — a self-hosted alternative to Mailchimp, Sendy, and Mautic. This template deploys the listmonk server on port9000 — admin dashboard, campaign engine, public subscription and tracking pages, and the transactional-mail API — backed by PostgreSQL, with the database schema install and Super Admin bootstrap handled automatically on first boot.
Architecture
- Listmonk — A stateful, single-replica workload serving the admin UI, the public subscription/tracking pages, and the HTTP API on port
9000. The container boots through listmonk’s own idempotent install chain: it waits for the database, installs the schema, applies any pending migrations, then starts the server. A default install is ready in about a minute with no manual setup step, and a restart re-runs the same chain safely — it detects an already-provisioned database, skips the install, and leaves existing data untouched. - PostgreSQL (single-instance, default) — The postgres template as a subchart: the store for all lists, subscribers, campaigns, templates, and settings.
- PostgreSQL (HA, optional) — The postgres-highly-available template instead: 3× Patroni PostgreSQL with automatic failover, fronted by an HAProxy leader endpoint that listmonk connects through for writes and schema migrations.
What Gets Created
- Stateful Listmonk Workload — The listmonk server on port
9000, one replica, with configurable CPU and memory. - Database Workloads — Single mode: one stateful PostgreSQL workload. HA mode: a stateful Patroni PostgreSQL workload, a stateful etcd workload, and a standard HAProxy leader-routing workload.
- Volume Sets — A persistent uploads volume set mounted at
/listmonk/uploadsfor media uploaded through the admin UI, plus the database subchart’s data volumes (10 GiB by default; per replica in HA mode). - Secrets — The database credentials secret created by the backing store subchart. The Super Admin credentials are not created by this template: you create that dictionary secret yourself before installing and reference it by name (see Prerequisites).
- Identity & Policy — An identity bound to the listmonk workload, with a policy granting
revealon exactly two secrets: your admin credentials secret and the active backing store’s credentials secret. - Cron Backup Workload (optional) — Created in the backing PostgreSQL store when database backups are enabled.
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. The template creates no admin secret of its own.Create the admin credentials secret
username and password. Secrets are org-level, so no GVC flag is involved:admin.secretName to the name you used.Read the password back later
-o yaml is required; without it the command prints the secret’s metadata table rather than its contents:- SMTP — required before any mail is delivered. Configured in listmonk’s own admin UI (see Post-Install Setup).
- Database backups (optional) — need a bucket and provider access set up beforehand (see Backing Up).
UI
CLI
Terraform
Pulumi
Choosing a Database Mode
Exactly one of the two backing stores must be enabled — the chart enforces this at render. Listmonk is wired to the active database automatically, including for the first-boot schema install.postgres.enabled: false and postgresHA.enabled: true.
Configuration
Key configuration values (see the template’svalues.yaml for the complete set):
Application
image— The official listmonk image from Docker Hub.resources— CPU and memory bounds for the listmonk workload. Listmonk is a single Go binary, so the defaults suit typical installs.volumeset.capacity— Initial size in GiB (minimum 10) of the uploads volume set mounted at/listmonk/uploads. It holds images and files uploaded through the admin Media page, including generated thumbnails, and survives restarts and redeploys.timezone— The container timezone, which governs the times used for scheduled campaigns.
Admin Bootstrap
admin.secretName— Name of the dictionary secret you created in Prerequisites, holding theusernameandpasswordof the Super Admin. The credentials never pass through values, and the template creates no secret of its own — it references yours and grants the workloadrevealon exactly that one secret.- The account is created during the first boot’s schema install, from the secret’s contents. Because the values are no longer visible when the chart renders, the container enforces upstream’s minimum lengths at startup instead — see the length warning in Prerequisites.
Access
publicAccess.enabled— Serve listmonk on the auto-assigned*.cpln.appHTTPS canonical endpoint. It is deliberately on by default and load-bearing: the subscriber-facing pages — subscription forms, unsubscribe links, and tracking pixels — are served to your subscribers on the open internet, so a private instance cannot do the job. The admin UI and admin API on the same endpoint remain authentication-gated, behind a credential you created rather than a published default. Set tofalsefor an internal-only instance: external requests are then refused at the edge, and in-GVC callers still reach it perinternalAccess.internalAccess.type— Controls which workloads can reach listmonk over the internal network:
Backing Store
Enable exactly one ofpostgres (single-instance, default) or postgresHA (HA) — see Choosing a Database Mode. In both modes, change the database password before installing (postgres.credentials.password / postgres.credentials.password); it seeds the database on first boot and cannot be changed by editing values afterwards.
If you run more than one release of this template in the same organization, give each its own postgres.config.credentialsSecretName. Secret names are organization-wide, so a second release left on the default name is refused at install and creates nothing — the first release is unaffected.
The database holds everything except uploaded media: lists, subscribers, campaigns, templates, users, and all of the settings you configure in the admin UI.
Upgrading From 1.0.x
Version1.1.0 moved the Super Admin login out of Helm values. Earlier versions shipped a username and a working password as values, used exactly as written — a published default guarding a login form on the public endpoint, sitting in the Helm release for the life of the install.
LISTMONK_ADMIN_* is never read again, so the secret’s contents only matter to a fresh install. Change the password in Admin → Settings → Users. If the install is still carrying the published 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
Connecting
https://<canonical>.cpln.app/admin and sign in with the credentials from your admin secret — the schema is already installed and the Super Admin already exists, so there is no setup wizard and no manual install step.
Post-Install Setup
Mail delivery and object-storage media are listmonk settings stored in its database, not template values. Configure them in the admin UI after installing:Configure SMTP
Set the root URL
*.cpln.app endpoint, or to your custom domain once you attach one, so that links and tracking URLs inside your emails point at the right host.Choose a media store (optional)
Backing Up
Database backups are optional and disabled 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, and settings, but not the uploaded media on the listmonk volume set. Enable withpostgres.backup.enabled or postgresHA.backup.enabled (matching your database mode), and complete the storage setup for your provider before installing.
- 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:postgresHA.backup.mode selects logical (scheduled pg_dump) or wal-g (continuous WAL archiving). The full per-provider walkthrough lives in the backing postgres and postgres-highly-available template documentation.
Important Notes
- Single instance by design — there is no
replicasknob. Listmonk runs its campaign workers in-process, so two instances against one database 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, which means an upgrade or restart causes a brief gap in availability instead of overlapping senders. Durability comes from PostgreSQL and the uploads volume set. - Create the admin secret before installing — a missing prerequisite secret leaves the workload waiting on something that does not exist, with zero log lines. See Prerequisites for how to diagnose it.
- Mind the minimum credential lengths — a username under 3 characters or a password under 8 makes upstream’s install step fail; the container catches this at startup and names the secret instead of looping on a misleading database message.
- Change the database password before installing (
postgres.credentials.password/postgres.credentials.password) — it is bundled plumbing, used exactly as given, and it remains a value by design. Both the admin account and the database are seeded on first boot only. - No mail is delivered until SMTP is configured in Admin → Settings → SMTP. Before that, starting a campaign is not an error — it runs and finishes with zero messages sent.
- Keep
publicAccessenabled for subscriber-facing pages to work. Subscription forms, unsubscribe links, and tracking pixels must be reachable from the internet; the admin UI and API stay authentication-gated either way. - Use
/healthfor external health checks, not/api/health— the latter requires an authenticated session. - Volumes survive reinstalls under the same release name; uninstalling deletes them — the database volumes and the uploads volume set go with the release, taking all lists, subscribers, campaigns, and media with them. Use
postgresHAand/or enable backups for durable production data.