SSD Nodes Learn
How to do am Matt ConnorBy Matt Connor · Updated 2026-07-24

how to host Rocket.Chat with Docker Compose

Learn how to run Rocket.Chat for your team using Docker Compose. We show you how to set up MongoDB replica set and TLS without any wahala for your VPS.

Wetin you dey build

Private team chat wey you get full control: Rocket.Chat wey dey run for your own VPS inside Docker Compose, with TLS security, and every message dey inside MongoDB database wey you fit back up and move. Rocket.Chat na strong open-source alternative for Slack and Teams — e get channels, direct messages, threads, file sharing, and voice and video, all for hardware wey you rent and control. The application na single container wey dey start in minutes. Most of the wahala wey dey happen dey inside the database beside am, so most of this guide na about MongoDB. One thing wey dey surprise everybody when dem dey start na: Rocket.Chat no go run if you use standalone MongoDB. E need replica set, even if that "set" na single node.

Prerequisites, and the RAM math nobody tells you

Check your machine size well. For small team, the minimum requirement na 2 vCPU and 4 GB of RAM. Rocket.Chat's Node.js process need roughly 1 to 1.5 GB for itself, and MongoDB's WiredTiger cache go take about half of wetin remain for RAM by default. If you use 2 GB VPS, the two go fit for boot, but dem go start to fight as soon as real traffic come: MongoDB go expand its cache, Node go expand its heap, kernel go run out of pages, and the out-of-memory killer go kill whichever process big pass — usually mongod. The container go print Killed, Docker go restart am, and you go get chat server wey dey crash every few minutes even when load small. 2 GB dey okay for just test with two people; e no be team server. Start with 4 GB, and if you want dozens of concurrent users, video calls, or plenty upload history, give am 8 GB.

You also need three things ready before you start. One domain name wey get A record wey point to the VPS public IP — Rocket.Chat's real-time features and mobile clients need stable hostname, no be bare IP. Ports 80 and 443 must open for both the server firewall and your provider's network firewall, because most panels dey keep dem as separate control. And one fresh Ubuntu 24.04 KVM VPS wey get root or sudo access. If you still dey decide if chat server na the right first service to run, see the guide to what is worth self-hosting in 2026 for see the tradeoffs.

Install the Docker engine and the Compose plugin

Use Docker's own apt repository. No use the docker.io package wey Ubuntu dey ship, and no use that old standalone docker-compose Python binary. Modern Compose na Docker plugin wey you go dey call as docker compose — use space, no use hyphen. The old docker-compose v1 don reach end-of-life and e no dey handle the healthcheck and dependency syntax wey dey below well.

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

Confirm say both pieces dey ground:

sudo docker version
sudo docker compose version

If docker compose version print something like Docker Compose version v2.x, that na the check wey matter. If e show error with docker: 'compose' is not a docker command, e mean say the plugin no install and you go face many wahala later — fix am here.

The compose file: MongoDB as a single-node replica set

Dis one na part wey many people dey fail, so read am well. Rocket.Chat dey use MongoDB change streams take push new messages to connected clients for real time. Change streams dey work only for replica set. If you point Rocket.Chat to plain standalone mongod, e go connect, but e no go fit open change stream, and e go just dey restart-loop forever. The fix no hard: you go run one ordinary single MongoDB container, but you must start am with --replSet and then initialise one-member set.

Create one working directory and one 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:

We pick some choices here for reason. Rocket.Chat port dey publish to 127.0.0.1:3000, no be 0.0.0.0 — de app itself no get TLS, so only de reverse proxy for de same box suppose reach am; if you bind am to every interface, you go put plaintext login page for public internet. MongoDB no dey publish to de host at all; you fit reach am only through Compose internal network under de name mongodb, wey na de same hostname de MONGO_URL dey use. MONGO_URL dey carry ?replicaSet=rs0 — if you remove am, de driver go treat de server as standalone even though e be replica set, and change streams go still fail. MONGO_OPLOG_URL dey point to de local database wey oplog dey; modern Rocket.Chat prefer change streams, but to set am no go cause wahala and e go keep old code paths dey work. De depends_on dey use condition: service_healthy, so Compose go wait until MongoDB answer one ping before e start Rocket.Chat — naim healthcheck dey do.

