SSD Nodes Learn 🎉 VPS từ $5.50/tháng
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-08-11

Tự host Chatwoot trên VPS bằng Docker

Triển khai Chatwoot trên VPS bằng Docker Compose và Traefik, ghim tag image, cấu hình SMTP gửi được, backup Postgres và uploads, nâng cấp an toàn.

Bạn sẽ triển khai gì

Để tự host Chatwoot trên VPS, bạn chạy 4 container: tiến trình web Rails, worker nền Sidekiq, PostgreSQL có extension pgvector và Redis. Chatwoot là help desk hỗ trợ khách hàng mã nguồn mở. Vì vậy, bạn có inbox chung cho team và widget chat trên website, chạy trên server do bạn kiểm soát. Cài đặt mất khoảng 20 phút. Những việc sau đó, gồm gửi mail, backup, nâng cấp và tính toán tài nguyên, mới quyết định hệ thống có còn chạy sau 1 năm hay không.

Mỗi container chỉ đảm nhiệm một việc. Rails phục vụ dashboard của agent và API của widget. Sidekiq xử lý các tác vụ chậm: gửi email, polling các channel đã kết nối, chạy các rule tự động hóa và tạo report. Postgres lưu conversation, contact, tài khoản agent và mọi setting bạn thay đổi trong dashboard. Redis lưu các queue của Sidekiq và channel pub/sub của ActionCable. Channel này đẩy message mới vào dashboard đang mở mà không cần reload trang. Redis ở đây không phải cache có thể bỏ đi, vì mất Redis đồng nghĩa với mất các job đang chờ xử lý.

Image Postgres trong file compose upstream là pgvector/pgvector:pg16 thay vì image postgres mặc định, vì schema của Chatwoot bật extension vector cho các tính năng AI. Nếu thay bằng Postgres mặc định, lần chạy database đầu tiên sẽ dừng với lỗi ERROR: extension "vector" is not available, vì image đó không có control file của extension. Hãy dùng image được upstream phát hành.

Hướng dẫn này giả định Docker và reverse proxy trên máy chủ đã hoạt động. Nếu chưa, hãy bắt đầu với Docker Compose trên VPS rồi quay lại.

VPS cần bao nhiêu tài nguyên để tự host Chatwoot?

Tính đến tháng 8 năm 2026, trang yêu cầu hệ thống upstream đề nghị tối thiểu 4 GB RAM và 4 CPU core, phù hợp với tối đa 10.000 cuộc hội thoại mỗi ngày. Trang này đưa ra mức 8 GB RAM và 8 core cho tối đa 20.000 cuộc hội thoại mỗi ngày. Trang cũng yêu cầu ít nhất 1 GB swap, với lý do nêu rõ: để máy không hết bộ nhớ trong lúc upgrade. Hãy dự trù 5 GB đến 10 GB disk cho Postgres, chưa tính file upload.

Nói thẳng: VPS 2 GB vẫn khởi động được Chatwoot và có vẻ ổn khi chỉ có 2 agent cùng inbox ít hoạt động. Nhưng nó thường gặp lỗi ở 2 thời điểm. Thứ nhất là Sidekiq. Upstream đo được Sidekiq sử dụng hơn 1 GB trên server bận, nên một đợt email tăng đột biến hoặc một report job có thể làm máy vượt quá dung lượng memory trước khi Rails, Postgres và Redis dùng phần của chúng. Thứ hai là lúc upgrade, vì db:chatwoot_prepare khởi động một Rails process mới để chạy migration, và việc boot Rails trên image này đã tốn hàng trăm MB trước khi thực hiện được công việc hữu ích nào.

Bạn sẽ không nhận được cảnh báo rõ ràng trước đó. Out of memory killer của kernel gửi SIGKILL đến process lớn nhất, Docker thấy container dừng, rồi restart: always khởi động lại container. docker compose ps sau đó hiển thị một container liên tục quay về Exited (137), trong đó 137 nghĩa là bị kill bởi signal 9. Xác nhận bằng sudo dmesg -T | grep -i "killed process"; lệnh này cho biết process mà kernel đã chọn.

