A Helmfile-based deployment for Stoatchat on Kubernetes. Infrastructure components (MongoDB, Redis, RabbitMQ, MinIO) use battle-tested Helm charts, making it straightforward to integrate into an existing cluster or swap in managed services. A helmfile2compose integration generates Docker Compose setups from the same source — useful if you want the same config layer for both targets, though the generated Compose file is less readable than a hand-written one and dekube is an extra component to learn (the official self-hosted repo provides a simpler, turnkey Compose setup).
No Kubernetes cluster? See Deploy with Compose.
This is a reference implementation — monitoring, GitOps, and security policies are left to operators. Architecture adapted from lasuite-platform (La Suite Numérique).
This is not intended to replace the official stoatchat self-host repo
This started when it was severely outdated. I wanted an up-to-date deployment, and I as love Helmfile, I built one from scratch targeting Kubernetes.
Then people asked for Compose support because Kubernetes is overkill for their use case — fair enough — so I built dekube to generate Compose files from the same Helmfile source rather than maintain a separate Compose setup by hand.
Compared to the official repo, this adds Kubernetes support with its benefits, declarative infrastructure management via Helm, as well as using a patched client build with extra features not yet upstreamed.
All services run behind a single domain with path-based routing via HAProxy Ingress. LiveKit uses a separate subdomain.
stoatchat.local
/api/* → api (REST)
/ws → events (WebSocket)
/autumn/* → file-server (uploads)
/january/* → proxy (embeds/metadata)
/gifbox/* → gifbox (GIF proxy)
/* → client (web UI)
/livekit/* → livekit-server (WebRTC)
Backend services share a single Revolt.toml configuration file, generated
as a Kubernetes ConfigMap by the stoatchat-config chart and replicated across
namespaces via Reflector.
- Kubernetes cluster (tested on k3s, any conformant distribution works)
- Helm v3
- Helmfile v0.169+
kubectlconfigured for the target clusteropenssl(for secret generation)
git clone git@github.com:baptisterajaut/stoatchat-platform.git && cd stoatchat-platform
# 1. Generate configuration and deploy
./init.shinit.sh offers two modes:
- Local development — deploys everything on a local cluster with self-signed TLS
- Remote deployment — scaffolds an environment file for an external cluster with Let's Encrypt TLS and external infrastructure
For local mode, init.sh:
- Checks that
helm,kubectl,helmfile, andopensslare installed - Copies
environments/local.yaml.example→environments/local.yamland generates a randomsecretSeed - Generates VAPID keypair →
environments/vapid.secret.yaml - Generates file encryption key →
environments/files.secret.yaml - Pauses for you to review
environments/local.yaml - Runs
helmfile -e local sync(deploys all releases) - Prints the LoadBalancer IP and
/etc/hostsentry - Exports the self-signed CA certificate to
stoatchat-ca.pem
After the script completes:
# Add to /etc/hosts (use the IP printed by init.sh)
<LB_IP> stoatchat.local
# Trust the CA certificate (see TLS section below)Open https://stoatchat.local and create an account.
If you need to re-extract the hosts entry and CA certificate after a redeployment without re-running the full setup:
./init.sh --post-deployFor remote mode, init.sh generates environments/<name>.yaml from
remote.yaml.example and prints instructions for registering the
environment in helmfile.yaml.gotmpl. A commented-out template is
already provided there:
# my-instance:
# values:
# - versions/infra-versions.yaml
# - versions/stoatchat-versions.yaml
# - environments/_defaults.yaml
# - versions/infra-versions.yaml
# - versions/stoatchat-versions.yaml
# - environments/my-instance.yaml
# - environments/my-instance.secret-overrides.yaml # optional
# - environments/vapid.secret.yaml
# - environments/files.secret.yamlSee Advanced deployment for external infrastructure, secret overrides, and production configuration.
# 1. Create environment file from template
SEED=$(openssl rand -hex 24)
sed -e "s|__DOMAIN__|stoatchat.local|g" \
-e "s|__VOICE_ENABLED__|false|g" \
-e "s|__VIDEO_ENABLED__|false|g" \
-e "s/^secretSeed: \"\"/secretSeed: \"${SEED}\"/" \
environments/local.yaml.example > environments/local.yaml
# 3. Generate VAPID keys (push notifications)
TMPKEY=$(mktemp)
openssl ecparam -name prime256v1 -genkey -noout -out "$TMPKEY"
VAPID_PRIVATE=$(base64 < "$TMPKEY" | tr -d '\n' | tr -d '=')
VAPID_PUBLIC=$(openssl ec -in "$TMPKEY" -pubout -outform DER 2>/dev/null \
| tail -c 65 | base64 | tr '/+' '_-' | tr -d '\n' | tr -d '=')
rm -f "$TMPKEY"
cat > environments/vapid.secret.yaml <<EOF
vapid:
privateKey: "$VAPID_PRIVATE"
publicKey: "$VAPID_PUBLIC"
EOF
# 4. Generate file encryption key
cat > environments/files.secret.yaml <<EOF
files:
encryptionKey: "$(openssl rand -base64 32)"
EOF
# 5. Review local.yaml, then deploy
helmfile -e local syncPrimary configuration file. Created from local.yaml.example by init.sh.
| Key | Default | Description |
|---|---|---|
domain |
stoatchat.local |
Base domain for all services |
secretSeed |
(generated) | Master seed for deterministic secret derivation |
apps.<name>.enabled |
true/false |
Toggle individual services |
apps.api.webhooks |
true |
Enable incoming webhooks (upstream defaults to false) |
apps.gifbox.tenorKey |
"" |
Tenor API key (see known limitation) |
livekit.enabled |
false |
Enable LiveKit voice/video (requires extra config) |
livekit.rtcPortRangeStart |
50000 |
First UDP port for WebRTC media |
livekit.rtcPortRangeEnd |
60000 |
Last UDP port for WebRTC media |
tls.issuer |
selfsigned |
TLS issuer: selfsigned or letsencrypt |
smtp.host |
"" |
SMTP server for email verification (empty = disabled) |
All apps default to enabled except gifbox, voiceIngress (requires LiveKit), and livekit itself.
Note:
gifboxis disabled by default. When deployed, the client points at the local/gifboxpath. Without it, the GIF button returns a clean 404. See known limitations.
apps:
voiceIngress:
enabled: true # enable after livekit is enabledWebhooks are enabled by default (apps.api.webhooks: true). This diverges from upstream Stoatchat, which defaults to false — there may be a reason for that we're not aware of, so disable them if you run into issues:
apps:
api:
webhooks: falseTo enable voice and video calls:
livekit:
enabled: true
apps:
voiceIngress:
enabled: trueLiveKit requires host-network access for WebRTC media. The UDP port range
defaults to 50000–60000 (configurable via livekit.rtcPortRangeStart /
rtcPortRangeEnd). TCP port 7881 must also be open. LiveKit is exposed
at /livekit on the main domain via an Ingress created by the
stoatchat-config chart.
Without SMTP configured, email verification is skipped entirely (see Known limitations). To enable it:
smtp:
host: "smtp.example.com"
port: 587
username: "user"
password: "pass"
fromAddress: "noreply@stoatchat.example.com"
useTls: false
useStarttls: trueTwo issuers are supported:
selfsigned(default) — cert-manager generates a local CA. Suitable for development. Requires trusting the CA certificate (see below).letsencrypt— production certificates via ACME HTTP-01 challenge. RequiresadminEmailto be set and the domain to be publicly reachable.
All infrastructure credentials (MongoDB, Redis, RabbitMQ, MinIO, LiveKit)
are deterministically derived from secretSeed using
sha256(seed:identifier). This means a single seed reproduces all
passwords — no separate credential management needed.
Non-derivable secrets (VAPID keypair, file encryption key) are generated
once by init.sh and stored in gitignored *.secret.yaml files.
Upstream now publishes official client images, but this project uses a custom
build from the echohaus fork
which adds video/screenshare support, invite-only registration, email bypass,
multi-region LiveKit, and noise cancellation. The Dockerfile in docker/client/
builds with placeholder env vars and serves via nginx with runtime sed
replacement at startup.
Build settings are in docker/client/build.conf:
WEB_REPO="https://github.com/Dadadah/stoat-for-web" # web client git repo
WEB_REF="echohaus" # branch or tag to build
ASSETS_REPO="https://github.com/stoatchat/assets.git" # assets git repo
WEB_IMAGE="baptisterajaut/stoatchat-web" # destination image nameBuild and push:
# Uses values from build.conf
docker/client/build.sh
# Custom tag (first argument, default: dev)
docker/client/build.sh v1.0.0Environment variables still override build.conf for CI usage:
STOATCHAT_WEBCLIENT_IMAGE_PUBLISHNAME=myuser/stoatchat-web \
STOATCHAT_WEB_REF=v1.0.0 \
docker/client/build.sh v1.0.0The script auto-detects nerdctl or docker and prompts before pushing.
| URL | Service |
|---|---|
https://stoatchat.local |
Web client |
https://stoatchat.local/api |
REST API |
https://stoatchat.local/ws |
WebSocket events |
https://stoatchat.local/autumn |
File server |
https://stoatchat.local/january |
Metadata proxy |
https://stoatchat.local/gifbox |
GIF proxy |
wss://stoatchat.local/livekit |
LiveKit (if enabled) |
Create an account at the web client URL. Without SMTP configured, email verification is skipped and accounts are immediately usable.
| Component | Chart | Namespace | Notes |
|---|---|---|---|
| MongoDB | bitnami/mongodb | stoatchat-mongodb |
Primary database |
| Redis | bitnami/redis | stoatchat-redis |
Event broker and KV store |
| RabbitMQ | stoatchat-app (official image) | stoatchat-rabbitmq |
Message broker |
| MinIO | minio/minio | stoatchat-minio |
S3-compatible object storage |
| LiveKit | livekit/livekit-server | stoatchat-livekit |
WebRTC server (optional) |
| cert-manager | jetstack/cert-manager | cert-manager |
TLS certificate management |
| HAProxy | haproxytech/kubernetes-ingress | haproxy-controller |
Ingress controller |
| Reflector | emberstack/reflector | reflector |
Cross-namespace secret replication |
All Stoatchat application services (api, events, file-server, etc.) deploy into
the stoatchat namespace using the generic helm/stoatchat-app chart.
With tls.issuer: selfsigned, cert-manager generates a local CA. Browsers
will show certificate warnings until the CA is trusted.
The CA certificate is exported to stoatchat-ca.pem by init.sh. You can also
extract it manually:
kubectl get secret stoatchat-ca-secret -n cert-manager \
-o jsonpath='{.data.tls\.crt}' | base64 -d > stoatchat-ca.pemsudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain stoatchat-ca.pemsudo cp stoatchat-ca.pem /usr/local/share/ca-certificates/stoatchat-ca.crt
sudo update-ca-certificatesPowerShell (requires admin):
Import-Certificate -FilePath "stoatchat-ca.pem" -CertStoreLocation Cert:\LocalMachine\RootOr with certutil:
certutil.exe -addstore root stoatchat-ca.pemYou can also import via the GUI: Chrome → Settings → Privacy and security → Security → Manage certificates, or search "Manage computer certificates" in the Start menu.
Firefox uses its own certificate store. Import stoatchat-ca.pem via
Settings → Privacy & Security → Certificates → View Certificates →
Authorities → Import.
- Deploy with Compose — run without Kubernetes using
generate-compose.sh - Advanced deployment — external infrastructure, secret overrides, production guide
- Architecture decisions
- Known limitations
This deployment already diverges from official Stoatchat in a few areas where the self-hosting experience can be improved:
- Video support enabled — uses a patched client build with video calls functional plus email validation bypass, multi-region LiveKit, invite-only registration, and noise cancellation
- Webhooks enabled by default — incoming webhooks work out of the box (upstream defaults to disabled)
For organizations requiring single sign-on, preliminary research has been done on integrating Keycloak (or any OIDC provider) with Stoatchat. See docs/oidc-implementation-suggestions.md for a detailed analysis. This will never be submitted upstream (maintainers want to provide OAuth2 for third-party apps, not consume external IdPs), but could be implemented as a local adaptation for self-hosters who need it.
Not affiliated with the Stoatchat/Revolt project. The Helmfile structure, secret derivation pattern, and deployment tooling are adapted from lasuite-platform (La Suite Numérique) — see docs/decisions.md for details.
Feedback, bug reports, and contributions welcome.