Use real version tags for both images — mongo:8.0 and one explicit Rocket.Chat release like 8.5.1 for here — and never use :latest, because dat one dey turn unattended docker pull into accidental upgrade wey you no fit migrate. Check de current stable Rocket.Chat release and de MongoDB versions wey e support before you pin dem. Rocket.Chat dey publish one machine-readable info document for every release: curl -s https://releases.rocket.chat/8.5.1/info | jq '{compatibleMongoVersions, lts}' dey return compatibleMongoVersions: ["8.0"] for 8.5.1, so mongo:8.0 na de only supported engine, plus one lts flag wey go tell you if dat release na long-term-support build wey worth pinning for server wey you no want dey babysit.

Initialise the replica set

Bring the stack up:

sudo docker compose up -d

Rocket.Chat go start to crash immediately and Docker go keep restarting am — dat one na normal, because replica set no exist yet. Create am once, by hand:

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

Correct result na { ok: 1 }. Within few seconds, single node go elect itself as primary; confirm am with:

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

You must see PRIMARY. Di most important detail for dis whole page na di host: "mongodb:27017" argument. If you run bare rs.initiate() without members list, MongoDB go advertise di replica set under di container internal hostname — random hash like a1b2c3d4e5f6. Rocket.Chat, wey dey connect from its own container, no fit resolve dat name, so MongoDB driver go fail DNS for am and loop forever dey log MongoServerSelectionError: getaddrinfo ENOTFOUND a1b2c3d4e5f6. Always start with di explicit service name wey match your MONGO_URL.

First boot: watch am come up

Once the set don become primary, next time Rocket.Chat restart, e go connect well and start first-run migrations. Follow the logs:

sudo docker compose logs -f rocketchat

The line wey you dey wait for na the startup banner:

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

First boot dey slow — the app dey run database migrations and build indexes, so give am one or two minutes before you start to worry. If the log dey repeat MongoServerSelectionError: Server selection timed out after 30000 ms with topology description of type ReplicaSetNoPrimary, e mean say replica set no don start; if e dey repeat getaddrinfo ENOTFOUND for one random hash, e mean say e start with wrong host. Any way wey e go, go back one step. Once you see SERVER RUNNING, Rocket.Chat dey listen on 127.0.0.1:3000 and e don time to put real hostname and TLS for front of am.

Put am behind TLS

No ever expose Rocket.Chat for plain HTTP. If you log in over http:// once, you don give your admin password to anybody wey dey the path. Put TLS for reverse proxy for the same box and forward am to 127.0.0.1:3000. Two things dey important: the proxy must forward the WebSocket upgrade headers, because Rocket.Chat na real-time and e go break if dem no dey, and the container ROOT_URL must match the public HTTPS address wey users dey type exactly.

Start with one plain HTTP nginx server block wey proxy to the app and forward the upgrade headers. Save am as /etc/nginx/sites-available/rocketchat, use symlink put am inside sites-enabled, and reload:

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;
    }
}

Leave am for port 80 for now — any block wey get listen 443 ssl; and no certificate no go even pass sudo nginx -t. Reload nginx (sudo nginx -t && sudo systemctl reload nginx), then issue the certificate. The cleanest way for Ubuntu na Let's Encrypt TLS certificates with Certbot and nginx: certbot --nginx go rewrite the block wey dey above, e go add listen 443 ssl;, the ssl_certificate lines, and automatic 80-to-443 redirect, and e go schedule renewal for you. If you don dey run many containers behind one proxy already, Traefik with automatic TLS for many Docker apps na the better option — just add router and service labels to the rocketchat service and Traefik go request and renew the certificate for you, without any nginx block at all. Either way, set ROOT_URL to https://chat.example.com for compose.yml and run sudo docker compose up -d again so the container go pick the change. If you want make the server only reachable from inside your own network instead of public internet, put self-hosted WireGuard VPN on the VPS for front and bind the proxy to the tunnel address.

