Hướng dẫn cài đặt Discourse trên VPS bằng Docker
Hướng dẫn chi tiết cài đặt Discourse qua Docker launcher chính thức. Bạn cần chuẩn bị RAM, swap, domain và SMTP. Bài viết giải thích cách cấu hình app.yml và xử lý lỗi rebuild.
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 trình cài đặt của dự án, trả lời một vài câu hỏi trong wizard và chờ quá trình build hoàn tất. Discourse được phát hành dưới dạng một Docker container duy nhất chứa ứng dụng Rails, PostgreSQL, Redis và nginx. Mọi thay đổi sau này của bạn đều nằm trong một file duy nhất, /var/discourse/containers/app.yml, và mỗi thay đổi sẽ được áp dụng cho site thông qua việc rebuild.
Cách cài đặt chính thức là discourse_docker: một script shell launcher cùng với một bộ các 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ế để tách rời thủ công. Nếu bạn đã quen với việc chạy các dịch vụ trên VPS bằng Docker Compose, hãy chuẩn bị cho một cấu trúc khác biệt. Ở đây không có docker compose up -d, và ./launcher rebuild app chính là quá trình deploy.
Những yêu cầu cần có trước khi bắt đầu với Discourse
Có bốn yêu cầu thường gây khó khăn, và mỗi yêu cầu đều có thể chặn bạn trước khi kịp truy cập trang đăng nhập.
- Bộ nhớ. Một container chạy PostgreSQL, Redis, Sidekiq và một web server Ruby. Bước build sẽ biên dịch các asset và cần nhiều bộ nhớ hơn so với khi trang web đang chạy.
- Một tên miền thực tế. File cấu hình mẫu đã nêu rõ: "Discourse sẽ không hoạt động với địa chỉ IP thuần túy."
- Đường truyền gửi mail. Việc kích hoạt tài khoản, đặt lại mật khẩu, lời mời quản trị viên và email tóm tắt đều được gửi qua SMTP (simple mail transfer protocol).
- Các 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 mà bạn đang chạy.
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 kèm swap và 10 GB dung lượng đĩa, đồng thời khuyến nghị 2 GB RAM với 20 GB dung lượng đĩa. Hãy hiểu dòng đầu tiên là con số giúp trình cài đặt hoàn tất, không phải con số bạn muốn để vận hành một cộng đồng. Sự khác biệt này rất quan trọng vì đỉnh điểm tiêu thụ bộ nhớ nằm ở bước build, không phải ở lưu lượng truy cập.
Trỏ domain về máy chủ trước khi cài đặt
Hãy tạo một bản ghi A cho hostname bạn sẽ sử dụng, sau đó xác nhận lại từ chính máy chủ đó.
dig +short forum.example.com
curl -4 -s https://ifconfig.coCả hai lệnh phải trả về cùng một địa chỉ IP. Chúng phải khớp nhau vì trình cài đặt sẽ chạy một bài kiểm tra kết nối tới hostname của bạn; nếu bản ghi vẫn trỏ về nơi khác, bài kiểm tra này sẽ thất bại. Bản ghi bạn vừa tạo cách đây hai phút có thể vẫn còn trong cache, vì vậy hãy đợi hết thời gian TTL (time to live) cũ thay vì cố gắng ép trình cài đặt chạy tiếp.
Hãy quyết định ngay bây giờ xem bản ghi có được proxy qua CDN hay không. Một bản ghi được proxy sẽ ẩn địa chỉ máy chủ của bạn, khiến yêu cầu cấp chứng chỉ của container thất bại, vì thử thách ACME (automatic certificate management environment) sẽ được phản hồi bởi proxy thay vì bởi Discourse. Hãy để bản ghi ở trạng thái không proxy (unproxied) cho lần cài đặt đầu tiên.
Chạy trình cài đặt chính thức
Một lệnh duy nhất sẽ cài đặt git, cài đặt Docker bằng script cài đặt của chính Docker, clone discourse_docker vào /var/discourse, và khởi động trình hướng dẫn thiết lập.
wget -qO- https://raw.githubusercontent.com/discourse/discourse_docker/main/install-discourse | sudo bashNếu Docker đã có sẵn trên máy và bạn muốn tự thực hiện từng bước, hãy làm thủ công các công việc tương tự.
sudo -s
git clone https://github.com/discourse/discourse_docker.git /var/discourse
cd /var/discourse
./discourse-setupHãy chạy lệnh với quyền root. Nếu khởi chạy bằng người dùng thông thường, discourse-setup sẽ dừng ngay lập tức với thông báo This script must be run as root. Please sudo or log in as root first.. Nếu trên máy chưa có Docker, nó sẽ dừng với thông báo Docker is not installed. Please install Docker first., vì việc clone thủ công sẽ không tự động cài đặt bất cứ thứ gì cho bạn.
Trình hướng dẫn thiết lập hỏi gì và ghi gì
Tính đến tháng 8 năm 2026, discourse-setup chỉ là một wrapper mỏng. Nó chạy discourse/setup-wizard:release dưới dạng một container với network của host và Docker socket được mount, nhờ đó trình hướng dẫn có thể kiểm tra máy chủ mà nó đang cấu hình. Nó yêu cầu nhập hostname và địa chỉ email quản trị, sau đó là thông tin SMTP của bạn. Nó ghi vào containers/app.yml, rồi thực hiện rebuild.
Có hai hành vi bạn cần biết trước khi bắt đầu. Nếu máy chủ thiếu bộ nhớ và không có swap, trình hướng dẫn sẽ dừng lại và đề nghị tạo swap: wrapper sau đó sẽ tạo một file /swapfile dung lượng 2 GB, thêm nó vào /etc/fstab, thiết lập vm.swappiness = 10 trong /etc/sysctl.d/30-discourse-swap.conf, rồi chạy lại trình hướng dẫn. Khi trình hướng dẫn hoàn tất, nó sẽ in ra 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ỏ, và lần đầu tiên là chậm nhất vì mọi asset đều được biên dịch từ đầu.
./discourse-setup --help liệt kê các flag quan trọng khi có sự cố xảy ra. --skip-rebuild ghi cấu hình mà không thực hiện build, còn --skip-connection-test bỏ qua các bước kiểm tra DNS và cổng. Chỉ sử dụng --skip-connection-test khi bạn đã biết rõ lý do tại sao bài kiểm tra thất bại, ví dụ như khi host nằm sau một network firewall mà bạn đang quản lý.
Đọc file app.yml trước khi rebuild lần đầu
Wizard tạo ra một file mà từ giờ bạn phải tự quản lý. Hãy mở nó 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à trang web phản hồi, và Discourse dựa vào đó để xây dựng các đường dẫn, vì vậy nếu giá trị này sai, trang web của bạn sẽ tải được một lần rồi chuyển hướng bạn đi nơi khác. DISCOURSE_DEVELOPER_EMAILS là danh sách các địa chỉ email cách nhau bằng dấu phẩy, và những địa chỉ đó sẽ tự động trở thành admin khi đăng ký lần đầu. Hãy điền địa chỉ của bạn vào đó và đăng ký bằng email này, vì đây là cách tạo tài khoản admin đầu tiên.
File này lưu mật khẩu SMTP của bạn dưới dạng văn bản thuần, vì vậy hãy giới hạn quyền truy cập thư mục bằng sudo chmod 700 /var/discourse/containers. Nó cũng là định dạng YAML, nghĩa là khoảng trắng cũng là cấu hình: một key bị lệch dòng sẽ làm quá trình build thất bại với lỗi parse và khiến bạn không có trang web nào cả. Một cái bẫy đã được ghi chú ngay trong file mẫu. Một ký tự # nằm trong mật khẩu không được đặt trong dấu ngoặc kép sẽ bắt đầu một dòng chú thích, vì vậy hãy đặt mật khẩu trong dấu ngoặc kép nếu nó chứa ký tự này.
Email là bước khiến hầu hết các cài đặt bị dừng lại
Kể từ tháng 8 năm 2026, trình cài đặt cho phép bạn bỏ qua SMTP và sử dụng đăng nhập Discourse ID thay thế, và app.yml đi kèm với một switch DISCOURSE_SKIP_EMAIL_SETUP tương ứng, được mô tả là bỏ qua xác thực thiết lập email. Việc bỏ qua là hợp lý cho lần đầu tìm hiểu phần mềm. Đây là lựa chọn tồi cho một cộng đồng, vì nếu không có thư gửi đi, sẽ 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 các nhà cung cấp VPS đều chặn cổng 25 gửi đi, vì vậy một mail server thông thường trên máy chủ sẽ không gửi được thư. Hãy sử dụng một relay có xác thực trên cổng 587, hoặc trên cổng 465 với TLS (transport layer security) ẩn. Đối với cổng 465, hãy thiết lập DISCOURSE_SMTP_FORCE_TLS: true, đây là cấu hình mẫu được khuyến nghị cho cổng đó. Hãy kiểm tra khả năng kết nối từ host trước khi bạn rebuild.
nc -vz smtp.example.com 587Kết quả thành công là một dòng duy nhất kết thúc bằng succeeded!. Một lệnh bị treo và sau đó hết thời gian chờ (timeout) nghĩa là cổng đó đã bị chặn trên đường truyền ra khỏi VPS của bạn, và không có thiết lập nào của Discourse có thể khắc phục điều đó. Hãy chuyển sang một cổng mà nhà cung cấp của bạn cho phép, hoặc yêu cầu họ mở cổng đó.
Khi trang web đã hoạt động, hãy gửi một tin nhắn thử nghiệm từ trang Email trong phần Admin, sau đó đọc các tab Skipped và Bounced trên cùng trang đó. Những tab này là nơi Discourse ghi lại các thư mà nó từ chối gửi và các thư mà relay từ chối nhận, đồng thời chúng nêu rõ lý do, giúp xử lý nhanh hơn so với việc đọc log.
TLS: để container tự cấp chứng chỉ
Nếu Discourse chiếm cổng 80 và 443, hãy sử dụng tính năng cấp chứng chỉ tích hợp sẵn. Bỏ comment hai dòng template SSL đã nêu ở trên, sau đó rebuild. Template này điều khiển acme.sh, lưu trữ chứng chỉ trong volume chia sẻ tại /shared/ssl, tự động gia hạn theo lịch trình bên trong container và cấu hình Discourse bắt buộc dùng HTTPS.
Cổng 80 phải luôn truy cập được từ Internet để quá trình này hoạt động, vì HTTP challenge được phản hồi tại đó. Firewall chỉ cho phép cổng 443 sẽ khiến quá trình build hoàn tất nhưng chứng chỉ không bao giờ được cấp. Kiểm tra kết quả bằng ./launcher logs app ngay sau khi rebuild.
Bạn có nên đặt nginx hoặc Caddy ở phía trước không?
Nếu Discourse là dịch vụ web duy nhất trên VPS, đừng làm vậy. Container đã chạy sẵn một nginx được tinh chỉnh, việc thêm một proxy thứ hai sẽ tạo thêm một bước trung gian, thêm một chứng chỉ cần gia hạn và thêm một nguồn gây lỗi header mới.
Hãy đặt proxy phía trước khi cùng một VPS phục vụ các trang web khác. Thêm templates/web.socketed.template.yml vào danh sách templates, comment hai dòng expose, và để nguyên hai template SSL ở trạng thái comment. Khi đó, container sẽ lắng nghe trên một unix socket tại /var/discourse/shared/standalone/nginx.http.sock và không giữ bất kỳ cổng nào, giúp giải phóng cổng 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 sau .sock là một phần trong cú pháp unix socket của nginx, và sudo nginx -t sẽ từ chối cấu hình nếu thiếu nó. X-Forwarded-Proto cũng không phải là tùy chọn. Discourse ghi các đường dẫn tuyệt đối, vì vậy nếu không có header đó, nó sẽ xuất ra các liên kết http:// trên một trang HTTPS, và các trình duyệt sẽ chặn chúng vì lỗi mixed content. Khi container đã sử dụng socket, việc quản lý TLS trở thành nhiệm vụ của bạn, vì vậy hãy cấp chứng chỉ trên host với Certbot trên Ubuntu 24.04 và nginx. Nếu bạn chưa quyết định chọn proxy nào, bài so sánh nginx, Caddy và Traefik sẽ giúp bạn hiểu rõ những đánh đổi mà bạn sẽ gặp phải.
Rebuild, nâng cấp và các lệnh bạn sẽ thực sự sử dụng
cd /var/discourse
./launcher rebuild apprebuild sẽ hủy container đang chạy, khởi tạo một container mới từ app.yml và khởi động nó. Trang web sẽ ngoại tuyến trong suốt quá trình build, vì vậy hãy coi mọi thay đổi cấu hình là thời gian bảo trì định kỳ kéo dài vài phút.
Việc chỉ thay đổi các giá trị trong env: không cần thực hiện bước này. ./launcher destroy app && ./launcher start app sẽ tạo lại container từ image bạn đã build trước đó, chỉ mất vài giây. Bất kỳ thay đổi nào trong templates: hoặc hooks: đều làm thay đổi chính image đó, nên cần phải rebuild toàn bộ.
Các bản nâng cấp được thực hiện theo hai cách. Các bản cập nhật nhỏ (point release) được áp dụng từ giao diện web tại /admin/upgrade, thông qua plugin docker_manager mà app.yml đã clone trong quá trình build. Các thay đổi đối với base image hoặc template sẽ đến từ git.
cd /var/discourse
git pull
./launcher rebuild appRebuild là nơi các máy chủ nhỏ thường gặp lỗi, vì quá trình biên dịch tài nguyên (asset compilation) là thời điểm tiêu thụ bộ nhớ cao nhất của toàn hệ thống. Một quá trình build bị dừng giữa chừng, với dmesg hiển thị dòng như Out of memory: Killed process chỉ định một tiến trình ruby, nghĩa là hệ thống đã hết bộ nhớ trong khi build dù trước đó trang web vẫn chạy bình thường. Hãy thêm swap và chạy lại lệnh rebuild.
./launcher logs app
./launcher enter app
./launcher cleanuplogs in ra đầu ra của container, enter mở một shell bên trong container đó và cleanup xóa các container đã dừng hoạt động hơn 24 giờ. Hãy chạy cleanup định kỳ, vì mỗi lần rebuild sẽ để lại một container cũ và dung lượng đĩa trên các VPS nhỏ sẽ bị đầy một cách âm thầm.
Sao lưu và các tệp tin không nằm trong bản sao lưu
Bạn thực hiện sao lưu từ trang Backups trong Admin. Bản lưu trữ sẽ nằm trên máy chủ tại /var/discourse/shared/standalone/backups/default/. Công việc tương tự cũng có thể chạy từ shell.
cd /var/discourse
./launcher enter app
discourse backupdiscourse restore <filename> thực hiện đảo ngược quá trình này, và việc khôi phục sẽ bị từ chối cho đến khi bạn chạy discourse enable_restore. Cơ chế bảo vệ này tồn tại để ngăn chặn một lệnh chạy nhầm làm ghi đè lên diễn đàn đang hoạt động.
Có hai lỗ hổng bạn cần tự xử lý. Bản lưu trữ chứa cơ sở dữ liệu, và nó chỉ chứa các tệp tin đã tải lên khi cài đặt sao lưu bao gồm tệp tải lên được bật, vì vậy hãy kiểm tra cài đặt đó trước khi tin tưởng vào bản sao lưu. Nó không bao giờ chứa app.yml, vì vậy việc khôi phục lên một VPS mới vẫn cần hostname và khối cấu hình SMTP của bạn, điều này có nghĩa là bạn cũng cần sao chép tệp đó ra khỏi máy chủ.
Bản lưu trữ cũng nằm trên cùng ổ đĩa với trang web mà nó bảo vệ, đây không được coi là một bản sao lưu đúng nghĩa. Hãy thiết lập lịch trình để đẩy nó sang một nơi khác.
rsync -avz root@forum.example.com:/var/discourse/shared/standalone/backups/default/ ~/discourse-backups/Chi phí RAM cho một diễn đàn hoạt động
Bootstrap thiết lập UNICORN_WORKERS và db_shared_buffers dựa trên bộ nhớ và CPU mà nó phát hiện được, và cấu hình mẫu giới hạn shared buffers ở mức một phần tư tổng bộ nhớ. Mỗi unicorn worker là một tiến trình Ruby đầy đủ, và Sidekiq chạy các tác vụ nền bên cạnh chúng, vì vậy mức sử dụng bộ nhớ phụ thuộc vào các yêu cầu đồng thời thay vì số lượng thành viên đã đăng ký. Một diễn đàn yên tĩnh với vài trăm thành viên không phải là khối lượng công việc nặng.
Đừng định cỡ máy chủ dựa trên một con số trong bài viết, bao gồm cả bài viết này. Hãy tự đo lường trên hệ thống của bạn.
free -m
docker stats --no-streamSwap liên tục được sử dụng cùng với các trang web phản hồi chậm nghĩa là bạn đang thiếu RAM. Bộ nhớ ổn định nhưng các trang web vẫn chậm thường là do nguyên nhân khác, vì vậy hãy đọc ./launcher logs app trước khi mua gói dịch vụ lớn hơn. Hãy thêm một bước kiểm tra từ bên ngoài máy chủ, vì một diễn đàn hết bộ nhớ vào lúc 3 giờ sáng sẽ bị lỗi âm thầm: một trình giám sát trạng thái Uptime Kuma tự host trên một máy chủ riêng biệt sẽ thông báo cho bạn trước khi các thành viên của bạn biết.
Khi Discourse không phải là lựa chọn phù hợp
Discourse là một ứng dụng lớn với quy trình cài đặt nặng và yêu cầu rebuild mỗi khi thay đổi cấu hình nằm trong app.yml. Cái giá đó đổi lại là các công cụ kiểm duyệt thực thụ và tính năng tìm kiếm vẫn hoạt động tốt khi kho lưu trữ dữ liệu lớn. Đối với nhóm ba mươi người chỉ cần một nơi để trò chuyện, nó quá cồng kềnh so với nhu cầu thực tế. Hãy đọc bài so sánh các phần mềm diễn đàn tự host trước, và chỉ chọn Discourse vì bạn cần những tính năng nó cung cấp, chứ không phải vì đó là cái tên bạn đã biết từ trước.
FAQ
Tôi có thể cài đặt Discourse trên VPS mà không cần tên miền không?
Không. Cấu hình mặc định yêu cầu Discourse không hoạt động với địa chỉ IP thuần, và DISCOURSE_HOSTNAME là bắt buộc. Discourse tạo các liên kết tuyệt đối từ hostname đó, nên việc dùng địa chỉ IP sẽ làm hỏng các liên kết và chặn quá trình cấp chứng chỉ. Hãy tạo một bản ghi A trước khi bắt đầu, và xác nhận bằng dig +short forum.example.com rằng nó phân giải đúng về địa chỉ máy chủ của bạn.
Tôi có bắt buộc phải cấu hình SMTP để hoàn tất cài đặt không?
Kể từ tháng 8 năm 2026, bạn có thể bỏ qua bước này. Trình cài đặt cung cấp tùy chọn đăng nhập bằng Discourse ID thay thế, và app.yml có một tham số cho phép bỏ qua kiểm tra cấu hình email. Tuy nhiên, để sử dụng lâu dài, bạn nên cấu hình SMTP vì việc kích hoạt tài khoản và đặt lại mật khẩu đều cần gửi email. Hãy sử dụng một relay có xác thực qua cổng 587 hoặc 465, vì hầu hết các nhà cung cấp VPS đều chặn cổng 25 chiều outbound.
Tại sao quá trình rebuild Discourse của tôi bị lỗi giữa chừng?
Nguyên nhân thường là do thiếu bộ nhớ. Quá trình biên dịch tài nguyên (asset compilation) trong khi build cần nhiều RAM hơn so với khi site đang chạy, vì vậy một máy chủ chạy forum ổn định vẫn có thể bị lỗi khi rebuild. Nếu dmesg hiển thị Out of memory: Killed process liên quan đến tiến trình ruby, hãy thêm swap (file swap mặc định của trình cài đặt là 2 GB) và chạy lại ./launcher rebuild app. Nếu quá trình build dừng lại do lỗi YAML, đó là do sai sót về thụt lề trong file app.yml.
Discourse có nên nằm sau Nginx hoặc Caddy của tôi không?
Chỉ khi VPS của bạn phục vụ nhiều site khác cùng lúc. Nếu chỉ chạy một mình, hãy để container giữ cổng 80 và 443 để tự cấp chứng chỉ, giúp giảm bớt các thành phần trung gian. Để chia sẻ máy chủ, hãy thêm templates/web.socketed.template.yml, comment các dòng expose, và 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 ra các liên kết http:// trên trang HTTPS.
Làm thế nào để sao lưu một bản Discourse tự host?
Sử dụng trang Backups trong phần Admin, hoặc chạy discourse backup sau khi đã vào ./launcher enter app. Các file lưu trữ sẽ nằm trên máy chủ tại /var/discourse/shared/standalone/backups/default/. Hãy xác nhận cài đặt bao gồm cả phần uploads đã được bật, copy /var/discourse/containers/app.yml cùng với file lưu trữ, và chuyển cả hai sang một máy khác. Bản sao lưu nằm trên cùng ổ đĩa với site sẽ không còn giá trị nếu ổ đĩa đó gặp sự cố.