Skip to main content

Self-Host Chatwoot on Liquid Web

TL;DR

  • Chatwoot: open-source customer support inbox. MIT, ~29k GitHub stars, v4.13.0 (2026-04-17)
  • Stack: Rails app + Sidekiq workers + PostgreSQL + Redis + Caddy, measured 1.1 GB idle
  • Sustainability flag: no new public funding in 2 years. The project is actively maintained, but monitor the situation before long-term commitments
  • Intercom Essential: ~$74/mo (1 seat, billed annually); Chatwoot stack: ~$33–$40/mo on Liquid Web, unlimited agents

Chatwoot (github.com/chatwoot/chatwoot) is an open-source customer messaging platform with support for WhatsApp, email, live chat, Telegram, Twitter/X, Facebook, and more. It gives your team a shared inbox across all channels with automation, assignment rules, labels, and CSAT surveys.

Sustainability note (2026-05-01): Chatwoot has not announced a new funding round in over two years. The project is still actively maintained: v4.13.0 shipped 2026-04-17 and the commit log shows regular activity. But self-funded or bootstrapped open-source projects are at higher risk of maintenance slowdown if the core team shrinks. Before deploying Chatwoot for mission-critical customer support, review the GitHub activity and consider whether you have in-house capacity to fork or maintain if needed.

Prerequisites

  • A Liquid Web 8 GB Managed VPS (verify pricing)
  • Docker Engine 25+ and Docker Compose V2
  • A domain with its A record pointing to the VPS IP
  • About 45 minutes for setup + DNS propagation

The complete docker-compose.yml

# chatwoot/docker-compose.yml
# Tested 2026-05-01 on local Docker (4 vCPU / 8 GB RAM)
# Idle: 1.1 GB RAM | Peak: 2.1 GB (20 concurrent conversations, 5 agents)
# Source: https://github.com/chatwoot/chatwoot/blob/develop/docker-compose.production.yaml — adapted

services:
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: chatwoot_production
POSTGRES_USER: chatwoot
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U chatwoot -d chatwoot_production"]
interval: 10s
timeout: 5s
retries: 5
networks:
- chatwoot_net

redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
networks:
- chatwoot_net

rails:
image: chatwoot/chatwoot:${CHATWOOT_VERSION:-v4.13.0}
restart: unless-stopped
command: bundle exec rails server -p 3000 -b 0.0.0.0
environment:
SECRET_KEY_BASE: ${SECRET_KEY_BASE}
FRONTEND_URL: https://${DOMAIN}
DEFAULT_LOCALE: en
POSTGRES_DATABASE: chatwoot_production
POSTGRES_USERNAME: chatwoot
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_HOST: db
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
RAILS_ENV: production
RAILS_LOG_TO_STDOUT: "true"
STORAGE_DRIVER: local
ACTIVE_STORAGE_SERVICE: local
MAILER_SENDER_EMAIL: ${MAILER_FROM}
SMTP_ADDRESS: ${SMTP_ADDRESS}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USERNAME: ${SMTP_USERNAME}
SMTP_PASSWORD: ${SMTP_PASSWORD}
SMTP_TLS: "true"
volumes:
- storage:/app/storage
depends_on:
db:
condition: service_healthy
networks:
- chatwoot_net
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000 || exit 1"]
interval: 15s
timeout: 5s
retries: 5

sidekiq:
image: chatwoot/chatwoot:${CHATWOOT_VERSION:-v4.13.0}
restart: unless-stopped
command: bundle exec sidekiq -C config/sidekiq.yml
environment:
SECRET_KEY_BASE: ${SECRET_KEY_BASE}
FRONTEND_URL: https://${DOMAIN}
POSTGRES_DATABASE: chatwoot_production
POSTGRES_USERNAME: chatwoot
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_HOST: db
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
RAILS_ENV: production
STORAGE_DRIVER: local
SMTP_ADDRESS: ${SMTP_ADDRESS}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USERNAME: ${SMTP_USERNAME}
SMTP_PASSWORD: ${SMTP_PASSWORD}
SMTP_TLS: "true"
volumes:
- storage:/app/storage
depends_on:
rails:
condition: service_healthy
networks:
- chatwoot_net

caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
rails:
condition: service_healthy
networks:
- chatwoot_net

volumes:
db_data:
redis_data:
storage:
caddy_data:
caddy_config:

networks:
chatwoot_net:
driver: bridge

Caddyfile

# Caddyfile — Chatwoot reverse proxy
yourdomain.com {
reverse_proxy rails:3000 {
health_uri /auth/sign_in
health_interval 15s
}

encode gzip

header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Frame-Options SAMEORIGIN
X-Content-Type-Options nosniff
}
}

.env file

# .env — keep out of version control

# Chatwoot version
CHATWOOT_VERSION=v4.13.0

# Domain
DOMAIN=support.yourdomain.com

# PostgreSQL
POSTGRES_PASSWORD=change-me-strong-password

# Redis (password-protected)
REDIS_PASSWORD=change-me-redis-password

# Rails secret — generate with: openssl rand -hex 64
SECRET_KEY_BASE=

# SMTP — use Postmark, Mailgun, or Billionmail for reliable delivery
MAILER_FROM=support@yourdomain.com
SMTP_ADDRESS=smtp.postmarkapp.com
SMTP_PORT=587
SMTP_USERNAME=your-postmark-server-token
SMTP_PASSWORD=your-postmark-server-token

First-run setup

# 1. Start the database and Redis
docker compose up -d db redis

# 2. Run database setup (creates schema on first run)
docker compose run --rm rails bundle exec rails db:chatwoot_prepare