The first-run setup wizard

Go to https://chat.example.com and Rocket.Chat go show you small wizard. First, na the admin account — you must put real name, username, email, and strong password; na only this account dey exist, so no lose am. Next, na organisation and server info — name, industry, size, site name, and default language; na just for decoration, fill am and move on. Then, the choice wey really matter: register this workspace with Rocket.Chat Cloud, or keep am standalone.

If you register, you go get mobile push notifications through Rocket.Chat gateway and add-on marketplace, but you go get control-plane relationship with Rocket.Chat cloud. If you pick standalone, the server go stay fully private and no dependency, but iOS and Android push notifications go stop to work, because Apple and Google no go allow self-built app hold push certificates — the official apps dey use cloud gateway. Pick standalone if privacy na your main priority and your users dey use web app; pick registration if mobile push na must-have for you. You fit change your mind later for Admin.

Lock am down before you invite anybody

Rocket.Chat dey come with open registration on — by default, the Registration Form dey set to Public, so anybody wey find the URL fit create account. If you dey use public hostname, e be like open door. Go to Admin → Settings → Accounts → Registration and set Registration Form to Disabled, so you fit create accounts manually or by invite link, or set am to Secret URL. While you dey there, turn off Allow Anonymous Read and Allow Anonymous Write unless you really want public read-only channel.

Decide where uploads go, too. The default File Upload storage na GridFS, wey dey store every image and attachment inside MongoDB itself. That one easy, but e mean say your database — and every mongodump wey you take — go dey grow continuously as people dey paste screenshots. Under Admin → Settings → File Upload you fit switch the storage to the local filesystem or an S3-compatible bucket, and set one sensible maximum file size. For small team, GridFS fine; just know say your backups go dey heavy as time dey pass.

Backups with mongodump

All your data dey inside the mongodb_data volume. No just copy the volume while database dey run — use mongodump take consistent dump, then stream am to one file for the host:

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

That single gzipped archive na your whole workspace: users, channels, messages, settings, and — if you keep uploads for GridFS — the files too. If you move uploads go filesystem or S3, back up that store separately. To restore for new stack, first initialise the replica set, then:

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

Copy the archive off the box — use object storage, another server, or anywhere wey the VPS wey dey die no go reach — and run the dump from cron every night. Backup wey you never restore na just hope, no be backup; practice the restore once for one throwaway VPS so you go know say e dey work before you really need am.

Upgrades: pin tags, read the notes, respect the Mongo matrix

Two rules dey make upgrades easy. First, upgrade Rocket.Chat one major version at a time. E dey run schema migrations when e start up, and e no go allow you jump majors; if you try go from 6.x straight to 8.x, e go stop with migration error instead of to corrupt your data. Change the image tag to the next major's latest release, read the release notes for any breaking changes, run docker compose up -d, and watch the logs finish migrating before you move on. Second, respect the MongoDB support matrix. Every Rocket.Chat release support one specific set of MongoDB versions, and curl -s https://releases.rocket.chat/<version>/info | jq .compatibleMongoVersions go tell you which one. When you wan move MongoDB — like from 7.0 to 8.0 — move one major at a time and set the feature-compatibility version after every step. For MongoDB 8.0, that command need explicit confirm: true, or e go refuse with message tell you to re-run am with the confirmation flag:

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

Take a mongodump before you upgrade any of these components. Na that one be your insurance policy.

Failure modes, with the exact strings