Nếu 4 GB vượt quá ngân sách, bạn có thể chạy máy 2 GB với 2 GB swap và chấp nhận response time sẽ tăng khi tải cao, thay vì để service dừng hẳn. Dù chọn phương án nào, việc đặt hard memory ceiling cho từng service vẫn đáng làm, để worker không kéo database dừng theo. Xem giới hạn memory trong Docker Compose.

File upload là phần tăng dung lượng mà không có giới hạn nếu bạn không tự đặt. Mỗi screenshot do khách hàng đính kèm đều được lưu vào storage volume và tồn tại ở đó, vì vậy hãy monitor docker system df -v thay vì mặc định cho rằng database là phần đã làm đầy disk.

Lấy file compose và cố định tag phiên bản

mkdir -p ~/chatwoot && cd ~/chatwoot
wget -O .env https://raw.githubusercontent.com/chatwoot/chatwoot/develop/.env.example
wget -O docker-compose.yaml https://raw.githubusercontent.com/chatwoot/chatwoot/develop/docker-compose.production.yaml
chmod 600 .env

File bạn vừa tải xuống đang ghi image: chatwoot/chatwoot:latest. Hãy thay đổi giá trị này trước khi làm bất kỳ việc gì khác.

services:
  base: &base
    image: chatwoot/chatwoot:v4.16.2
    env_file: .env
    volumes:
      - storage_data:/app/storage

latest nghĩa là lần chạy docker compose pull tiếp theo sẽ lấy bất cứ phiên bản nào được phát hành vào thời điểm đó. Phiên bản đó có thể là major version kèm các migration mà bạn chưa từng đọc. Trên thực tế, migration của Chatwoot không thể hoàn tác, nên việc nâng phiên bản ngoài ý muốn sẽ buộc bạn phải restore từ backup thay vì undo. Hãy cố định tag và chỉ thay đổi tag một cách có chủ đích. v4.16.2 là bản phát hành hiện tại vào tháng 8 năm 2026; xem trang releases để biết tag cần cố định hôm nay.

Service base là một YAML anchor được cả railssidekiq merge vào. Vì vậy, thay đổi tag ở một nơi sẽ áp dụng cho cả hai service. Trong khi mở file, hãy xóa dòng version: '3' ở đầu file. Compose hiện đại bỏ qua dòng này và in the attribute 'version' is obsolete, it will be ignored trong mỗi lệnh.

Điền file .env

Trước tiên, hãy tạo secret. Upstream yêu cầu giá trị chỉ gồm chữ và số, vì ký tự đặc biệt có thể bị biến dạng khi giá trị đi qua shell hoặc YAML parser.

head /dev/urandom | tr -dc A-Za-z0-9 | head -c 63 ; echo ''

Sau đó đặt các key này trong .env.

SECRET_KEY_BASE=<the 63 characters you just generated>
FRONTEND_URL=https://support.example.com
FORCE_SSL=true
DEFAULT_LOCALE=en
ENABLE_ACCOUNT_SIGNUP=true

POSTGRES_HOST=postgres
POSTGRES_USERNAME=postgres
POSTGRES_PASSWORD=<long random string>
POSTGRES_DATABASE=chatwoot

REDIS_URL=redis://redis:6379
REDIS_PASSWORD=<a different long random string>

RAILS_ENV=production
INSTALLATION_ENV=docker
ACTIVE_STORAGE_SERVICE=local

POSTGRES_HOST=postgresredis://redis:6379 là tên Compose service, được phân giải trên network mặc định của project. FRONTEND_URL không phải thông tin trang trí. Chatwoot dùng nó để tạo URL của widget script và mọi liên kết trong email gửi đi. Vì vậy, giá trị sai sẽ tạo ra các liên kết đặt lại mật khẩu trỏ đến một host không phản hồi.

Đây là bẫy trong file upstream. Service postgres không đọc .env. Nó có block environment riêng, trong đó POSTGRES_PASSWORD= để trống. Vì vậy, chỉ đặt password trong .env sẽ khiến database không có password, còn application lại có password. Hãy trỏ service này đến cùng một biến:

  postgres:
    image: pgvector/pgvector:pg16
    restart: always
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=chatwoot
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}

