SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-26

Rocket.Chat Docker Compose 自架完整教學

在 VPS 以 Docker Compose 自架 Rocket.Chat,涵蓋單節點 MongoDB replica set、TLS 與備份,並修正常見啟動失敗與 OOM 錯誤。

建置內容

這是一套完全由你掌控的私人團隊聊天服務:Rocket.Chat 透過 Docker Compose 執行於自有 VPS,使用 TLS termination,所有訊息都儲存在 MongoDB 資料庫中,方便備份與搬遷。Rocket.Chat 是成熟的開放原始碼 Slack 與 Teams 替代方案,提供頻道、直接訊息、執行緒、檔案分享,以及語音和視訊功能,全部執行於你租用並管理的硬體上。應用程式只需單一容器,幾分鐘內即可啟動。實際發生的大多數問題都位於旁邊的資料庫,因此本指南主要介紹 MongoDB,尤其是第一​次設定時最容易讓人意外的必要條件:Rocket.Chat 無法使用獨立的 MongoDB 執行。它需要 replica set,即使該「set」只有單一節點也一樣。

先決條件,以及沒人告訴你的 RAM 計算方式

請如實評估伺服器規模。小型團隊的實際最低配置是 2 vCPU 和 4 GB RAM。Rocket.Chat 的 Node.js 程序本身約需 1 到 1.5 GB,而 MongoDB 的 WiredTiger 快取預設會使用剩餘 RAM 的約一半。在 2 GB VPS 上,兩者啟動時看似足夠,但實際流量一到就會互相爭用:MongoDB 擴大快取,Node 擴大 heap,kernel 耗盡可用 pages,out-of-memory killer 會終止占用量最大的程序,通常是 mongod。容器會輸出 Killed,Docker 隨後重新啟動容器,結果是在本應輕鬆處理的負載下,chat server 每隔幾分鐘就中斷。2 GB 適合兩人試用;不適合作為團隊伺服器。請從 4 GB 開始;如果預期有數十名同時上線的使用者、video call 或持續增加的上傳紀錄,請配置 8 GB。

開始前還需要準備三項條件。第一,準備一個 A record 指向 VPS 公開 IP 的網域名稱。Rocket.Chat 的即時功能與 mobile client 需要穩定的 hostname,不能只使用 IP。第二,在伺服器 firewall 和 provider 的 network firewall 上開放 Ports 80 and 443;大多數控制面板會將後者作為獨立的控制項。第三,準備全新的 Ubuntu 24.04 KVM VPS,並具備 root 或 sudo 權限。如果你仍在評估 chat server 是否適合作為第一個要執行的服務,2026 年值得自行代管哪些服務的指南 可協助你了解其中的取捨。

安裝 Docker engine 與 Compose plugin

請使用 Docker 自己的 apt repository,不要使用 Ubuntu 隨附的 docker.io package,也不要使用過時的獨立 docker-compose Python binary。新版 Compose 是透過 docker compose 呼叫的 Docker plugin,中間使用空格,而不是連字號。舊版 docker-compose v1 已停止支援,無法正確處理下方的 healthcheck 與相依性語法。

sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

確認兩個元件都已存在:

sudo docker version
sudo docker compose version

docker compose version 顯示類似 Docker Compose version v2.x 的內容,才是需要確認的結果。如果出現 docker: 'compose' is not a docker command 錯誤,表示 plugin 未安裝。請先在此處修正,否則稍後會遇到難以判斷原因的錯誤。

Compose 檔案:MongoDB 作為單節點 replica set

這是最容易設定錯誤的部分,請仔細閱讀。Rocket.Chat 使用 MongoDB 的 change streams,即時將新訊息推送給已連線的用戶端,而 change streams 只有在 replica set 上才能使用。若將 Rocket.Chat 指向一般的獨立 mongod,它會先連線,接著無法開啟 change stream,最後持續重新啟動。解決方式並不複雜:執行單一的一般 MongoDB container,但以 --replSet 啟動,然後初始化包含單一成員的 set。

建立工作目錄和 compose.yml