Rocket.Chat dey restart-loop right after docker compose up, and docker compose logs rocketchat dey fill with MongoServerSelectionError. MongoDB dey run but driver no fit select primary, and the exact string go tell you which mistake you make. Server selection timed out after 30000 ms with a topology type of ReplicaSetNoPrimary mean say you never run rs.initiate() — the set no get config yet. getaddrinfo ENOTFOUND follow by one random hash mean say you start am without the explicit host: "mongodb:27017", so MongoDB advertise one container hostname wey no fit resolve. Use sudo docker compose exec mongodb mongosh --eval 'rs.status()' check am: if e show error MongoServerError: no replset config has been received, start the set; if e show one member wey name na random hash, re-initiate am with the service name.

Web UI load, but login dey spin forever and e no ever finish. Open the browser console and you go see WebSocket connection to 'wss://chat.example.com/websocket' failed. This one na almost always ROOT_URL mismatch or proxy wey no dey forward the upgrade headers. Confirm say ROOT_URL equal the exact public address including https://, and say your nginx location block set Upgrade and Connection "upgrade" with proxy_http_version 1.1. Change any of dem and re-run docker compose up -d.

One container dey die continuously and docker compose ps show say e Restarting. docker compose logs cut mid-line and sudo dmesg | tail show Out of memory: Killed process 12345 (mongod) from oom-killer; the exit code na 137. The box don finish RAM. The real fix na to use bigger VPS — 4 GB minimum. As temporary fix, add swap and cap MongoDB cache with --wiredTigerCacheSizeGB 1 inside its command, but swap go only delay the next OOM when real load come:

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

docker compose up fail with Error response from daemon: driver failed programming external connectivity ... bind: address already in use. Something already dey hold port 3000 — often na one previous Rocket.Chat container wey no stop cleanly, or another app. Find am with sudo ss -ltnp | grep :3000, stop that process or container, or change the host side of the mapping to 127.0.0.1:3001:3000 and update your proxy proxy_pass to match am.

FAQ

Rocket.Chat really need MongoDB replica set?

Yes, even if na single server with one database node. Rocket.Chat dey deliver messages for real time by using MongoDB change streams, and change streams na feature wey only dey work for replica-set — standalone mongod no fit open one. You no need many machines; you fit run one MongoDB container wey you start with --replSet rs0 and use rs.initiate() to initialise one-member set. If you skip that step, the driver no go see primary, so Rocket.Chat go dey restart-loop with MongoServerSelectionError: Server selection timed out and e no go finish boot.

How much RAM Rocket.Chat self-hosted need?

Plan for 4 GB as minimum wey go work well, and 8 GB if the team big. Rocket.Chat Node process dey use about 1 to 1.5 GB and MongoDB dey take roughly half of the remaining RAM for its WiredTiger cache. So, for 2 GB box, the two go fight and out-of-memory killer go kill mongod if load heavy, wey go show Killed for logs and exit code 137. 2 GB na only for testing software with small number of test users.

How I go put Rocket.Chat behind HTTPS?

Run reverse proxy for the same VPS wey dey terminate TLS and forward am to 127.0.0.1:3000, and set the container ROOT_URL to your public https:// address. The proxy must forward the WebSocket upgrade headers or login go hang. Certbot with nginx na simplest setup for single-app; Traefik na better option if you dey run many containers behind one proxy and you want automatic certificate management.

How I go back up my self-hosted Rocket.Chat?

Use mongodump take do consistent database dump instead of to just copy the volume: docker compose exec -T mongodb mongodump --db rocketchat --archive --gzip > backup.archive.gz. That archive carry users, channels, messages, and settings, plus uploaded files if you keep storage for GridFS. Copy am out of the server, use cron to automate am every night, and try mongorestore for another box so you go know say the restore dey work.

How I go upgrade Rocket.Chat without break MongoDB?

Upgrade Rocket.Chat one major version at a time — e dey run migrations for boot and e no go allow you skip major versions — and read the release notes for every version before you change the pinned image tag. Check which MongoDB versions your target release support with curl -s https://releases.rocket.chat/<version>/info | jq .compatibleMongoVersions, and when you dey move MongoDB, step one major version at a time and set setFeatureCompatibilityVersion with confirm: true after every step. Always take mongodump first.