Skip to main content

Overview

Chatwoot is an open-source customer engagement platform — an embeddable live-chat widget, a shared email inbox, and an omnichannel agent desk in one application. This template deploys the Community Edition (-ce image, MIT core): a Rails web tier serving the dashboard, REST API, widget and WebSockets on port 3000, a Sidekiq worker that also runs database migrations, a bundled single-node Redis, and a pgvector-capable PostgreSQL — highly available by default, or single-instance with one flag. Attachments go to a persistent volume or to object storage.
This template does not create a GVC. It deploys into an existing GVC that you already have; pass it with --gvc GVC_NAME.

What Gets Created

Your keys secret (see Prerequisites) is not created by the template.

Prerequisites

Chatwoot signs its sessions and encrypts sensitive columns with four keys that you supply through a dictionary secret created before installing. The values never pass through Helm values.
1

Create the keys secret

All four keys are required. Secrets are org-level, so no --gvc flag is involved:
Set secrets.name to this name.
2

Back the four values up

Store a copy outside Control Plane. All four keys are write-once for the life of the installation: rotating SECRET_KEY_BASE logs out every user, and rotating any ACTIVE_RECORD_ENCRYPTION_* key makes stored two-factor secrets undecryptable, locking out every agent who enabled MFA.
3

Choose the two bundled passwords

The database password (postgres.credentials.password) and the Redis password (redis.auth.password) ship as change-me-… placeholders that the template uses as-is. Pick strong values and pass them at install time — both seed their component on first boot and are not updated by later value edits. The Redis password may contain only letters, digits, - and _.
If the keys secret does not exist at install time, the deployment wedges silently. cpln logs returns zero lines — the container never starts, so it has nothing to log. The one place the reason appears is status.versions[].message:
Note this is get-deployments — plain cpln workload get has no versions field. Creating the secret repairs the deployment on its own after several minutes, or force a redeployment to skip the wait.
Everything else works with the defaults. Three optional features need their own setup first:
  • Object-storage attachments — an existing bucket plus either a Control Plane cloud account (AWS S3) or a static-key dictionary secret (S3-compatible servers). See Attachment Storage. Required for more than one web replica.
  • Authenticated SMTP — a dictionary secret holding SMTP_USERNAME and SMTP_PASSWORD. See Email.
  • Database backups — a bucket and access setup for AWS S3, Google Cloud Storage or an S3-compatible server. See Backing Up.

Installation

Once the keys secret exists, install into your existing GVC with the two bundled passwords set:
To install using another method, follow the instructions for it:

UI

Browse, install, and manage templates visually

CLI

Manage templates from your terminal

Terraform

Declare templates in your Terraform configurations

Pulumi

Declare templates in your Pulumi programs

Configuration

Each subsection shows the shipped defaults for one top-level values area.

Chatwoot Web

Both the web and worker workloads run chatwoot.image. Leave chatwoot.frontendUrl empty unless you front Chatwoot with a custom domain; both tiers then advertise the web workload’s public URL in links.

Sidekiq Worker

The worker is a fixed single replica because it also runs the database migrations and the first-run bootstrap. Scale background throughput with worker.concurrency, not replicas. It has no probes, so it reports ready as soon as it is scheduled, even while migrating — read its logs rather than its readiness:

Prerequisite Secret

Attachment Storage

Attachments are written to a volume set mounted at /app/storage on the web workload and survive restarts, redeploys and upgrades under the same release name.The volume is attached to the web workload only, so the Sidekiq worker cannot read attachments in this mode — attachment emails and ActiveStorage analyze and purge jobs cannot succeed. Local storage also works only with a single web replica. Use object storage for production.

Email

SMTP is off by default and a default install works without it — the onboarding wizard creates the first account with no email confirmation. For an authenticated relay, create the credentials secret before installing and set smtp.auth.secretName to its name; the Chatwoot identity is granted reveal on exactly that secret:
With SMTP disabled, Chatwoot falls back to sendmail, which is absent from the image, so nothing is delivered: agent invitations, password resets and email-channel replies all fail. Configure smtp.* before inviting agents.

Access

Public access is on by default because the chat widget and your visitors live outside the GVC. Set publicAccess.enabled: false for an internal-only instance; in-GVC callers still reach it according to internalAccess.type: The worker and the bundled Redis are never publicly reachable: the worker accepts no inbound traffic, and Redis accepts only same-GVC traffic.

Database

