Chatwoot is an open-source customer engagement platform unifying live chat, email, and social/messaging conversations into a shared inbox, with real-time visitor tracking and team collaboration. This guide deploys it via Docker Compose with PostgreSQL, Redis, Sidekiq for background jobs, and Traefik for TLS, then walks through creating and testing a live-chat inbox.
Prerequisites: a Linux server (4 vCPU / 8GB RAM minimum), Docker + Docker Compose, a domain A record (e.g.
chatwoot.example.com), optional SMTP credentials (needed for email channel, password resets, notifications).
Set Up the Project
$ mkdir -p ~/chatwoot/{postgres,redis,storage,letsencrypt}
$ cd ~/chatwoot
-
postgres— database files -
redis— cache + background job queue data -
storage— file uploads/attachments -
letsencrypt— Traefik ACME certs
Generate a secret key:
$ openssl rand -hex 32
Create the environment file:
$ nano .env
DOMAIN=chatwoot.example.com
LETSENCRYPT_EMAIL=[email protected]
FRONTEND_URL=https://chatwoot.example.com
SECRET_KEY_BASE=GENERATED_SECRET_KEY
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_DATABASE=chatwoot
POSTGRES_USERNAME=chatwoot
POSTGRES_PASSWORD=STRONG_POSTGRES_PASSWORD
REDIS_URL=redis://redis:6379
RAILS_ENV=production
NODE_ENV=production
RAILS_LOG_TO_STDOUT=true
ACTIVE_STORAGE_SERVICE=local
# Optional: SMTP configuration for email
# SMTP_ADDRESS=smtp.example.com
# SMTP_PORT=587
# SMTP_USERNAME=SMTP_USERNAME
# SMTP_PASSWORD=SMTP_PASSWORD
# SMTP_DOMAIN=chatwoot.example.com
# SMTP_ENABLE_STARTTLS_AUTO=true
# [email protected]
Replace the placeholders with your domain, email, DB password, and generated secret key. Uncomment and fill in the SMTP block if you need email channel support.
Deploy with Docker Compose
$ nano docker-compose.yml
services:
traefik:
image: traefik:v3.7.0
container_name: traefik
restart: unless-stopped
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "./letsencrypt:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
postgres:
image: pgvector/pgvector:pg16
container_name: chatwoot-postgres
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DATABASE}
POSTGRES_USER: ${POSTGRES_USERNAME}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- "./postgres:/var/lib/postgresql/data"
healthcheck:
test: ["CMD", "pg_isready", "-d", "${POSTGRES_DATABASE}", "-U", "${POSTGRES_USERNAME}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:alpine
container_name: chatwoot-redis
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes"]
volumes:
- "./redis:/data"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
rails:
image: chatwoot/chatwoot:v4.14.1
container_name: chatwoot-rails
restart: unless-stopped
command: bundle exec rails s -p 3000 -b 0.0.0.0
env_file:
- .env
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- "./storage:/app/storage"
labels:
- "traefik.enable=true"
- "traefik.http.routers.chatwoot.rule=Host(`${DOMAIN}`)"
- "traefik.http.routers.chatwoot.entrypoints=websecure"
- "traefik.http.routers.chatwoot.tls.certresolver=letsencrypt"
- "traefik.http.services.chatwoot.loadbalancer.server.port=3000"
sidekiq:
image: chatwoot/chatwoot:v4.14.1
container_name: chatwoot-sidekiq
restart: unless-stopped
command: bundle exec sidekiq -C config/sidekiq.yml
env_file:
- .env
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- "./storage:/app/storage"
- traefik — TLS termination, auto HTTP→HTTPS redirect, Let's Encrypt via ACME
- postgres — primary datastore for conversations/users/config
- redis — caching, sessions, Sidekiq queues
- rails — the web app + WebSocket server on port 3000
- sidekiq — background workers (email, webhooks, scheduled tasks)
Prepare the database and start everything:
$ docker compose run --rm rails bundle exec rails db:chatwoot_prepare
$ docker compose up -d
$ docker compose ps -a
$ docker compose logs
Confirm all containers show Up, and rails/sidekiq/traefik logs show no repeated errors or connection refusals.
First-Run Setup
Visit https://chatwoot.example.com (a 502 on first load just means give it a few seconds and refresh):
- Enter Name, Company Name, Work Email, Password.
- Toggle the newsletter checkbox as you like.
- Finish Setup to land on the dashboard.
Create a Live Chat Inbox
- Click here to create an inbox on the welcome dashboard.
- Select Website as the channel.
- Enter Website Name and Website Domain.
- Set widget color, welcome heading, and tagline; optionally enable channel greeting.
- Create inbox, optionally assign agents, then Copy the embed snippet from the confirmation screen.
Test the Widget
- Settings → Inboxes, click the gear icon on your inbox, then Script to view the embed code.
- Open in CodePen for a live preview.
- Click the chat bubble, send a test message.
- Back in Chatwoot, Conversations shows the new message — open it and reply from the dashboard.
Next Steps
Chatwoot is running with persistent storage, background workers, and TLS. From here:
- Add email and social channels (Facebook, WhatsApp, Instagram) alongside the website widget
- Configure SMTP so password resets and notification emails actually send
- Set up canned responses and team assignment rules to route conversations efficiently
For the full guide, visit the original article on Vultr Docs.
Top comments (1)
I noticed that the
docker-compose.ymlfile uses thepgvector/pgvector:pg16image for PostgreSQL, which is a great choice for efficient full-text search capabilities. However, it's worth considering the implications of using a specialized PostgreSQL image, as it may require additional configuration and tuning for optimal performance. Have you considered using a more standard PostgreSQL image, such as the officialpostgres:16image, and configuring full-text search capabilities separately? This could potentially simplify the deployment process and make it easier to manage and maintain the database.