Compose đọc .env từ thư mục project để thực hiện việc thay thế ${...}. Vì vậy, cả hai phía sẽ nhận cùng một chuỗi. Nếu cấu hình sai, Rails sẽ dừng với PG::ConnectionBad: FATAL: password authentication failed for user "postgres".

Một hành vi khiến gần như mọi người bất ngờ: Postgres image chỉ áp dụng POSTGRES_PASSWORD khi khởi tạo một data directory trống. Thay đổi giá trị sau đó không có tác dụng, vì initdb không chạy lần thứ hai. Nếu bạn đã khởi động stack một lần, hãy thay đổi password trực tiếp trong database.

docker compose exec postgres psql -U postgres -c "ALTER USER postgres WITH PASSWORD 'the-new-password';"

ENABLE_ACCOUNT_SIGNUP=true chỉ dùng tạm thời. Nó mở form đăng ký công khai để bạn tạo account đầu tiên. Hãy đặt nó thành false và chạy lại docker compose up -d ngay sau khi account của bạn được tạo. Nếu không, bất kỳ ai tìm thấy URL đều có thể đăng ký trên support desk của bạn. Sau đó, agent sẽ được thêm bằng invitation và password của họ chỉ được lưu trong app này. Cách này vẫn ổn cho đến khi bạn chạy khoảng nửa tá service và không muốn duy trì một danh sách account riêng cho từng service. Khi đó, một identity provider tự host như Authentik là thành phần thay thế các danh sách đó.

.env hiện chứa mọi secret của stack này ở dạng plain text. Hãy đặt permission của file ở mode 600 và không đưa file vào git. Cách Compose đọc env file và nơi secret có thể bị lộ trình bày các điểm cần chú ý, trong đó có sự khác nhau giữa env_fileenvironment.

Đặt Chatwoot phía sau Traefik hiện có

Không dựng thêm một reverse proxy cho một ứng dụng. Nếu Traefik đã thực hiện TLS termination (kết thúc lớp bảo mật truyền tải) cho các container khác trên máy này, Chatwoot chỉ cần tham gia bằng một block label. Nếu bạn chưa có cấu hình đó, hãy thiết lập một lần theo Traefik phía trước nhiều ứng dụng Docker Compose, rồi quay lại đây.

Giữ docker-compose.yaml của upstream gần với bản gốc để sau này có thể diff với bản mới hơn, và đặt các thay đổi của bạn trong một file override. Compose tự động merge docker-compose.override.yaml, còn tách Compose thành nhiều file giải thích các quy tắc merge.

services:
  rails:
    networks:
      - default
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.chatwoot.rule=Host(`support.example.com`)"
      - "traefik.http.routers.chatwoot.entrypoints=websecure"
      - "traefik.http.routers.chatwoot.tls.certresolver=letsencrypt"
      - "traefik.http.services.chatwoot.loadbalancer.server.port=3000"

networks:
  proxy:
    external: true

Dùng tên entrypoint và certresolver của riêng bạn. Container phải nằm trên cùng Docker network với Traefik; đó là tác dụng của entry proxy. Container cũng phải tiếp tục nằm trên default, nếu không nó sẽ mất kết nối đến Postgres và Redis. Đây là dòng mà nhiều người quên nhất.

Giữ nguyên block ports:. Upstream bind block này vào 127.0.0.1:3000, chỉ cho loopback truy cập, nên nó không thể truy cập từ Internet và vẫn hữu ích để kiểm thử từ bên trong máy bằng curl -I http://127.0.0.1:3000.

Dashboard của agent duy trì một websocket đến /cable để nhận message theo thời gian thực. Traefik chuyển tiếp HTTP upgrade mà không cần cấu hình thêm, nên không cần thêm gì. Nếu sau này bạn đặt một CDN hoặc proxy khác phía trước Traefik, hãy cho phép websocket ở đó. Dấu hiệu thường gặp là dashboard tải bình thường nhưng message mới chỉ xuất hiện sau khi refresh thủ công.

Khởi tạo database và khởi động stack

