SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-08-22

Cài Discourse trên VPS bằng Docker

Hướng dẫn cài Discourse bằng Docker launcher chính thức: kiểm tra RAM và swap, trỏ domain, cấu hình SMTP trong app.yml, rebuild, TLS và reverse proxy.

Cài đặt Discourse trên VPS: một container, một file cấu hình

Để cài đặt Discourse trên VPS, bạn chạy installer do dự án cung cấp, trả lời một wizard ngắn rồi chờ quá trình build hoàn tất. Discourse được phân phối dưới dạng một Docker container duy nhất, chứa ứng dụng Rails, PostgreSQL, Redis và nginx. Mọi cấu hình bạn thay đổi sau này đều nằm trong một file, /var/discourse/containers/app.yml, và mọi thay đổi đều được áp dụng cho site thông qua một lần rebuild.

Cách cài đặt chính thức là discourse_docker: một shell script launcher cùng một bộ template YAML. Discourse không hỗ trợ file Compose do bạn tự viết, và container này không được thiết kế để bạn tự tách thành các phần riêng. Nếu bạn đã quen với việc chạy các service trên VPS bằng Docker Compose, mô hình này sẽ khác. Ở đây không có docker compose up -d, và ./launcher rebuild app chính là quy trình deploy.

Những gì Discourse cần trước khi bắt đầu

Có 4 yêu cầu thường bị bỏ sót, và mỗi yêu cầu đều có thể gây lỗi trước khi bạn đến được trang đăng nhập.

  • Bộ nhớ. Một container chạy PostgreSQL, Redis, Sidekiq và Ruby web server. Bước build biên dịch các asset và cần nhiều bộ nhớ hơn site đang hoạt động.
  • Tên miền thực. Cấu hình mẫu đi kèm ghi rõ: "Discourse sẽ không hoạt động với một địa chỉ IP đơn thuần."
  • Đường gửi mail ra ngoài. Kích hoạt tài khoản, đặt lại mật khẩu, lời mời admin và mail digest đều được gửi qua SMTP (simple mail transfer protocol).
  • Cổng 80 và 443 phải trống trên host, trừ khi bạn chủ động đặt Discourse phía sau một proxy đang chạy sẵn.
ChartDiscourse published hardware requirements (official install docs, August 2026)
The data behind this chart
[
  {
    "label": "Documented minimum",
    "ram_gb": 1,
    "storage_gb": 10
  },
  {
    "label": "Documented recommended",
    "ram_gb": 2,
    "storage_gb": 20
  }
]

Tài liệu cài đặt chính thức đặt mức tối thiểu là 1 GB RAM có swap và 10 GB disk, đồng thời khuyến nghị 2 GB RAM với 20 GB disk. Hãy hiểu dòng đầu tiên là mức giúp installer hoàn tất, không phải mức bạn muốn dùng để vận hành một community. Khoảng chênh lệch này quan trọng vì mức sử dụng bộ nhớ cao nhất xuất hiện trong quá trình build, không phải khi xử lý traffic.

Trỏ domain về server trước khi cài đặt

Tạo một A record cho hostname bạn sẽ dùng, sau đó xác nhận từ chính server.

dig +short forum.example.com
curl -4 -s https://ifconfig.co

Cả hai lệnh phải in ra cùng một địa chỉ. Chúng phải khớp vì trình hướng dẫn cài đặt sẽ kiểm tra kết nối đến hostname của bạn. Record vẫn trỏ đến nơi khác sẽ làm bài kiểm tra này thất bại. Record bạn tạo cách đây 2 phút cũng có thể vẫn còn nằm trong cache. Hãy chờ TTL (time to live) cũ hết hạn thay vì cố xử lý lỗi trong trình hướng dẫn.

Quyết định ngay record này có được CDN proxy hay không. Record được proxy sẽ ẩn địa chỉ server. Khi đó yêu cầu cấp certificate của container sẽ thất bại vì proxy trả lời ACME (automatic certificate management environment) challenge thay cho Discourse. Hãy để record ở trạng thái không proxy trong lần cài đặt đầu tiên.

Chạy installer chính thức

Một command sẽ cài git, cài Docker bằng install script của Docker, clone discourse_docker vào /var/discourse và khởi động setup wizard.

wget -qO- https://raw.githubusercontent.com/discourse/discourse_docker/main/install-discourse | sudo bash