Enable exactly one of postgresHA (default) and postgres — the chart refuses to render with both or neither. Chatwoot is wired to the active database automatically: the HAProxy leader endpoint in HA mode, or the single instance directly. In both modes the database credentials come from postgres.credentials.*: this template writes them into a dictionary secret named by postgresHA.config.credentialsSecretName or postgres.config.credentialsSecretName and hands that name to the bundled database. There is nothing for you to create.
The other keys of the bundled postgres-highly-available and postgres templates are available under postgresHA.* and postgres.*, with one exception: postgresHA.proxy.enabled must stay true, because the HAProxy endpoint is the address Chatwoot connects to.
Choose the database mode before installing — it cannot be switched later. Flipping postgresHA.enabled / postgres.enabled on a live release points Chatwoot at a different, empty database (a separate volume set and a different PostgreSQL major version), so the app re-runs onboarding and your existing conversations are orphaned rather than migrated.
Secret names are org-wide, and the credentials secret is created by this template in both modes. If you run more than one release of this template in the same org, give each its own postgresHA.config.credentialsSecretName (or postgres.config.credentialsSecretName). A second release left on the default name is refused at install and creates nothing; the first release is unaffected.

Redis

Redis is required and runs as a single node by design: it carries the pub/sub channel behind every live update, and Redis does not propagate published messages between replicas, so a multi-node topology would silently drop a share of the updates that make the dashboard and widget refresh in real time. Persistence is on and eviction is disabled, so a queued job is never dropped. Change redis.auth.password before installing; only letters, digits, - and _ are accepted because the value is embedded in a connection URL.

Connecting

Read the public hostname and the stored credentials:
To verify the web tier from your machine without going through the public endpoint, forward port 3000 and query the readiness route, which reports the PostgreSQL and Redis status:

First Run

Chatwoot ships no default account. The first browser session to reach the endpoint runs the onboarding wizard, and the user it creates is the super admin.
1

Wait for the web workload to report ready

On the default HA database path a first install takes several minutes: the database cluster elects a leader, the worker loads the schema and runs the migrations, and only then does the web tier pass its readiness probe. The worker may restart a few times while the database comes up — that is expected on a first HA install.
2

Complete the onboarding wizard

Browse to the canonical endpoint of RELEASE_NAME-chatwoot. It redirects to /installation/onboarding, where the form creates your account and the super admin user. Do this as soon as the workload is ready — the wizard is unauthenticated until it has been completed. For an install with publicAccess.enabled: false, reach the same page through cpln port-forward RELEASE_NAME-chatwoot 3000:3000 --gvc GVC_NAME at http://localhost:3000.
3

Create an inbox

In the dashboard, add an inbox — a website widget for live chat, or an API channel for a custom integration. The widget snippet and the API channel identifier are issued there.
4

Configure email before inviting agents

Agent invitations, password resets and email-channel replies are all delivered by mail. Set up SMTP before you invite your team.
Instance-wide settings live in Chatwoot’s database, not in Helm values. Sign in as the super admin at https://<canonical>.cpln.app/super_admin; under Settings, ENABLE_ACCOUNT_SIGNUP controls whether visitors can create their own accounts. Self-serve signup is off on a fresh install — accounts come from the onboarding wizard and from agent invitations until you turn it on.

Operations

Backing Up

Database backups are optional and off by default. They cover the PostgreSQL database — conversations, contacts, inboxes and users. Attachments are not included: in object-storage mode they live in your bucket, in local mode on the RELEASE_NAME-chatwoot-storage volume set. Enable backups with postgresHA.backup.enabled: true or postgres.backup.enabled: true (matching your database mode) and complete the storage setup for your provider before installing. The keys below are shown as backup.*; set them inside the enabled database block.
1

Create a bucket

Create an S3 bucket. Set backup.aws.bucket and backup.aws.region to match.
2

Set up a cloud account

If you do not have one, create a cloud account for your AWS account. Set backup.aws.cloudAccountName to its name.
3

Create a bucket-scoped IAM policy

Create an AWS IAM policy with the JSON below (replace YOUR_BUCKET_NAME), then set backup.aws.policyName to the policy’s name:
In HA mode, backup.mode selects logical (a scheduled dump run by the RELEASE_NAME-postgres-ha-backup cron workload) or wal-g (continuous WAL archiving from the database workload). Single-instance mode takes scheduled logical dumps through RELEASE_NAME-postgres-backup. backup.*.prefix is the key prefix within the bucket, and the schedule is a standard cron expression in UTC.

Restoring a Backup

The database restore is the one documented for the bundled database: Restoring a Backup on the postgres-highly-available page (logical and WAL-G), or Restoring a Backup on the postgres page. In HA mode, connect through RELEASE_NAME-postgres-ha-proxy.GVC_NAME.cpln.local so the restore targets the current leader; use the username and password from the secret named by *.config.credentialsSecretName. Two Chatwoot-specific points:
  • The web and worker workloads must not be writing to the database while a logical dump is replayed. After the restore, force a redeployment of both so they reconnect and the cache is rebuilt:
  • Attachments are restored separately: copy them back into the bucket (object-storage mode) or onto the RELEASE_NAME-chatwoot-storage volume set (local mode).