Khởi động các service dữ liệu trước và chờ Postgres hoàn tất lần chạy đầu tiên.

docker compose up -d postgres redis
docker compose logs postgres | tail -n 5

Chờ database system is ready to accept connections. Sau đó tạo schema.

docker compose run --rm rails bundle exec rails db:chatwoot_prepare

Lệnh này tạo database nếu database chưa tồn tại, rồi nạp schema và dữ liệu seed mặc định. Lệnh in các dòng migration và thoát sạch. Nếu lệnh cứ in postgres:5432 - no response, entrypoint đang chờ một database chưa chấp nhận kết nối. Trong lần chạy đầu tiên, nguyên nhân thường là initdb vẫn đang xử lý. Hãy chờ, đọc log của Postgres, rồi chạy lại. Nếu lệnh dừng tại extension vector, bạn đã thay image pgvector bằng Postgres chuẩn.

docker compose up -d
docker compose ps
docker compose logs --tail 30 rails

Cả bốn container phải hiển thị Up, và log của rails phải kết thúc bằng một dòng Puma đang listening trên http://0.0.0.0:3000. Sau đó kiểm tra đường dẫn public:

curl -sI https://support.example.com | head -n 1

HTTP/2 200 có nghĩa là toàn bộ chuỗi hoạt động đúng. Lỗi 404 từ Traefik nghĩa là router rule không khớp, thường do hostname bị gõ sai. Lỗi 502 nghĩa là Traefik đã khớp router nhưng không thể kết nối đến container. Nguyên nhân gần như luôn là thiếu network proxy hoặc loadbalancer.server.port không phải 3000.

Mở URL, tạo account tại /app/auth/signup, sau đó đặt ENABLE_ACCOUNT_SIGNUP=false và chạy docker compose up -d để đóng form.

Vì sao reset mật khẩu và trao đổi email thất bại khi không có SMTP

Chatwoot không có cấu hình SMTP (simple mail transfer protocol) là một hệ thống hỗ trợ không thể gửi email, và điều đó làm hỏng nhiều thứ hơn cả thông báo. Tính năng reset mật khẩu ngừng hoạt động, nên admin bị khóa tài khoản sẽ tiếp tục bị khóa. Lời mời agent ngừng hoạt động vì lời mời được gửi qua email. Trả lời khách hàng trong cuộc hội thoại email cũng ngừng hoạt động, nên cuộc hội thoại chỉ chạy một chiều. Đây là bước nhiều người bỏ qua, rồi phát hiện ra khi đang gặp sự cố nghiêm trọng nhất.

Cơ chế này rất rõ ràng. Khi không có cấu hình SMTP, ActionMailer giữ mặc định gửi đến localhost trên port 25. Bên trong Rails container không có mail server, nên job gửi mail phát sinh Errno::ECONNREFUSED: Connection refused - connect(2) for "localhost" port 25. Mail được gửi từ background job, vì vậy dòng log đó xuất hiện trong Sidekiq log chứ không có trong Rails log. Trong khi đó, người bấm "quên mật khẩu" vẫn thấy thông báo xác nhận, nhưng không nhận được email nào.

MAILER_SENDER_EMAIL=Support <support@example.com>
SMTP_DOMAIN=example.com
SMTP_ADDRESS=smtp.example.com
SMTP_PORT=587
SMTP_USERNAME=support@example.com
SMTP_PASSWORD=<the relay password>
SMTP_AUTHENTICATION=plain
SMTP_ENABLE_STARTTLS_AUTO=true

Dùng port 587 với STARTTLS. Cơ chế này mở kết nối dưới dạng plain text rồi nâng cấp lên kết nối được mã hóa trước khi xác thực. Hầu hết nhà cung cấp VPS chặn port 25 outbound để hạn chế spam, nên relay trên port 587 thường là lựa chọn duy nhất có thể kết nối. SMTP_DOMAIN là domain mà server của bạn thông báo trong quá trình trao đổi SMTP, và một số relay sẽ từ chối nếu giá trị này không khớp.

Áp dụng cấu hình rồi theo dõi worker:

docker compose up -d rails sidekiq
docker compose logs -f sidekiq