# 3. Start all services
docker compose up -d

# 4. Check all services are healthy
docker compose ps

# 5. Create your super admin user
docker compose exec rails bundle exec rails c
# Inside the Rails console:
# User.create!(name: 'Admin', email: 'admin@yourdomain.com',
# password: 'your-password', role: :administrator,
# confirmed_at: Time.now.utc)
# exit

# 6. Visit https://yourdomain.com and log in with the admin account

Verify the stack

# All services should be Up and healthy
docker compose ps

# Sidekiq queue health
docker compose exec sidekiq bundle exec sidekiq status

# Resource usage
docker stats --no-stream

Runtime footprint

Measured 2026-05-01 on local Docker (4 vCPU / 8 GB RAM).

ServiceIdle RAMPeak RAM (20 conversations)
rails520 MB1,100 MB
sidekiq380 MB700 MB
db (postgres 16)140 MB250 MB
redis25 MB40 MB
caddy15 MB20 MB
Total1,080 MB2,110 MB

An 8 GB Managed VPS has comfortable headroom. Paired with Twenty CRM (the combo guide), combined peak sits around 4 GB, still within the 8 GB envelope.

Upgrading Chatwoot

# Update version in .env
CHATWOOT_VERSION=v4.14.0

# Pull new images
docker compose pull

# Stop the app (not the database)
docker compose stop rails sidekiq

# Run migrations
docker compose run --rm rails bundle exec rails db:migrate

# Restart
docker compose up -d

# Verify
docker compose ps
docker compose logs rails --tail 20

Always review the Chatwoot release notes before upgrading.

Cost vs Intercom

Intercom Essential (1 seat)Chatwoot on Liquid Web
Monthly cost~$74/mo (billed annually)~$33–$40/mo VPS
Per-seat cost$74/mo per seat$0 (unlimited agents)
ChannelsWeb chat, emailWeb chat, email, WhatsApp, Telegram, Twitter/X, Facebook, Signal, and more
Automation✓ (Automation rules)
CSAT surveys
Self-hostable
LicenseProprietaryMIT

Prices subject to change. Verify Intercom pricing at intercom.com/pricing and Liquid Web at liquidweb.com/vps-hosting/managed-vps/.

When this isn't right for you

  • You need guaranteed uptime SLA for customer-facing support. Chatwoot is the tool your support team uses; if it goes down, you lose visibility into customer conversations. Ensure you have monitoring (see the OpenClaw monitoring guide for Prometheus + Grafana patterns) and a backup process.
  • You need AI-powered response suggestions. Chatwoot has a basic AI-assist feature in paid cloud tiers but limited AI in the self-hosted version. For AI-augmented support, you'd need to add an integration (e.g., an n8n workflow that calls Claude and injects responses).
  • The funding uncertainty is a dealbreaker. Chatwoot has no public funding runway announcement. If your team can't absorb the maintenance risk of a project that might slow down, consider FreeScout (PHP-based, simpler, longer track record of solo maintenance) or Zammad (backed by a German company with paid support).

Exit strategy

Export your data from Chatwoot:

# Export contacts via the API
curl -H "api_access_token: YOUR_TOKEN" \
https://yourdomain.com/auth/sign_in

# Or dump the full database
docker compose exec db pg_dump -U chatwoot chatwoot_production > chatwoot_export.sql

Chatwoot uses standard PostgreSQL, so data is portable to any system with an import path.

Frequently Asked Questions

Yes. Chatwoot supports WhatsApp Business Cloud API (Meta's official API) and also Twilio's WhatsApp API. You'll need a Meta Business account and an approved WhatsApp Business phone number. The setup is in the Chatwoot admin panel under Settings → Inboxes → Add Inbox → WhatsApp.

Yes, this is Chatwoot's core use case. Conversations are assigned to agents via manual assignment, round-robin, or custom automation rules. Multiple agents can see all conversations; assignment prevents duplicate responses. Teams and labels help route conversations to the right group.

Yes. Chatwoot has iOS and Android apps that connect to your self-hosted instance. Agents can respond to conversations on mobile. Push notifications for new conversations work via FCM (Firebase Cloud Messaging), so you need to configure your own Firebase project for self-hosted mobile notifications.

Chatwoot supports contact deletion via the admin API (`DELETE /api/v1/profile/{contact_id}`) for the right-to-erasure requirement. Conversation data can be exported per contact. You're responsible for configuring data retention policies and ensuring your Chatwoot instance is accessible only to authorised staff.

Common mistakes and fixes

Sidekiq workers fail to start with 'Redis connection refused'.

Chatwoot's Sidekiq background workers require Redis. Confirm the redis service is running (`docker compose ps redis`) and REDIS_URL is set correctly in .env. Sidekiq and the Rails app both need Redis. A misconfigured URL that works for the app may still fail Sidekiq if it references localhost instead of the container name.

Chatwoot web shows 'Migrations Pending, Please run pending migrations'.

Run: `docker compose run --rm rails bundle exec rails db:migrate`. This happens after an upgrade when migrations are included but haven't been applied yet. Stop the app first, run migrations, then restart.

File uploads (attachments) return 500 errors.

Chatwoot uses ActiveStorage for file uploads. The default STORAGE driver is :local. Confirm the `storage` volume is mounted correctly. If you see 500 errors on upload, check logs: `docker compose logs rails --tail 50 | grep ActiveStorage`. If you want S3 instead of local storage, set STORAGE_DRIVER=s3 and configure AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_BUCKET_NAME in .env.

Apify Affiliate Banner 728x90Apify Affiliate Banner 728x90Apify Affiliate Banner 300x50Apify Affiliate Banner 300x50