services:
  mongodb:
    image: mongo:8.0
    restart: always
    command: ["mongod", "--replSet", "rs0", "--bind_ip_all", "--oplogSize", "128"]
    volumes:
      - mongodb_data:/data/db
      - mongodb_config:/data/configdb
    healthcheck:
      test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping')"]
      interval: 10s
      timeout: 10s
      retries: 12

  rocketchat:
    image: registry.rocket.chat/rocketchat/rocket.chat:8.5.1
    restart: always
    depends_on:
      mongodb:
        condition: service_healthy
    environment:
      MONGO_URL: "mongodb://mongodb:27017/rocketchat?replicaSet=rs0"
      MONGO_OPLOG_URL: "mongodb://mongodb:27017/local?replicaSet=rs0"
      ROOT_URL: "https://chat.example.com"
      PORT: "3000"
    ports:
      - "127.0.0.1:3000:3000"

volumes:
  mongodb_data:
  mongodb_config:

這裡的幾項設定都是刻意安排的。Rocket.Chat 的連接埠發布至 127.0.0.1:3000,而不是 0.0.0.0。應用程式本身未啟用 TLS,因此只有同一台主機上的 reverse proxy 應能連線;若綁定至所有介面,就會直接將明文登入頁面暴露在公開網際網路上。MongoDB 完全不發布至主機,只能透過 Compose 的內部網路,以 mongodb 這個名稱連線;這正是 MONGO_URL 使用的主機名稱。MONGO_URL 會傳遞 ?replicaSet=rs0;若省略該設定,driver 會將伺服器視為 standalone,即使它實際上是 replica set,change streams 仍會失敗。MONGO_OPLOG_URL 指向存放 oplog 的 local 資料庫;新版 Rocket.Chat 偏好使用 change streams,但設定此項並無害處,也能讓較舊的程式碼路徑正常運作。depends_on 使用 condition: service_healthy,因此 Compose 會等待 MongoDB 回應 ping 後,才啟動 Rocket.Chat;這就是 healthcheck 的用途。

請為兩個映像固定實際的版本標籤:mongo:8.0,以及此處所用的明確 Rocket.Chat 版本,例如 8.5.1。絕不要使用 :latest,因為這會讓無人值守的 docker pull 變成意外且無法進行遷移的升級。固定版本前,請先確認目前穩定的 Rocket.Chat 版本,以及該版本支援的 MongoDB 版本。

Rocket.Chat 會為每個版本發布機器可讀的資訊文件:curl -s https://releases.rocket.chat/8.5.1/info | jq '{compatibleMongoVersions, lts}' 對 8.5.1 會回傳 compatibleMongoVersions: ["8.0"],因此唯一受支援的引擎是 mongo:8.0。文件也會提供 lts 旗標,指出該版本是否為適合固定使用的長期支援版本,方便不希望持續維護伺服器的情境。

並非每個專案都會發布有版本標籤的映像。遇到這種情況,應改為固定原始碼版本:自行託管 openGym 健身追蹤器 表示簽出特定的 git 標籤並從該版本建置,而不是跟隨會持續變動的分支。

初始化副本集

啟動堆疊:

sudo docker compose up -d

Rocket.Chat 會立即崩潰,Docker 也會持續重新啟動它。這是預期行為,因為副本集尚未建立。請手動建立一次:

sudo docker compose exec mongodb mongosh --eval 'rs.initiate({_id: "rs0", members: [{_id: 0, host: "mongodb:27017"}]})'

正確結果為 { ok: 1 }。幾秒內,這個單一節點會自行選出為 primary;使用以下指令確認:

sudo docker compose exec mongodb mongosh --quiet --eval 'rs.status().members[0].stateStr'

你應看到 PRIMARY。本頁最重要的細節是 host: "mongodb:27017" 引數。如果執行不含 members 清單的基本 rs.initiate(),MongoDB 會使用容器的內部主機名稱公布副本集,例如隨機產生的雜湊值 a1b2c3d4e5f6。Rocket.Chat 從自己的容器連線時無法解析該名稱,因此 MongoDB driver 的 DNS 解析會失敗,並持續記錄 MongoServerSelectionError: getaddrinfo ENOTFOUND a1b2c3d4e5f6。務必使用與 MONGO_URL 相符的明確服務名稱來初始化。

首次啟動:監看服務啟動

設定為 primary 後,Rocket.Chat 下一次重新啟動時就能順利連線,並開始首次啟動時的 migration。查看日誌:

sudo docker compose logs -f rocketchat

請等待啟動橫幅出現:

+--------------------------------------------+
        SERVER RUNNING
   Rocket.Chat Version: 8.5.1
        NodeJS Version: 22.22.3 - x64
+--------------------------------------------+