Thực hiện reset mật khẩu từ trang đăng nhập. Khi gửi mail hoạt động bình thường, job mailer sẽ hoàn tất bình thường trong Sidekiq log. Khi gửi thất bại, log sẽ hiển thị exception class, sau đó Sidekiq retry với thời gian chờ tăng dần. Vì vậy, một relay bị lỗi có thể tạo ra cùng một lỗi sau mỗi vài phút trong nhiều giờ.

Có hai kiểu từ chối thường gặp, và cả hai đều không phải lỗi của Chatwoot. 535 Authentication failed nghĩa là username hoặc password không đúng với relay đó; nhiều nhà cung cấp yêu cầu application password thay vì password tài khoản. 550 Sender address rejected nghĩa là MAILER_SENDER_EMAIL là địa chỉ mà relay không cho phép dùng làm địa chỉ gửi, nên địa chỉ này phải là mailbox hoặc domain bạn đã xác minh với họ.

Nhận email vào một cuộc hội thoại là một tác vụ riêng. Tác vụ này cần MAILER_INBOUND_EMAIL_DOMAINRAILS_INBOUND_EMAIL_SERVICE, cùng với một mail server chuyển các message đến cho Chatwoot. Thuê một relay là cách triển khai nhanh nhất. Nếu muốn tự quản lý toàn bộ mail path, tự chạy mail server bằng Mailcow sẽ giải thích những gì bạn thực sự phải đảm nhận.

Cần sao lưu những gì và cách xác minh bản khôi phục hoạt động

Một bản sao lưu Chatwoot gồm 4 phần. Bỏ qua bất kỳ phần nào sẽ biến việc khôi phục thành dựng lại hệ thống.

  • Volume storage_data, vì ACTIVE_STORAGE_SERVICE=local ghi các file đã tải lên vào disk và chỉ lưu một bản ghi tham chiếu trong Postgres.
  • Database Postgres, nơi lưu các cuộc hội thoại, contact, tài khoản agent và mọi setting.
  • File .env, vì file này chứa SECRET_KEY_BASE và các key ACTIVE_RECORD_ENCRYPTION_*.
  • Các file compose, vì chúng ghi lại image tag chính xác tương ứng với schema database.

Nếu chỉ khôi phục database, mọi cuộc hội thoại sẽ trở lại nhưng attachment bị hỏng, vì các row trỏ đến những file không còn trên disk.

cd ~/chatwoot
docker compose exec -T postgres pg_dump -U postgres -Fc chatwoot > db-$(date +%F).dump

-T rất quan trọng. Nếu thiếu tùy chọn này, Compose sẽ cấp phát pseudo terminal. Pseudo terminal thay đổi các byte newline trong stream, khiến bạn nhận được file dump bị pg_restore từ chối. -Fc là custom format. Format này nén dữ liệu và cho phép pg_restore khôi phục có chọn lọc.

docker run --rm -v chatwoot_storage_data:/data:ro -v "$PWD":/backup alpine \
  tar czf /backup/storage-$(date +%F).tgz -C /data .

Tên volume là tên thư mục project cộng với _storage_data. Hãy xác nhận tên này bằng docker volume ls | grep storage_data trước khi tin dùng command đó, vì Docker sẽ tạo một volume rỗng thay vì báo lỗi khi bạn chỉ định volume không tồn tại. Bạn sẽ nhận được một archive hợp lệ nhưng rỗng, hoàn toàn không có lỗi. Sau đó kiểm tra kích thước bằng ls -lh storage-*.tgz.

Cả hai file hiện nằm trên cùng disk với dữ liệu mà chúng bảo vệ, nên thực tế chúng không bảo vệ bạn khỏi sự cố nào. Hãy đẩy chúng ra khỏi máy chủ và mã hóa chúng, vì database dump chứa toàn bộ tin nhắn của khách hàng ở dạng plain text. Sao lưu off-site đã mã hóa bằng restic trình bày phần lập lịch và chính sách retention.

Quy trình khôi phục, hãy chạy trước khi cần

Khôi phục lên một VPS thứ hai, không khôi phục lên VPS đang chạy production. Copy .env, các file compose và cả hai archive sang đó, rồi chạy:

docker compose up -d postgres
docker compose exec -T postgres pg_restore -U postgres -d chatwoot --clean --if-exists < db-2026-08-10.dump
docker run --rm -v chatwoot_storage_data:/data -v "$PWD":/backup alpine \
  sh -c 'rm -rf /data/* && tar xzf /backup/storage-2026-08-10.tgz -C /data'
docker compose up -d

--clean --if-exists xóa các object hiện có trước khi nạp dữ liệu. Vì vậy, chỉ trỏ command này đến database mà bạn chấp nhận mất dữ liệu. Sau đó đăng nhập và mở một cuộc hội thoại có attachment. Nếu danh sách tin nhắn tải được và file tải xuống được, bản sao lưu hoạt động.

Khôi phục với SECRET_KEY_BASE khác sẽ vô hiệu hóa mọi session cookie, khiến tất cả người dùng bị đăng xuất. Khôi phục với các key ACTIVE_RECORD_ENCRYPTION_* khác còn nghiêm trọng hơn: Chatwoot không thể giải mã các column chứa thông tin xác thực của channel và phát sinh ActiveRecord::Encryption::Errors::Decryption. Đó là lý do .env nằm trong danh sách sao lưu.

Cách nâng cấp Chatwoot lên tag mới

Thứ tự thực hiện quan trọng hơn bản thân các lệnh.

  1. Đọc release notes giữa tag hiện tại và tag đích để tìm các bước thủ công bắt buộc.
  2. Tạo database dump và storage archive mới, rồi kiểm tra kích thước của cả hai file có hợp lý không.
  3. Sửa image tag của service base trong docker-compose.yaml.
  4. Pull image mới, dừng stack, chạy migration rồi khởi động lại.
docker compose pull
docker compose down
docker compose run --rm rails bundle exec rails db:chatwoot_prepare
docker compose up -d
docker compose images

Hãy pull image trước khi migrate, vì migration phải chạy từ image mới. Image cũ không chứa các migration file mới. Hãy dừng stack trước khi migrate, vì code cũ và schema mới không tương thích. Một Rails process cũ đang chạy có thể phát sinh lỗi hoặc ghi các row mà schema mới không chấp nhận. Việc dừng stack cũng giải phóng lượng memory mà migration cần. Đây là lý do upstream yêu cầu có swap.

docker compose images in ra tag mà mỗi container thực sự đang chạy. Lệnh này giúp phát hiện trường hợp bạn đã sửa tag nhưng quên pull image.

Không nên bỏ qua nhiều version cùng lúc. Với một install cũ, upstream khuyên nên đi qua các tag trung gian, vì migration sẽ bị xóa sau khi được hợp nhất vào base schema. Do đó, một database quá cũ có thể rơi vào trạng thái không còn đường nâng cấp tiếp. Hãy nâng từng minor version một và chạy bước prepare sau mỗi lần nâng cấp.

Nếu Rails khởi động trước khi migration chạy, nó sẽ từ chối phục vụ và ghi ActiveRecord::PendingMigrationError: Migrations are pending vào log. Khi đã đặt restart: always, container sẽ khởi động lại liên tục, nên docker compose ps hiển thị uptime bị reset sau vài giây. Hãy chạy bước prepare để xử lý lỗi này.

Rollback nghĩa là đặt lại tag cũ và khôi phục dump. Không có reverse migration path đáng tin cậy, nên bước 2 là cần thiết.

Các dạng lỗi và chuỗi bạn sẽ thấy

502 Bad Gateway từ Traefik. Router đã khớp nhưng backend không phản hồi. Kiểm tra docker compose ps để xác nhận rails hiển thị là Up, sau đó chạy docker network inspect proxy và xác nhận container rails xuất hiện trong danh sách container. Container không được attach sẽ vô hình đối với Traefik, vì vậy request khớp với router rồi không đi đến đâu.

Dashboard tải được nhưng phải refresh mới thấy message mới. Websocket đến /cable không đi qua được, hoặc FRONTEND_URL không khớp với địa chỉ trên thanh địa chỉ của trình duyệt. Khi không khớp, trang sẽ cố mở websocket đến một origin khác và trình duyệt sẽ chặn kết nối.

