SSD Nodes Learn Hosting plans →
How to do am Matt ConnorBy Matt Connor · Updated 2026-08-26

How to Host Rocket.Chat with Docker Compose for Naija

Run Rocket.Chat for your VPS with Docker Compose, but no miss the MongoDB replica set requirement. This guide covers TLS, backups, and common errors wey fit stop am.

Wetin you dey build

Na private team chat wey na you own fully: Rocket.Chat wey dey run for your own VPS under Docker Compose, TLS don terminate for am, and every message dey sit inside MongoDB database wey you fit back up and move. Rocket.Chat na mature open-source alternative to Slack and Teams. E get channels, direct messages, threads, file sharing, voice, and video. Everything dey run for hardware wey you rent and control. The application na one container wey fit come up within minutes. Most things wey fit go wrong dey inside the database beside am. Na why most of this guide focus on MongoDB, especially the one requirement wey dey surprise almost everybody the first time: Rocket.Chat no go run with standalone MongoDB. E need replica set, even if that “set” na just one node.

Prerequisites, and the RAM math wey nobody dey tell you

Size the box honestly. The realistic minimum for small team na 2 vCPU and 4 GB of RAM. Rocket.Chat Node.js process need roughly 1 to 1.5 GB by itself, and MongoDB WiredTiger cache dey claim about half of the RAM wey remain by default. For 2 GB VPS, both of dem fit start together, but real traffic go make dem clash: MongoDB cache go grow, Node heap go grow, kernel go run out of pages, and out-of-memory killer go kill whichever process big pass, usually mongod. Container go print Killed, Docker go restart am, and chat server go dey drop every few minutes under load wey e suppose handle easily. 2 GB dey okay to test am with two people; e no be enough for team server. Start with 4 GB, and use 8 GB if you expect dozens of concurrent users, video calls, or upload history wey dey grow.

You also need three things ready before you start. A domain name with an A record wey point to the VPS public IP. Rocket.Chat real-time features and mobile clients need stable hostname, not bare IP. Ports 80 and 443 open for both server firewall and your provider network firewall. For most panels, na separate control be that. And a fresh Ubuntu 24.04 KVM VPS with root or sudo. If you still dey decide whether chat server dey make sense as the first service to run, the guide about wetin worth self-hosting for 2026 explain the tradeoffs.

Install Docker engine and Compose plugin

Use Docker own apt repository, no be the docker.io package wey Ubuntu ship, and no be the old standalone docker-compose Python binary. Modern Compose na Docker plugin wey you invoke as docker compose, with space, no hyphen. The old docker-compose v1 don reach end-of-life and e no handle the healthcheck and dependency syntax wey dey below.

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 parts dey present:

sudo docker version
sudo docker compose version

docker compose version wey print something like Docker Compose version v2.x na the check wey matter. If e show error like docker: 'compose' is not a docker command, the plugin no install and you go soon get confusing failures later. Fix am here.

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

This na the part wey people dey get wrong, so read am slowly. Rocket.Chat dey use MongoDB change streams to push new messages go connected clients in real time, and change streams dey available only for a replica set. If you point Rocket.Chat to ordinary standalone mongod, e go connect, fail to open change stream, then enter restart loop forever. The fix no be anything strange: you go run one ordinary MongoDB container, but start am with --replSet, then initialise one-member set.

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

Some choices here na deliberate. Dem publish the Rocket.Chat port to 127.0.0.1:3000, no be 0.0.0.0. The app itself no get TLS, so na only the reverse proxy for the same machine suppose reach am; if you bind am to every interface, e go put plain-text login page directly for public internet. MongoDB no dey published to the host at all. E dey reachable only through Compose internal network under the name mongodb, and na exactly the hostname wey MONGO_URL dey use. MONGO_URL carry ?replicaSet=rs0. If you leave am out, the driver go treat the server as standalone even though e be replica set, and change streams go still fail. MONGO_OPLOG_URL point to local database where the oplog dey. Modern Rocket.Chat prefer change streams, but setting am no cause problem and e keep older code paths working. depends_on use condition: service_healthy, so Compose go wait until MongoDB answer ping before e start Rocket.Chat. Na this the healthcheck dey do.

Pin real version tags for both images: mongo:8.0 and one clear Rocket.Chat release like 8.5.1. Never use :latest. E fit turn an unattended docker pull into upgrade wey happen by mistake and wey you no fit migrate safely. Check the current stable Rocket.Chat release and the MongoDB versions wey e support before you pin. Rocket.Chat dey publish 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 the only supported engine. E also get lts flag wey tell you whether the release na long-term-support build wey make sense to pin for server wey you no want dey monitor all the time. No be every project dey publish versioned image. For that case, put the pin for the source instead. self-hosting the openGym workout tracker mean say you check out specific git tag and build from there, instead of following branch wey dey change.