Nếu Docker đã có trên máy và bạn muốn xem từng bước, hãy thực hiện các bước này thủ công.

sudo -s
git clone https://github.com/discourse/discourse_docker.git /var/discourse
cd /var/discourse
./discourse-setup

Chạy bằng root. Nếu chạy bằng user thông thường, discourse-setup sẽ dừng ngay với This script must be run as root. Please sudo or log in as root first.. Nếu máy chưa có Docker, lệnh sẽ dừng với Docker is not installed. Please install Docker first. vì thao tác clone thủ công không tự cài Docker cho bạn.

Các câu hỏi của wizard và những gì nó ghi

Tính đến tháng 8 năm 2026, discourse-setup chỉ là một lớp wrapper mỏng. Nó chạy discourse/setup-wizard:release dưới dạng container, dùng network của host và mount Docker socket, để wizard có thể kiểm tra máy đang được cấu hình. Wizard hỏi hostname và các địa chỉ email admin, sau đó hỏi phần cấu hình SMTP. Nó ghi containers/app.yml rồi build lại.

Có 2 hành vi bạn nên biết trước khi bắt đầu. Nếu máy thiếu memory và không có swap, wizard sẽ dừng và đề nghị tạo swap. Khi đó wrapper tạo một /swapfile 2 GB, thêm nó vào /etc/fstab, đặt vm.swappiness = 10 trong /etc/sysctl.d/30-discourse-swap.conf rồi chạy lại wizard. Khi wizard hoàn tất, nó in Rebuilding app in 5 seconds (Ctrl+C to cancel)... và chạy ./launcher rebuild app trên host. Quá trình build này mất vài phút trên một VPS nhỏ. Lần build đầu tiên lâu nhất vì mọi asset đều được compile từ đầu.

./discourse-setup --help liệt kê các flag cần biết khi có lỗi. --skip-rebuild ghi config nhưng không build, còn --skip-connection-test bỏ qua các bước kiểm tra DNS và port. Chỉ dùng --skip-connection-test khi bạn đã biết nguyên nhân test thất bại, chẳng hạn host nằm sau một network firewall do bạn quản lý.

Đọc app.yml trước lần rebuild đầu tiên

Wizard ghi một file mà từ giờ bạn phải tự quản lý. Mở file bằng sudo nano /var/discourse/containers/app.yml. Đây là những phần quyết định gần như mọi thứ.

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
  ## Uncomment these two lines if you wish to add Lets Encrypt (https)
  #- "templates/web.ssl.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"

expose:
  - "80:80"   # http
  - "443:443" # https

env:
  DISCOURSE_HOSTNAME: "forum.example.com"
  DISCOURSE_DEVELOPER_EMAILS: "you@example.com"
  DISCOURSE_SMTP_ADDRESS: smtp.example.com
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: user@example.com
  DISCOURSE_SMTP_PASSWORD: "your-smtp-password"

DISCOURSE_HOSTNAME là địa chỉ mà site phản hồi, và Discourse dùng địa chỉ này để tạo các link. Vì vậy, giá trị sai có thể khiến site tải được một lần rồi chuyển bạn sang một địa chỉ khác. DISCOURSE_DEVELOPER_EMAILS là danh sách phân tách bằng dấu phẩy. Các địa chỉ trong danh sách này sẽ tự động có quyền admin khi đăng ký lần đầu. Hãy điền địa chỉ của bạn vào đó và đăng ký bằng địa chỉ này, vì đây là cách tạo tài khoản admin đầu tiên.

File lưu SMTP password dưới dạng plain text, vì vậy hãy giới hạn quyền truy cập vào thư mục bằng sudo chmod 700 /var/discourse/containers. File này cũng là YAML, nên whitespace được dùng làm cấu hình. Một key bị thụt lề sai sẽ khiến build fail với lỗi parse và bạn sẽ không có site. Có một lỗi dễ gặp được ghi ngay trong file mẫu. # trong password không đặt trong dấu nháy sẽ bắt đầu một comment, vì vậy hãy đặt trong dấu nháy mọi password có chứa ký tự này.

Email là bước khiến phần lớn quá trình cài đặt dừng lại