This restore path has not been exercised against a Chatwoot install. The database procedure is the bundled template’s own; follow it with a test release first, and keep the four keys from your prerequisite secret with the backup — a dump restored under different ACTIVE_RECORD_ENCRYPTION_* keys cannot decrypt the two-factor secrets it contains.

Upgrading From 1.0.0

Version 1.0.1 only updates the bundled highly available database to a release that compacts its etcd cluster. Without compaction, etcd’s backend grows with time alone and eventually goes read-only, taking PostgreSQL failover with it. Upgrade; see etcd History Compaction for the mechanism and the symptoms. Only installs running the HA database (postgresHA.enabled, the default) are affected. Compaction stops further growth but cannot shrink a backend that has already grown.

Upgrading From 1.0.1 or Earlier

Version 1.1.0 changed the single-instance database path:
  • postgres.config.username, postgres.config.password and postgres.config.database moved to postgres.credentials.username, postgres.credentials.password and postgres.credentials.database. The new postgres.config.credentialsSecretName names the dictionary secret this template now creates from those three values. Carrying the old keys fails the render with config.username was REMOVED in postgres 3.4.0 — move the three keys and you are done, and ignore that message’s advice to create a secret yourself.
  • postgres.backup.minio.accessKey and postgres.backup.minio.secretKey were replaced by postgres.backup.minio.credentialsSecretName, a prerequisite dictionary secret holding accessKey and secretKey — see Backing Up.
Keep postgres.credentials.* equal to the values your database was initialised with; the running database still enforces them.

Upgrading From 1.1.0 or Earlier

Version 1.2.0 moved the highly available database to postgres-highly-available 2.5.0, which no longer creates its own credentials secret:
  • postgresHA.postgres.username, postgresHA.postgres.password and postgresHA.postgres.database were removed. Carrying them fails the render with the postgres block was REMOVED in 2.5.0. Delete the block.
  • In HA mode the credentials now come from postgres.credentials.* — the same keys the single-instance path uses — and this template writes them into the secret named by the new postgresHA.config.credentialsSecretName.
Use the credentials your cluster already has. Set postgres.credentials.username, postgres.credentials.password and postgres.credentials.database to exactly the values your postgresHA.postgres.* block held. The database was initialised with them and still enforces them; putting new values in the secret does not change the running database — it gives Chatwoot a password that no longer works.

Upgrading From 1.2.0 or Earlier

Version 1.2.1 replaced postgresHA.backup.minio.accessKey and postgresHA.backup.minio.secretKey with postgresHA.backup.minio.credentialsSecretName, a prerequisite dictionary secret holding accessKey and secretKey. Only installs backing up the HA database to an S3-compatible server are affected — see Backing Up for the secret.

Scaling and Availability

  • Web tier — chatwoot.replicas sets how many web replicas run. Replicas share the database and the bundled Redis, so a message received by one replica reaches a WebSocket client connected to another, and no sticky sessions are needed. Anything above 1 requires storage.type s3 or s3-compatible; the chart refuses to render the combination with local storage.
  • Worker and Redis — both are fixed at one replica. Scale background throughput with worker.concurrency.
  • Database — the default postgresHA path keeps serving through a replica failure and a rolling restart; the postgres path has no failover and is for development or light use. The mode is an install-time choice.
  • Helm upgrades — a helm upgrade can restart the bundled datastores even when their values did not change, and the web tier is unready while Redis is down; upgrade in a quiet window.
  • After any Redis restart, restart the web workload. Background jobs and the cache reconnect on their own, but the web tier’s live-update subscription does not: nothing is logged, the health endpoint stays green, and agents and visitors stop seeing new messages until they refresh. A forced redeployment of the web workload restores it:

Troubleshooting