Initialize replica set

Make the stack start:

sudo docker compose up -d

Rocket.Chat go start to crash immediately, and Docker go keep restarting am. Na expected behaviour because the replica set never 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, the single node go elect itself as primary. Confirm am with:

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

You suppose see PRIMARY. The most important detail for this whole page na the host: "mongodb:27017" argument. If you run bare rs.initiate() without members list, MongoDB go advertise the replica set with the container internal hostname, a random hash like a1b2c3d4e5f6. Rocket.Chat, wey dey connect from its own container, no fit resolve that name. So the MongoDB driver DNS lookup go fail, and e go keep looping while e dey log MongoServerSelectionError: getaddrinfo ENOTFOUND a1b2c3d4e5f6. Always initiate am with the explicit service name wey match your MONGO_URL.

First boot: monitor am e start

Once the set don become primary, Rocket.Chat next restart go connect cleanly and start the 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 because the app dey run database migrations and build indexes, so give am one or two minutes before you worry. If the log dey repeat MongoServerSelectionError: Server selection timed out after 30000 ms with topology description of type ReplicaSetNoPrimary, the replica set never initiate; if e dey repeat getaddrinfo ENOTFOUND for random hash, dem initiate am with wrong host. For both cases, go back one step. Once you see SERVER RUNNING, Rocket.Chat dey listen on 127.0.0.1:3000 and na time to put real hostname and TLS in front of am.

TLS protection put am

Never expose Rocket.Chat for plain HTTP. If you log in through http:// one time, anybody wey dey along the network path fit get your admin password. Terminate TLS for a reverse proxy wey dey the same server, then forward traffic go 127.0.0.1:3000. Two things matter: proxy must forward the WebSocket upgrade headers, because Rocket.Chat dey handle real-time traffic and e go fail without dem. Also, the container ROOT_URL must exactly match the public HTTPS address wey users dey type.

Start with plain HTTP nginx server block wey proxies traffic go the app and forwards the upgrade headers. Save am as /etc/nginx/sites-available/rocketchat, create symlink enter sites-enabled, then 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. A block wey get listen 443 ssl; but no certificate no go even pass sudo nginx -t. Reload nginx (sudo nginx -t && sudo systemctl reload nginx), then issue the certificate. For Ubuntu, the cleanest option na Let's Encrypt TLS certificates with Certbot and nginx: certbot --nginx rewrites the block above directly, adds listen 443 ssl;, the ssl_certificate lines, and automatic 80-to-443 redirect. E also schedules renewal for you. If you already dey run several containers behind one proxy, Traefik with automatic TLS for many Docker apps na the tidier option. Add router and service labels to the rocketchat service, then Traefik go request and renew the certificate for you, without any nginx block. Either way, set ROOT_URL to https://chat.example.com inside compose.yml and run sudo docker compose up -d again so the container go pick up the change. If you want make people reach the server only from inside your own network, instead of the public internet, put a self-hosted WireGuard VPN on the VPS in front and bind the proxy to the tunnel address.

The first-run setup wizard

Go https://chat.example.com, and Rocket.Chat go guide you through one short wizard. First, set the admin account with real name, username, email, and strong password. Na only this account dey exist, so make you no lose am. Next, enter organisation and server info: name, industry, size, site name, and default language. Na mostly appearance settings; fill dem and continue. Then choose the option wey really matter: register this workspace with Rocket.Chat Cloud, or keep am standalone.

Registration enables mobile push notifications through Rocket.Chat gateway and the add-on marketplace. But e also creates control-plane relationship with Rocket.Chat cloud. Standalone keeps the server fully private and no depend on outside service. But iOS and Android push notifications go stop to work. Apple and Google no allow self-built app to hold the push certificates, and the official apps dey route through the cloud gateway. Choose standalone if privacy na the main reason and your users dey use the web app; choose registration if mobile push no be negotiable. You fit change this choice later under Admin.

Tranca am well before you invite anybody

Rocket.Chat dey ship with open registration on by default. Registration Form dey set to Public, so anybody wey find the URL fit create account. For public hostname, na open door be that. Go Admin → Settings → Accounts → Registration and set Registration Form to Disabled, so you fit create accounts by hand or through invite link, or set am to Secret URL. While you dey there, off Allow Anonymous Read and Allow Anonymous Write unless you specifically want public read-only channel. If creating every account by hand sound stressful and this no be the only service wey your team dey log into, point Rocket.Chat OAuth login to self-hosted Authentik SSO server, so you go handle new and removed users once for one place instead of doing am app by app.