Tính đến tháng 8 năm 2026, wizard cho phép bỏ qua SMTP và dùng đăng nhập Discourse ID thay thế. app.yml cũng có tùy chọn DISCOURSE_SKIP_EMAIL_SETUP tương ứng, được mô tả tại đó là bỏ qua việc xác thực thiết lập email. Bỏ qua là lựa chọn hợp lý để xem nhanh phần mềm. Nhưng đây là lựa chọn không phù hợp cho một community, vì khi không gửi được email ra ngoài thì không ai có thể kích hoạt tài khoản hoặc đặt lại mật khẩu.

Vấn đề thực tế là hầu hết nhà cung cấp VPS chặn port outbound 25, nên mail server chạy trực tiếp trên máy sẽ không gửi được mail. Hãy dùng authenticated relay trên port 587, hoặc port 465 với TLS ngầm (transport layer security). Với port 465, hãy đặt DISCOURSE_SMTP_FORCE_TLS: true; sample config khuyến nghị tùy chọn này cho port đó. Hãy kiểm tra khả năng kết nối từ host trước khi build lại.

nc -vz smtp.example.com 587

Kết quả đúng là một dòng duy nhất kết thúc bằng succeeded!. Nếu command bị treo rồi timeout, điều đó có nghĩa là port bị chặn trên đường đi ra khỏi VPS của bạn và không có thiết lập Discourse nào khắc phục được. Hãy chuyển sang port mà nhà cung cấp cho phép hoặc yêu cầu nhà cung cấp mở port đó.

Sau khi site hoạt động, hãy gửi một message test từ trang Email trong Admin, rồi đọc các tab Skipped và Bounced trên cùng trang đó. Discourse ghi lại trong các tab này những mail mà nó từ chối gửi và những mail bị relay từ chối. Các tab này cũng nêu nguyên nhân, nên kiểm tra sẽ nhanh hơn đọc log.

TLS: để container tự cấp certificate

Nếu Discourse sở hữu cổng 80 và 443, hãy dùng cơ chế cấp certificate tích hợp sẵn của Discourse. Bỏ comment 2 dòng SSL template được hiển thị ở trên, sau đó rebuild. Template này điều khiển acme.sh, lưu certificate trong shared volume tại /shared/ssl, tự gia hạn theo lịch bên trong container và cấu hình Discourse bắt buộc dùng HTTPS.

Cổng 80 phải tiếp tục truy cập được từ Internet để cơ chế này hoạt động, vì HTTP challenge được xử lý tại đó. Firewall chỉ cho phép cổng 443 sẽ khiến quá trình build hoàn tất nhưng certificate không bao giờ được cấp. Kiểm tra kết quả ngay sau khi rebuild bằng ./launcher logs app.

Bạn có nên đặt nginx hoặc Caddy phía trước không?

Nếu Discourse là web service duy nhất trên VPS, không nên. Container đã chạy nginx được tối ưu sẵn. Một proxy thứ hai sẽ thêm một hop, thêm một certificate cần gia hạn và thêm một nguồn gây lỗi header.

Hãy đặt proxy phía trước khi cùng VPS phục vụ các site khác. Thêm templates/web.socketed.template.yml vào danh sách template, comment cả hai dòng expose và giữ nguyên 2 template SSL ở trạng thái comment. Khi đó container sẽ listen trên unix socket tại /var/discourse/shared/standalone/nginx.http.sock và không giữ port nào, giải phóng port 80 và 443 cho proxy của bạn.

server {
  listen 443 ssl;
  server_name forum.example.com;

  location / {
    proxy_pass http://unix:/var/discourse/shared/standalone/nginx.http.sock:;
    proxy_set_header Host $http_host;
    proxy_http_version 1.1;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;
  }
}

Dấu hai chấm ở cuối .sock là một phần của cú pháp unix socket của nginx. sudo nginx -t sẽ từ chối config nếu thiếu dấu này. X-Forwarded-Proto cũng không phải tùy chọn. Discourse ghi link tuyệt đối, nên nếu thiếu header đó, nó sẽ tạo link http:// trên trang HTTPS. Trình duyệt sẽ chặn các link này vì mixed content. Khi container dùng socket, việc xử lý TLS thuộc về bạn. Vì vậy, hãy issue certificate trên host bằng Certbot trên Ubuntu 24.04 và nginx. Nếu bạn chưa chọn proxy, bài so sánh nginx, Caddy và Traefik sẽ trình bày các đánh đổi của từng lựa chọn.