Symptom: RELEASE_NAME-chatwoot and RELEASE_NAME-chatwoot-worker never reach ready, and cpln logs returns zero lines.Cause: a secret referenced by name does not exist — usually the keys secret (secrets.name), or the storage or SMTP secret when those features are enabled. The container never starts, so there is nothing to log.Fix: read status.versions[].message, which names the missing secret, then create it. The deployment recovers on its own after several minutes, or force a redeployment to skip the wait.
Symptom: the web log repeats waiting for the chatwoot schema... and the readiness probe never passes.Cause: the web tier deliberately stays down until the worker has created the schema, so this means the worker’s migration has not completed. On a first HA install the worker restarts a few times with PG::ConnectionBad while the database cluster elects a leader, which is expected. A migration that keeps failing is not.Fix: read the worker’s log — the worker reports ready even while crash-looping, so its readiness is not a signal.
Symptom: new messages appear only after a page refresh; /api still reports healthy and nothing is logged.Cause: the bundled Redis restarted (a redeploy, a reschedule or a Helm upgrade) and the web tier’s live-update subscription did not re-establish.Fix: force a redeployment of the web workload.
Symptom: helm upgrade fails before anything is applied, citing config.username was REMOVED in postgres 3.4.0.Cause: your values still carry the 1.0.x single-instance keys postgres.config.username, postgres.config.password or postgres.config.database.Fix: move the three keys to postgres.credentials.* as described in Upgrading From 1.0.1 or Earlier. Do not create a secret yourself — this template creates it.
Symptom: helm upgrade fails before anything is applied, citing the postgres block was REMOVED in 2.5.0.Cause: your values still carry postgresHA.postgres.username, postgresHA.postgres.password or postgresHA.postgres.database from version 1.1.0 or earlier.Fix: delete the postgresHA.postgres block and put the same three values under postgres.credentials.* — see Upgrading From 1.1.0 or Earlier.
Symptom: installing a second Chatwoot release in the same org fails with cannot be updated because it is being managed by a different release, and nothing is created.Cause: both releases use the same credentialsSecretName. Secret names are org-wide, and the first release owns that secret.Fix: give the second release its own postgresHA.config.credentialsSecretName (or postgres.config.credentialsSecretName). The first release is unaffected.
Symptom: with storage.type: s3-compatible, uploads succeed but images and files fail to load for agents or visitors.Cause: Chatwoot serves an attachment by redirecting the browser to storage.s3Compatible.endpoint, and that address resolves only inside the GVC.Fix: point storage.s3Compatible.endpoint at an address your users’ browsers can reach, and run helm upgrade.
Symptom: invitations, password resets and email-channel replies are not delivered.Cause: smtp.enabled is false. Chatwoot then falls back to sendmail, which the image does not contain.Fix: configure smtp.* as described in Email and run helm upgrade.
Symptom: helm install or helm upgrade fails with chatwoot.replicas > 1 requires storage.type 's3' or 's3-compatible'.Cause: local attachments live on a volume set attached to one web replica, so a file uploaded through one replica would be missing from another.Fix: switch to object storage as described in Attachment Storage before raising chatwoot.replicas.

Important Notes

  • Create the keys secret before installing — secrets.name must name an existing dictionary secret holding SECRET_KEY_BASE and the three ACTIVE_RECORD_ENCRYPTION_* keys; a missing secret wedges the deployment silently.
  • All four keys are write-once — rotating SECRET_KEY_BASE logs out every user, and rotating an ACTIVE_RECORD_ENCRYPTION_* key makes stored two-factor secrets undecryptable. Back the values up outside Control Plane.
  • Set postgres.credentials.password and redis.auth.password at install time — both ship as placeholders, seed their component on first boot, and are not updated by later value edits.
  • Complete the onboarding wizard right after install — the first browser session to reach the endpoint creates the super admin, with no email confirmation.
  • Restart the web workload after any Redis restart — live updates stop silently until it restarts.
  • Choose the database mode before installing — switching postgresHA and postgres on a live release points Chatwoot at a different, empty database.
  • In single-instance mode the database image must carry pgvector — keep postgres.image on a pgvector build; the chart refuses a stock postgres: image.
  • chatwoot.replicas above 1 requires object storage — local attachments are per-replica.
  • In local storage mode the worker cannot read attachments — attachment emails and ActiveStorage analyze and purge jobs fail. Use object storage for production.
  • AWS S3 is keyless only — a cloud account plus a bucket-scoped IAM policy; static keys are accepted only for S3-compatible endpoints.
  • With SMTP off, no mail is delivered — configure smtp.* before inviting agents.
  • Do not scale the worker or Redis — both are fixed singletons; scale background throughput with worker.concurrency.
  • Give each release its own credentialsSecretName — secret names are org-wide and a second release on the default name is refused at install.
  • Self-serve signup is managed in the app — turn it on at /super_admin under Settings; it is off on a fresh install.
  • Enterprise features are not included — the -ce image omits SSO/SAML, audit logs, agent capacity management, custom branding, SLA policies and Captain AI.
  • Data survives restarts and upgrades — conversations live in the database volume sets and local attachments in RELEASE_NAME-chatwoot-storage. Uninstalling deletes those volume sets; your keys secret survives an uninstall.

External References

Chatwoot Self-Hosted Docs

Official self-hosting documentation

Environment Variables

Every setting the Chatwoot application reads from its environment

Super Admin Console

Instance settings and the Sidekiq dashboard at /super_admin

Community vs Enterprise

What the Community Edition image includes and excludes

Chatwoot on GitHub

Source code and release notes

Chatwoot Template

View the source files, default values, and chart definition