Decide where uploads go too. The default File Upload storage na GridFS, wey dey store every image and attachment inside MongoDB itself. This one simple, but e mean say your database, and every mongodump wey you take, go keep growing as people paste screenshots. Under Admin → Settings → File Upload, you fit switch the storage to local filesystem or S3-compatible bucket, and set reasonable maximum file size. For small team, GridFS dey okay; just know say your backups go heavier as time dey go.

Backups wey use mongodump

All your data dey inside the mongodb_data volume. No just copy the volume while database still dey run. Use mongodump take make consistent dump, then stream am go file for the host:

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

That one gzipped archive na your complete workspace: users, channels, messages, settings, and, if you leave uploads for GridFS, the files too. If you move uploads go filesystem or S3, back up that store separately. To restore am for fresh 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 comot from the box go object storage, another server, anywhere wey VPS failure no go carry the backup join. Run the dump from cron every night. Backup wey you never restore na hope, no be backup. Practise the restore once for throwaway VPS, so you go know say e work before you need am.

Upgrades: tag pin, read the notes, respect the MongoDB matrix

Two rules dey make upgrades boring. First, upgrade Rocket.Chat one major version at a time. E dey run schema migrations when e boot and e deliberately refuse to jump between major versions; if you try move from 6.x straight to 8.x, e go stop with migration error instead of corrupting your data. Change the image tag to the latest release for the next major version, read the release notes for breaking changes, run docker compose up -d, and monitor the logs until migration finish before you continue. Second, respect the MongoDB support matrix. Each Rocket.Chat release support specific MongoDB versions, and curl -s https://releases.rocket.chat/<version>/info | jq .compatibleMongoVersions go tell you which ones. When you upgrade MongoDB, like from 7.0 to 8.0, move one major version at a time and set the feature-compatibility version after each step. For MongoDB 8.0, that command need explicit confirm: true, or e go refuse and show message wey tell you to run am again with the confirmation flag:

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

Take a mongodump before every upgrade of either component. Na the complete insurance policy be that.

Failure modes, with the exact strings

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

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

A container dey die repeatedly and docker compose ps dey show say e Restarting. docker compose logs dey cut off halfway through line, and sudo dmesg | tail dey show Out of memory: Killed process 12345 (mongod) from the oom-killer; the exit code na 137. The box no get enough RAM. The real fix na bigger VPS, 4 GB minimum. As temporary measure, add swap and limit MongoDB cache with --wiredTigerCacheSizeGB 1 inside its command, but swap only go 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. Most times na 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.

FAQ

Rocket.Chat really need MongoDB replica set?

Yes, even if na one server with one database node. Rocket.Chat dey deliver messages in real time with MongoDB change streams, and na only replica set get change streams feature; standalone mongod no fit open one. You no need multiple machines. Run one MongoDB container wey start with --replSet rs0, then initialise one-member set with rs.initiate(). If you skip this step, driver no go find primary, so Rocket.Chat go dey restart-loop with MongoServerSelectionError: Server selection timed out and e no go finish booting.

How much RAM self-hosted Rocket.Chat need?

Plan 4 GB as practical minimum and 8 GB for busy team. Rocket.Chat Node process dey use about 1 to 1.5 GB, while MongoDB dey claim roughly half of the remaining RAM for e WiredTiger cache. So for 2 GB machine, both go compete for memory, and out-of-memory killer go terminate mongod under any real load. Logs go show Killed and exit code 137. Two GB only enough to test the software with small number of test users.

How I fit put Rocket.Chat behind HTTPS?

Run reverse proxy for the same VPS. Make e terminate TLS and forward request go 127.0.0.1:3000. Set the container ROOT_URL to your public https:// address. The proxy must forward the WebSocket upgrade headers, otherwise login go hang. Certbot with nginx na the simplest setup for one app. Traefik cleaner if you dey run several containers behind one proxy and want automatic certificate management.

How I fit back up self-hosted Rocket.Chat?

Take consistent database dump with mongodump instead of copying the volume: docker compose exec -T mongodb mongodump --db rocketchat --archive --gzip > backup.archive.gz. That archive get users, channels, messages, and settings. E also get uploaded files if you leave storage for GridFS. Copy the archive comot from the server. Automate the backup every night with cron. Then practise a mongorestore for temporary machine so you go confirm say restore really work.

How I fit upgrade Rocket.Chat without breaking MongoDB?

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