FATAL: password authentication failed for user "postgres". Password trong .env khác với password đã được ghi vào Postgres data volume. Sửa bằng ALTER USER bên trong container đang chạy, vì chỉnh lại .env sẽ không thay đổi database đã được khởi tạo.

NOAUTH Authentication required. Redis đang chạy với --requirepass nhưng application kết nối mà không có password, nên REDIS_PASSWORD bị thiếu trong .env hoặc không được nạp. Kiểm tra trực tiếp bằng docker compose exec redis redis-cli -a "$REDIS_PASSWORD" ping; lệnh này phải trả về PONG.

Container thoát với code 137. Đây là SIGKILL. Trên máy có ít tài nguyên, nguyên nhân là OOM killer của kernel. Thêm swap, đặt memory limit cho từng service hoặc chuyển sang plan lớn hơn.

FAQ

VPS Chatwoot tự host cần bao nhiêu RAM?

Tính đến tháng 8 năm 2026, upstream yêu cầu tối thiểu 4 GB RAM và 4 CPU core, đáp ứng tối đa 10,000 cuộc hội thoại mỗi ngày; với tối đa 20,000 cuộc hội thoại, cần 8 GB RAM và 8 core. Hãy thêm ít nhất 1 GB swap, vì quá trình upgrade chạy thêm một Rails process để áp dụng migration, và các máy cấu hình nhỏ thường hết memory ở bước này. VPS 2 GB vẫn boot và hoạt động với vài agent, nhưng riêng Sidekiq có thể vượt 1 GB khi có tải. Vì vậy, trong thời gian cao điểm và lúc upgrade, hãy chuẩn bị cho việc container bị kill với exit code 137.

Vì sao email reset password của Chatwoot không bao giờ đến?

Vì chưa cấu hình SMTP nên ActionMailer cố gửi thư đến localhost trên port 25, nhưng bên trong container không có mail server. Job bị fail trong Sidekiq với Errno::ECONNREFUSED: Connection refused - connect(2) for "localhost" port 25, trong khi trình duyệt vẫn hiển thị thông báo thành công. Đặt SMTP_ADDRESS, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORDMAILER_SENDER_EMAIL trong .env, restart các service rails và sidekiq, sau đó monitor docker compose logs -f sidekiq trong khi bạn trigger thao tác reset.

Cần backup những gì để restore Chatwoot?

Cơ sở dữ liệu Postgres, Docker volume storage_data, file .env và các compose file. Chỉ backup database là không đủ, vì file upload nằm trong volume còn Postgres chỉ lưu reference đến chúng. Do đó, restore chỉ database sẽ tạo các cuộc hội thoại có attachment bị hỏng. .env cũng quan trọng vì một SECRET_KEY_BASE khác sẽ đăng xuất tất cả user, còn các key ACTIVE_RECORD_ENCRYPTION_* khác khiến những column được mã hóa không thể đọc được.

Làm thế nào để upgrade Chatwoot mà không làm hỏng database?

Hãy tạo backup, đổi image tag trong compose file, rồi chạy docker compose pull, docker compose down, docker compose run --rm rails bundle exec rails db:chatwoot_preparedocker compose up -d. Hãy pull trước vì migration phải chạy từ image mới, đồng thời stop stack trước vì code cũ chạy trên schema mới sẽ gây lỗi. Với installation cũ, hãy nâng từng minor version một, vì migration sẽ bị xóa sau khi được gộp vào base schema.

Có thể dùng image postgres chuẩn thay cho pgvector không?

Không. Schema của Chatwoot bật extension vector, nên image postgres chuẩn sẽ fail trong quá trình db:chatwoot_prepare với lỗi ERROR: extension "vector" is not available, vì image đó không chứa control file của extension. Hãy giữ pgvector/pgvector:pg16 từ upstream compose file, hoặc dùng image khác có pgvector cho major version Postgres của bạn.

#chatwoot#tự lưu trữ#docker-compose#support-desk#smtp#sao lưu