Build lại, nâng cấp và các lệnh bạn sẽ thực sự dùng

cd /var/discourse
./launcher rebuild app

rebuild xóa container đang chạy, tạo container mới từ app.yml rồi khởi động container đó. Website offline trong toàn bộ thời gian build, vì vậy hãy xem mọi thay đổi cấu hình là một lần downtime theo lịch kéo dài vài phút.

Nếu chỉ thay đổi các giá trị bên dưới env: thì không cần làm vậy. ./launcher destroy app && ./launcher start app tạo lại container từ image đã build trước đó, chỉ mất vài giây. Bất kỳ thay đổi nào bên dưới templates: hoặc hooks: đều thay đổi chính image, nên cần rebuild đầy đủ.

Có 2 cách nhận upgrade. Point release được áp dụng từ web interface tại /admin/upgrade, do plugin docker_manager cung cấp; plugin này được app.yml clone trong quá trình build. Các thay đổi đối với base image hoặc template đến từ git.

cd /var/discourse
git pull
./launcher rebuild app

Build lại là lúc các server nhỏ dễ gặp lỗi, vì biên dịch asset là thời điểm hệ thống dùng nhiều memory nhất. Nếu build dừng giữa chừng và dmesg hiển thị một dòng như Out of memory: Killed process, trong đó nêu một process ruby, thì hệ thống đã hết memory trong lúc build dù website trước đó vẫn chạy bình thường. Hãy thêm swap rồi chạy build lại.

./launcher logs app
./launcher enter app
./launcher cleanup

logs in output của container, enter mở một shell bên trong container, còn cleanup xóa các container đã dừng hơn 24 giờ. Thỉnh thoảng hãy chạy cleanup, vì mỗi lần rebuild đều để lại một container cũ và disk trên VPS nhỏ có thể âm thầm hết chỗ.

Bản backup và file không nằm trong backup

Tạo backup từ trang Backups trong Admin. Archive được ghi trên host tại /var/discourse/shared/standalone/backups/default/. Bạn cũng có thể chạy cùng job từ shell.

cd /var/discourse
./launcher enter app
discourse backup

discourse restore <filename> đảo ngược thao tác đó, và hệ thống sẽ từ chối restore cho đến khi bạn chạy discourse enable_restore. Cơ chế bảo vệ này ngăn một command chạy nhầm ghi đè lên forum đang hoạt động.

Bạn phải tự xử lý 2 điểm còn thiếu. Archive chứa database và chỉ chứa các file đã upload khi bật setting bao gồm upload. Vì vậy, hãy kiểm tra setting đó trước khi tin rằng backup đã đầy đủ. Archive không bao giờ chứa app.yml. Do đó, khi restore lên một VPS mới, bạn vẫn cần hostname và SMTP block. Bạn cũng phải copy file đó ra khỏi host.

Archive cũng nằm trên cùng disk với site mà nó bảo vệ. Đây không phải là backup đúng nghĩa. Hãy định kỳ kéo archive sang một nơi khác.

rsync -avz root@forum.example.com:/var/discourse/shared/standalone/backups/default/ ~/discourse-backups/

Chi phí RAM của một forum hoạt động nhiều

Bootstrap thiết lập UNICORN_WORKERS và db_shared_buffers dựa trên lượng memory và CPU mà nó phát hiện, còn config mẫu giới hạn shared buffers ở mức một phần tư tổng memory. Mỗi worker của unicorn là một tiến trình Ruby đầy đủ, còn Sidekiq chạy các background job bên cạnh chúng. Vì vậy, mức sử dụng memory phụ thuộc vào số request đồng thời, không phải số member đã đăng ký. Một forum yên ắng với vài trăm member không phải workload nặng. Điều thường quan trọng hơn là những gì khác đang dùng chung máy chủ, và nếu đó là một photo library, các mức RAM tối thiểu đã đo trong so sánh PhotoPrism và Immich sẽ cho biết một lần rebuild Discourse còn đủ headroom để hoàn tất hay không.

Không được sizing server dựa trên một con số trong bài viết, kể cả bài viết này. Hãy đo trên hệ thống của bạn.

free -m
docker stats --no-stream