首次啟動需要較長時間,因為應用程式會執行資料庫 migration 並建立索引。因此,請先等待 1 到 2 分鐘,再判斷是否發生問題。如果日誌持續重複 MongoServerSelectionError: Server selection timed out after 30000 ms,且拓撲描述的類型為 ReplicaSetNoPrimary,表示 replica set 尚未初始化;如果日誌持續對隨機 hash 重複 getaddrinfo ENOTFOUND,表示初始化時使用了錯誤的主機。無論是哪種情況,都請返回上一步。看到 SERVER RUNNING 後,表示 Rocket.Chat 正在 127.0.0.1:3000 上監聽,此時即可為它設定正式主機名稱與 TLS。

置於 TLS 後方

絕不要讓 Rocket.Chat 透過純 HTTP 對外提供服務。只要曾經透過 http:// 登入一次,就等於把管理員密碼交給路徑上的任何人。請在同一台主機上的反向代理終止 TLS,再將流量轉送至 127.0.0.1:3000。有兩點很重要:代理必須轉送 WebSocket upgrade 標頭,因為 Rocket.Chat 是即時應用程式,缺少這些標頭就會失效;此外,容器的 ROOT_URL 必須與使用者輸入的公開 HTTPS 位址完全一致。

先建立純 HTTP 的 nginx server block,將請求代理至應用程式,並轉送 upgrade 標頭。將其儲存為 /etc/nginx/sites-available/rocketchat,建立符號連結至 sites-enabled,然後重新載入:

server {
    listen 80;
    server_name chat.example.com;

    client_max_body_size 100M;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

目前先讓它使用 80 埠。包含 listen 443 ssl; 且沒有憑證的設定區塊甚至無法通過 sudo nginx -t。重新載入 nginx(sudo nginx -t && sudo systemctl reload nginx),然後申請憑證。在 Ubuntu 上,最簡單的方法是使用 使用 Certbot 和 nginx 申請 Let’s Encrypt TLS 憑證certbot --nginx 會直接改寫上述設定區塊,加入 listen 443 ssl;ssl_certificate 行,以及自動將 80 轉向 443 的設定,並為你排程續期。如果你已經透過同一個代理管理多個容器,使用 Traefik 為多個 Docker 應用程式自動設定 TLS 會更簡潔;只要在 rocketchat 服務加入 router 和 service labels,Traefik 就會代為申請及續期憑證,完全不需要 nginx block。無論採用哪種方式,請在 compose.ymlROOT_URL 設為 https://chat.example.com,然後重新執行 sudo docker compose up -d,讓容器套用變更。若你希望伺服器只能從自己的網路存取,而不是公開網際網路,請在前方配置 VPS 上的自架 WireGuard VPN,並將代理繫結至 VPN 通道位址。

首次執行設定精靈

瀏覽至 https://chat.example.com,Rocket.Chat 會引導你完成簡短的設定精靈。首先設定管理員帳號,包括真實姓名、使用者名稱、電子郵件地址及高強度密碼。這是目前唯一存在的帳號,因此請妥善保管。接著設定組織與伺服器資訊,包括名稱、產業、規模、網站名稱及預設語言。這些設定僅影響外觀,填寫後即可繼續。最後是實際重要的選擇:向 Rocket.Chat Cloud 註冊此工作區,或將其維持為獨立部署

註冊後可透過 Rocket.Chat 的 gateway 啟用行動推播通知及附加元件市集,但代價是伺服器需要與 Rocket.Chat 的 cloud 建立控制平面關係。獨立部署可讓伺服器完全維持私有,且不依賴外部服務;但 iOS 和 Android 的推播通知會停止運作。這是因為 Apple 和 Google 不允許自建應用程式持有推播憑證,而官方應用程式會透過 cloud gateway 轉送通知。如果隱私是主要考量,且使用者主要透過 Web 應用程式使用服務,請選擇獨立部署。如果行動推播不可或缺,請選擇註冊。之後可在 Admin 中變更此設定。

先完成安全限制,再邀請使用者

Rocket.Chat 預設會開啟註冊功能,Registration Form 預設設為 Public,因此任何找到 URL 的人都能建立帳號。對公開主機名稱而言,這等於直接暴露入口。前往 Admin → Settings → Accounts → Registration,將 Registration Form 設為 Disabled,由你手動建立帳號或透過邀請連結建立,或設為 Secret URL。在同一頁關閉 Allow Anonymous ReadAllow Anonymous Write,除非你確實需要公開的唯讀頻道。如果手動建立每個帳號過於繁瑣,而且團隊登入的不只有這項服務,可以將 Rocket.Chat 的 OAuth 登入指向 自架的 Authentik SSO 伺服器,讓新進與離職人員的帳號集中在同一處管理,而不必逐一處理各項應用程式。

也要決定上傳檔案的儲存位置。預設的 File Upload 儲存方式是 GridFS,會將每張圖片與每個附件直接儲存在 MongoDB 中。這種方式很簡單,但表示只要使用者不斷貼上螢幕截圖,資料庫以及你建立的每份 mongodump 都會持續增長。前往 Admin → Settings → File Upload,即可將儲存方式切換為本機檔案系統或相容於 S3 的 bucket,並設定合理的檔案大小上限。對小型團隊而言,GridFS 已足夠;但請注意,備份會隨時間變得更大。

使用 mongodump 備份

所有資料都存放在 mongodb_data volume 中。不要在資料庫執行期間直接複製 volume。請使用 mongodump 建立一致性 dump,並將輸出串流至主機上的檔案:

sudo docker compose exec -T mongodb mongodump --db rocketchat --archive --gzip > rocketchat-$(date +%F).archive.gz

這個 gzip 壓縮封存檔包含完整的工作區:使用者、頻道、訊息、設定,以及在 uploads 保留於 GridFS 的情況下,還包括檔案。如果你已將 uploads 移至檔案系統或 S3,請另外備份該儲存區。要還原至全新的 stack,請先初始化 replica set,然後執行:

sudo docker compose exec -T mongodb mongorestore --archive --gzip --drop < rocketchat-2026-07-15.archive.gz

將封存檔複製到主機以外的位置,例如 object storage 或其他伺服器。無論選擇哪個位置,都不能讓 VPS 損毀時連同備份一併遺失,並使用 cron 每晚執行 dump。從未實際還原過的備份只是希望,不算備份。請先在一次性 VPS 上演練還原,確認程序可正常運作,再等到真正需要時才使用。

升級:固定標籤、閱讀版本說明、遵循 MongoDB 支援矩陣

只要遵循兩項規則,升級就能維持單純。第一,每次只將 Rocket.Chat 升級一個主要版本。 Rocket.Chat 啟動時會執行結構描述遷移,並刻意拒絕跨越多個主要版本;如果嘗試直接從 6.x 升級至 8.x,系統會因遷移錯誤停止,而不會損毀資料。將映像檔標籤更新至下一個主要版本的最新版本,閱讀該版本的版本說明以確認是否有重大變更,執行 docker compose up -d,並監看日誌,確認遷移完成後再繼續。第二,遵循 MongoDB 支援矩陣。 每個 Rocket.Chat 版本支援特定的 MongoDB 版本範圍,curl -s https://releases.rocket.chat/<version>/info | jq .compatibleMongoVersions 會告訴你支援哪些版本。升級 MongoDB 時,例如從 7.0 升級至 8.0,請每次只跨越一個主要版本,並在每次升級後設定功能相容性版本。在 MongoDB 8.0 上,該命令需要明確指定 confirm: true,否則會拒絕執行,並顯示訊息要求你加入確認旗標重新執行:

sudo docker compose exec mongodb mongosh --eval 'db.adminCommand({setFeatureCompatibilityVersion: "8.0", confirm: true})'

每次升級任一元件前,先建立 mongodump。這就是完整的保險措施。

失敗模式與確切字串

Rocket.Chat 在 docker compose up 後立即反覆重新啟動,且 docker compose logs rocketchatMongoServerSelectionError 填滿。 MongoDB 正在執行,但 driver 無法選取 primary;確切字串會告訴你發生哪個錯誤。Server selection timed out after 30000 ms 顯示拓撲類型為 ReplicaSetNoPrimary,表示你從未執行 rs.initiate(),因此 replica set 尚未建立設定。getaddrinfo ENOTFOUND 後接著出現隨機 hash,表示你在未明確指定 host: "mongodb:27017" 的情況下進行初始化,導致 MongoDB 公告無法解析的容器 hostname。使用 sudo docker compose exec mongodb mongosh --eval 'rs.status()' 進行診斷:若出現 MongoServerError: no replset config has been received 錯誤,請初始化 replica set;若顯示某個成員的 name 是隨機 hash,請改用服務名稱重新初始化。

Web UI 可以載入,但登入畫面永遠轉圈且無法完成。 開啟瀏覽器主控台,你會看到 WebSocket connection to 'wss://chat.example.com/websocket' failed。這幾乎總是 ROOT_URL 不一致,或代理未轉送 upgrade headers 所造成。確認 ROOT_URL 等於完全相同的公開位址,且包含 https://;另外確認 nginx 的 location 區塊以 proxy_http_version 1.1 設定 UpgradeConnection "upgrade"。修改任一項後,重新執行 docker compose up -d

容器持續結束,且 docker compose ps 顯示 Restarting docker compose logs 在半行處中斷,sudo dmesg | tail 顯示 oom-killer 造成的 Out of memory: Killed process 12345 (mongod);exit code 為 137。這表示主機的 RAM 不足。真正的解決方法是改用更大的 VPS,最低需要 4 GB。權宜之計是加入 swap,並在 MongoDB 的 command 中使用 --wiredTigerCacheSizeGB 1 限制 cache;但在實際負載下,swap 只能延後下一次 OOM:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

docker compose up 失敗並顯示 Error response from daemon: driver failed programming external connectivity ... bind: address already in use 連接埠 3000 已被其他程式占用。常見原因是先前未正常停止的 Rocket.Chat 容器,或其他應用程式。使用 sudo ss -ltnp | grep :3000 找出占用者,停止該程序或容器;也可以將 mapping 的主機端改為 127.0.0.1:3001:3000,並同步更新代理的 proxy_pass

FAQ

Rocket.Chat 確實需要 MongoDB replica set 嗎?

需要,即使只有一台伺服器和一個資料庫節點也一樣。Rocket.Chat 使用 MongoDB change streams 即時傳送訊息,而 change streams 只有 replica set 支援,獨立執行的 mongod 無法開啟這項功能。不需要多台機器;啟動一個使用 --replSet rs0 啟動的 MongoDB container,再以 rs.initiate() 初始化單成員 replica set。若略過這個步驟,driver 就找不到 primary,因此 Rocket.Chat 會因 MongoServerSelectionError: Server selection timed out 不斷重新啟動,且始終無法完成啟動。

自行託管 Rocket.Chat 需要多少 RAM?

實務上至少規劃 4 GB,團隊使用量較高時則規劃 8 GB。Rocket.Chat 的 Node process 約使用 1 到 1.5 GB,MongoDB 會將剩餘 RAM 約一半配置給 WiredTiger cache。因此在 2 GB 的主機上,兩者會互相爭用記憶體;只要有實際負載,out-of-memory killer 就會終止 mongod,並在日誌中顯示 Killed 和 exit code 137。2 GB 僅足以讓幾名測試使用者評估此軟體。

如何讓 Rocket.Chat 透過 HTTPS 提供服務?

在同一台 VPS 上執行 reverse proxy,由它執行 TLS termination,並將請求轉送到 127.0.0.1:3000;同時將 container 的 ROOT_URL 設為公開的 https:// 位址。代理程式必須轉送 WebSocket upgrade headers,否則登入會停滯。使用 nginx 搭配 Certbot 是單一應用程式最簡單的設定;如果要在同一個代理後方執行多個 container,並且需要自動管理憑證,Traefik 的設定更簡潔。

如何備份自行託管的 Rocket.Chat?

使用 mongodump 建立一致的資料庫 dump,不要直接複製 volume:docker compose exec -T mongodb mongodump --db rocketchat --archive --gzip > backup.archive.gz。該 archive 包含使用者、頻道、訊息和設定;如果儲存空間仍使用 GridFS,也會包含上傳的檔案。將它複製到伺服器外部,以 cron 每晚自動執行,並在臨時主機上演練 mongorestore,確認還原確實可運作。

如何升級 Rocket.Chat 而不破壞 MongoDB?

Rocket.Chat 每次只升級一個 major version。它會在啟動時執行 migration,且拒絕跳過 major version;更新 pinned image tag 前,請先閱讀各版本的 release notes。使用 curl -s https://releases.rocket.chat/<version>/info | jq .compatibleMongoVersions 檢查目標版本支援哪些 MongoDB 版本;升級 MongoDB 時,也要每次只跨一個 major version,並在每次升級後使用 confirm: true 設定 setFeatureCompatibilityVersion。務必先建立 mongodump