Swap được sử dụng liên tục cùng với các trang tải chậm nghĩa là máy chủ thiếu RAM. Nếu memory ổn định nhưng trang vẫn chậm thì nguyên nhân thường nằm ở chỗ khác, vì vậy hãy đọc ./launcher logs app trước khi mua plan lớn hơn. Bạn cũng nên thêm một phép kiểm tra từ bên ngoài máy chủ, vì forum hết memory lúc 3am thường fail âm thầm: một status monitor Uptime Kuma tự host trên một host riêng sẽ báo cho bạn trước khi member phát hiện.

Khi Discourse không phải lựa chọn phù hợp

Discourse là một ứng dụng lớn, quá trình cài đặt nặng và phải rebuild sau mỗi thay đổi cấu hình trong app.yml. Đổi lại, bạn có công cụ moderation thực tế và chức năng tìm kiếm vẫn hoạt động khi archive đã lớn. Với 30 người chỉ cần một nơi để trao đổi, Discourse cung cấp nhiều hơn nhu cầu của cuộc trò chuyện. Hãy đọc bài so sánh các phần mềm forum tự host trước, rồi chọn Discourse vì bạn cần những gì nó cung cấp, không phải chỉ vì bạn đã biết tên sản phẩm này.

FAQ

Tôi có thể cài Discourse trên VPS mà không cần tên miền không?

Không. Cấu hình đi kèm nêu rõ Discourse sẽ không hoạt động với địa chỉ IP thuần, và bắt buộc phải có DISCOURSE_HOSTNAME. Discourse tạo các liên kết tuyệt đối từ hostname đó, nên dùng địa chỉ IP sẽ làm hỏng liên kết và khiến việc cấp chứng chỉ thất bại. Hãy tạo bản ghi A trước khi bắt đầu, rồi dùng dig +short forum.example.com để xác nhận bản ghi trỏ đến địa chỉ của server.

Tôi có phải cấu hình SMTP để hoàn tất quá trình cài đặt không?

Tính đến tháng 08/2026, bạn có thể bỏ qua bước này. Trình hướng dẫn cài đặt cung cấp tùy chọn đăng nhập bằng Discourse ID, và app.yml có một switch để bỏ qua bước xác thực cấu hình email. Với mọi mục đích ngoài việc xem thử, hãy cấu hình SMTP, vì email kích hoạt tài khoản và đặt lại mật khẩu đều được gửi qua email. Dùng relay có xác thực trên port 587 hoặc 465, vì hầu hết nhà cung cấp VPS chặn port outbound 25.

Vì sao quá trình rebuild Discourse của tôi bị lỗi giữa chừng?

Memory là nguyên nhân thường gặp nhất. Việc compile asset trong quá trình build cần nhiều memory hơn site đang chạy, nên một máy vẫn phục vụ forum bình thường vẫn có thể fail khi rebuild. Nếu dmesg hiển thị Out of memory: Killed process và dòng này nêu một tiến trình ruby, hãy thêm swap (swapfile của wizard có dung lượng 2 GB), rồi chạy lại ./launcher rebuild app. Nếu build dừng do lỗi YAML, nguyên nhân thường là lỗi thụt lề trong app.yml.

Discourse có nên chạy phía sau nginx hoặc Caddy của tôi không?

Chỉ nên làm vậy khi VPS cũng phục vụ các site khác. Nếu server chỉ chạy Discourse, hãy để container giữ các port 80 và 443 và tự cấp chứng chỉ. Cách này có ít thành phần cần quản lý hơn. Để dùng chung máy với các site khác, hãy thêm templates/web.socketed.template.yml, comment các dòng expose, rồi proxy đến unix socket tại /var/discourse/shared/standalone/nginx.http.sock. Hãy chuyển tiếp X-Forwarded-Proto, nếu không Discourse sẽ tạo các liên kết http:// trên trang HTTPS.

Làm thế nào để backup Discourse tự host?

Dùng trang Backups trong Admin, hoặc chạy discourse backup sau ./launcher enter app. Các archive được lưu trên host tại /var/discourse/shared/standalone/backups/default/. Xác nhận setting bao gồm upload đang được bật, sao chép /var/discourse/containers/app.yml cùng với archive, rồi chuyển cả hai sang một máy khác, vì backup nằm trên cùng disk với site sẽ không thể tồn tại sau chính sự cố mà nó được tạo ra để khắc phục.