Rocket.Chat self-hosted con Docker Compose
Installa Rocket.Chat su VPS con Docker Compose: configura il replica set MongoDB a nodo singolo, TLS e backup. Risolvi gli errori più comuni.
Cosa stai realizzando
Una chat privata per il team, sotto il tuo completo controllo: Rocket.Chat eseguito sul tuo VPS tramite Docker Compose, con terminazione TLS e tutti i messaggi archiviati in un database MongoDB di cui puoi eseguire il backup e che puoi spostare. Rocket.Chat è un'alternativa open source consolidata a Slack e Teams, con canali, messaggi diretti, thread, condivisione di file e chiamate vocali e video, tutto sull'hardware che noleggi e amministri. L'applicazione è un singolo container e si avvia in pochi minuti. La maggior parte dei problemi effettivi riguarda il database che la supporta. Per questo gran parte della guida è dedicata a MongoDB e, in particolare, all'unico requisito che sorprende quasi tutti la prima volta: Rocket.Chat non funziona con un'istanza MongoDB standalone. Richiede un replica set, anche se il "set" è costituito da un solo nodo.
Prerequisiti e calcolo della RAM che spesso viene ignorato
Dimensiona il server in modo realistico. Il minimo pratico per un piccolo team è 2 vCPU e 4 GB di RAM. Il processo Node.js di Rocket.Chat richiede circa 1-1.5 GB da solo, mentre la cache WiredTiger di MongoDB utilizza per impostazione predefinita circa metà della RAM rimanente. Su un VPS da 2 GB, i due componenti funzionano all'avvio, ma entrano in conflitto non appena arriva traffico reale: MongoDB aumenta la cache, Node aumenta l'heap, il kernel esaurisce le pagine disponibili e l'out-of-memory killer termina il processo più grande, di solito mongod. Il container mostra Killed, Docker lo riavvia e il server di chat si interrompe ogni pochi minuti sotto un carico che dovrebbe gestire senza problemi. 2 GB sono sufficienti per fare una prova con due persone; non sono adatti a un server per team. Parti da 4 GB e usa 8 GB se prevedi decine di utenti simultanei, videochiamate o un archivio di upload in crescita.
Prima di iniziare devi inoltre predisporre tre elementi. Un nome di dominio con un record A che punti all'IP pubblico del VPS: le funzionalità real-time di Rocket.Chat e i client mobili richiedono un hostname stabile, non un semplice IP. Le porte 80 e 443 aperte sia nel firewall del server sia nel firewall di rete del provider, che nella maggior parte dei pannelli è un controllo separato. Infine, un VPS KVM Ubuntu 24.04 appena installato, con accesso root o sudo. Se stai ancora valutando se un server di chat sia il servizio giusto da installare per primo, la guida a ciò che vale la pena self-hostare nel 2026 illustra i compromessi.
Installare il motore Docker e il plugin Compose
Utilizzare il repository apt di Docker, non il pacchetto docker.io fornito da Ubuntu e non il vecchio binario Python standalone docker-compose. Compose moderno è un plugin Docker che si esegue come docker compose, con uno spazio, non con un trattino. Il vecchio docker-compose v1 non è più supportato e gestisce in modo errato la sintassi di healthcheck e delle dipendenze riportata di seguito.
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-pluginVerificare che entrambi i componenti siano presenti:
sudo docker version
sudo docker compose versiondocker compose version che stampa un output simile a Docker Compose version v2.x è il controllo rilevante. Se restituisce l'errore docker: 'compose' is not a docker command, il plugin non è stato installato. Correggere il problema ora per evitare errori difficili da diagnosticare in seguito.
Il file Compose: MongoDB come replica set a nodo singolo
Questa è la parte in cui si commettono più errori, quindi leggila con attenzione. Rocket.Chat usa i change stream di MongoDB per inviare in tempo reale i nuovi messaggi ai client connessi, ma i change stream sono disponibili solo su un replica set. Se punti Rocket.Chat a un'istanza standalone semplice mongod, l'applicazione si connette, non riesce ad aprire un change stream e quindi entra in un ciclo continuo di riavvii. La soluzione è semplice: esegui un normale container MongoDB, ma avvialo con --replSet e inizializza quindi un set con un solo membro.
Crea una directory di lavoro e un file 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:Alcune scelte sono deliberate. La porta di Rocket.Chat viene pubblicata su 127.0.0.1:3000, non su 0.0.0.0. L'applicazione non usa TLS, quindi dovrebbe essere raggiungibile soltanto dal reverse proxy sullo stesso server; associarla a tutte le interfacce esporrebbe direttamente su Internet una pagina di accesso in chiaro. MongoDB non viene pubblicato sull'host; è raggiungibile soltanto tramite la rete interna di Compose con il nome mongodb, che corrisponde esattamente al nome host usato da MONGO_URL. MONGO_URL contiene ?replicaSet=rs0: se lo ometti, il driver considera il server standalone anche se è un replica set, e i change stream continuano a non funzionare. MONGO_OPLOG_URL punta al database local in cui risiede l'oplog; le versioni moderne di Rocket.Chat preferiscono i change stream, ma impostare questo parametro non crea problemi e mantiene compatibili i percorsi utilizzati dal codice meno recente. depends_on usa condition: service_healthy, quindi Compose attende che MongoDB risponda a un ping prima di avviare Rocket.Chat. Questo è lo scopo dell'healthcheck.
Imposta tag di versione fissi per entrambe le immagini, mongo:8.0, e specifica una release di Rocket.Chat come 8.5.1, come in questo esempio. Non usare mai :latest: trasforma un docker pull non presidiato in un aggiornamento accidentale e non migrabile. Verifica la release stabile corrente di Rocket.Chat e le versioni di MongoDB supportate prima di fissare le versioni. Rocket.Chat pubblica un documento informativo leggibile dalle macchine per ogni release: curl -s https://releases.rocket.chat/8.5.1/info | jq '{compatibleMongoVersions, lts}' restituisce compatibleMongoVersions: ["8.0"] per la versione 8.5.1, quindi mongo:8.0 è l'unico engine supportato. Il documento include anche un flag lts che indica se la release è una build a supporto a lungo termine, utile da fissare su un server che non vuoi dover monitorare continuamente. Non tutti i progetti pubblicano un'immagine versionata. In questo caso, il vincolo si applica al codice sorgente: gestire in proprio il tracker di allenamento openGym significa effettuare il checkout di uno specifico tag Git e compilare il progetto da quello, invece di seguire un branch soggetto a modifiche.
Inizializza il replica set
Avvia lo stack:
sudo docker compose up -dRocket.Chat inizierà subito a terminare in modo anomalo e Docker continuerà a riavviarlo. È previsto, perché il replica set non esiste ancora. Crealo una sola volta, manualmente:
sudo docker compose exec mongodb mongosh --eval 'rs.initiate({_id: "rs0", members: [{_id: 0, host: "mongodb:27017"}]})'Il risultato corretto è { ok: 1 }. Entro pochi secondi il nodo singolo si elegge primary; verifica con:
sudo docker compose exec mongodb mongosh --quiet --eval 'rs.status().members[0].stateStr'Devi visualizzare PRIMARY. Il dettaglio più importante di questa pagina è l'argomento host: "mongodb:27017". Se esegui un semplice rs.initiate() senza un elenco di membri, MongoDB pubblicizza il replica set usando il nome host interno del container, un hash casuale come a1b2c3d4e5f6. Rocket.Chat, che si connette dal proprio container, non riesce a risolvere quel nome. Di conseguenza, il driver MongoDB fallisce la risoluzione DNS e continua a registrare MongoServerSelectionError: getaddrinfo ENOTFOUND a1b2c3d4e5f6 all'infinito. Inizializza sempre il replica set con il nome esplicito del servizio che corrisponde al tuo MONGO_URL.
Primo avvio: monitorare l'avvio
Quando il set è primario, il successivo riavvio di Rocket.Chat stabilisce la connessione correttamente e avvia le migrazioni iniziali. Seguire i log:
sudo docker compose logs -f rocketchatLa riga da attendere è il banner di avvio:
+--------------------------------------------+
SERVER RUNNING
Rocket.Chat Version: 8.5.1
NodeJS Version: 22.22.3 - x64
+--------------------------------------------+Il primo avvio è lento: l'applicazione esegue le migrazioni del database e crea gli indici. Attendere quindi uno o due minuti prima di verificare la presenza di problemi. Se il log ripete invece MongoServerSelectionError: Server selection timed out after 30000 ms con una descrizione della topologia di tipo ReplicaSetNoPrimary, il replica set non è stato inizializzato. Se ripete getaddrinfo ENOTFOUND con un hash casuale, è stato inizializzato con l'host errato. In entrambi i casi, tornare al passaggio precedente. Quando compare SERVER RUNNING, Rocket.Chat è in ascolto su 127.0.0.1:3000 ed è il momento di configurare un hostname reale e TLS davanti all'applicazione.
Mettilo dietro TLS
Non esporre mai Rocket.Chat tramite HTTP non cifrato. Accedi una volta tramite http:// e avrai trasmesso la password di amministrazione a chiunque si trovi sul percorso di rete. Termina TLS in un reverse proxy sullo stesso server e inoltra le richieste a 127.0.0.1:3000. Due aspetti sono essenziali: il proxy deve inoltrare gli header di upgrade WebSocket, perché Rocket.Chat funziona in tempo reale e senza questi header non funziona correttamente, e il valore di ROOT_URL del container deve corrispondere esattamente all'indirizzo HTTPS pubblico digitato dagli utenti.
Inizia con un blocco server nginx in HTTP non cifrato che faccia da proxy verso l'applicazione e inoltri gli header di upgrade. Salvalo come /etc/nginx/sites-available/rocketchat, crea un link simbolico in sites-enabled e ricarica la configurazione:
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;
}
}Per ora lascialo sulla porta 80: un blocco con listen 443 ssl; e senza certificato non supererà nemmeno sudo nginx -t. Ricarica nginx (sudo nginx -t && sudo systemctl reload nginx), quindi emetti il certificato. Su Ubuntu, la procedura più semplice è certificati TLS Let's Encrypt con Certbot e nginx: certbot --nginx riscrive in sede il blocco precedente, aggiungendo listen 443 ssl;, le righe ssl_certificate e un redirect automatico da 80 a 443, oltre a pianificare il rinnovo. Se esegui già diversi container dietro un unico proxy, Traefik con TLS automatico per molte applicazioni Docker è l'opzione più ordinata: aggiungi le label del router e del servizio al servizio rocketchat e Traefik richiederà e rinnoverà il certificato automaticamente, senza alcun blocco nginx. In entrambi i casi, imposta ROOT_URL su https://chat.example.com in compose.yml ed esegui nuovamente sudo docker compose up -d affinché il container applichi la modifica. Se vuoi rendere il server raggiungibile soltanto dalla tua rete interna e non da Internet pubblico, anteponi un VPN WireGuard self-hosted sul VPS e associa il proxy all'indirizzo del tunnel.
La procedura guidata di configurazione iniziale
Apri https://chat.example.com e Rocket.Chat ti guiderà attraverso una breve procedura guidata. Per prima cosa configura l'account amministratore inserendo nome reale, nome utente, indirizzo email e una password complessa. Questo è l'unico account esistente, quindi non perderne l'accesso. Successivamente inserisci le informazioni sull'organizzazione e sul server: nome, settore, dimensioni, nome del sito e lingua predefinita. Sono impostazioni principalmente estetiche; compilale e procedi. Infine scegli l'opzione realmente importante: registrare questo workspace con Rocket.Chat Cloud oppure mantenerlo standalone.
La registrazione abilita le notifiche push sui dispositivi mobili tramite il gateway di Rocket.Chat e il marketplace degli add-on, ma crea una relazione con il control plane cloud di Rocket.Chat. La modalità standalone mantiene il server completamente privato e senza dipendenze esterne. Tuttavia, le notifiche push su iOS e Android smettono di funzionare, perché Apple e Google non consentono a un'app compilata autonomamente di gestire i certificati push; le app ufficiali passano attraverso il gateway cloud. Scegli standalone se la privacy è l'obiettivo principale e gli utenti lavorano prevalentemente nell'app web. Scegli la registrazione se le notifiche push sui dispositivi mobili sono indispensabili. Puoi modificare questa scelta in seguito nella sezione Admin.
Bloccalo prima di invitare qualcuno
Rocket.Chat viene fornito con la registrazione pubblica attiva. Per impostazione predefinita, Registration Form è impostato su Public, quindi chiunque trovi l'URL può creare un account. Su un hostname pubblico, questo lascia il servizio esposto. Vai in Admin → Settings → Accounts → Registration e imposta Registration Form su Disabled, così puoi creare gli account manualmente o tramite un link di invito, oppure su Secret URL. Nella stessa sezione, disattiva Allow Anonymous Read e Allow Anonymous Write, a meno che tu non voglia espressamente un canale pubblico in sola lettura. Se creare ogni account manualmente è troppo laborioso e questo non è l'unico servizio a cui il team accede, configura il login OAuth di Rocket.Chat con un server SSO Authentik self-hosted, così la gestione degli ingressi e delle uscite avviene una sola volta, in un unico punto, invece che applicazione per applicazione.
Decidi anche dove salvare gli upload. Lo storage predefinito di File Upload è GridFS, che memorizza ogni immagine e allegato direttamente in MongoDB. È semplice, ma significa che il database, e ogni mongodump che esegui, aumentano continuamente di dimensione quando gli utenti incollano screenshot. In Admin → Settings → File Upload puoi spostare lo storage nel filesystem locale o in un bucket compatibile con S3 e impostare una dimensione massima ragionevole per i file. Per un team piccolo GridFS va bene; considera però che i backup diventeranno più pesanti nel tempo.
Backup con mongodump
Tutti i dati si trovano nel volume mongodb_data. Non copiare semplicemente il volume mentre il database è in esecuzione. Crea invece un dump coerente con mongodump e trasmettilo a un file sull'host:
sudo docker compose exec -T mongodb mongodump --db rocketchat --archive --gzip > rocketchat-$(date +%F).archive.gzQuesto singolo archivio compresso con gzip contiene l'intero workspace: utenti, canali, messaggi, impostazioni e, se hai lasciato gli upload su GridFS, anche i file. Se hai spostato gli upload nel filesystem o su S3, esegui il backup di questo archivio separatamente. Per ripristinare i dati su uno stack nuovo, inizializza prima il replica set, quindi:
sudo docker compose exec -T mongodb mongorestore --archive --gzip --drop < rocketchat-2026-07-15.archive.gzCopia l'archivio fuori dal server, su object storage, su un altro server o in qualsiasi altra posizione in cui la perdita del VPS non elimini anche il backup, ed esegui il dump ogni notte tramite cron. Un backup che non hai mai ripristinato è una speranza, non un backup. Esegui una volta il ripristino su un VPS usa e getta, così verifichi che funzioni prima di averne bisogno.
Aggiornamenti: blocca i tag, leggi le note e rispetta la matrice MongoDB
Due regole rendono gli aggiornamenti prevedibili. La prima è aggiornare Rocket.Chat una versione principale alla volta. All'avvio esegue migrazioni dello schema e rifiuta deliberatamente di saltare versioni principali; se provi a passare direttamente dalla 6.x alla 8.x, si arresta con un errore di migrazione invece di danneggiare i dati. Aggiorna il tag dell'immagine all'ultima release della versione principale successiva, leggi le note di quella release per individuare modifiche incompatibili, esegui docker compose up -d e monitora i log fino al completamento della migrazione prima di procedere. La seconda è rispettare la matrice di supporto di MongoDB. Ogni release di Rocket.Chat supporta un insieme specifico di versioni di MongoDB e curl -s https://releases.rocket.chat/<version>/info | jq .compatibleMongoVersions indica quali. Quando aggiorni MongoDB, ad esempio dalla 7.0 alla 8.0, procedi di una versione principale alla volta e imposta la versione di compatibilità delle funzionalità dopo ogni passaggio. Su MongoDB 8.0, questo comando richiede un confirm: true esplicito; in caso contrario, restituisce un messaggio che chiede di eseguirlo nuovamente con il flag di conferma:
sudo docker compose exec mongodb mongosh --eval 'db.adminCommand({setFeatureCompatibilityVersion: "8.0", confirm: true})'Esegui un mongodump prima di ogni aggiornamento di uno dei due componenti. Questa è l'unica misura di sicurezza necessaria.
Modalità di errore, con le stringhe esatte
Rocket.Chat entra in un ciclo di riavvio subito dopo docker compose up e docker compose logs rocketchat si riempie con MongoServerSelectionError. MongoDB è in esecuzione, ma il driver non riesce a selezionare un primary. La stringa esatta indica quale errore è stato commesso. Server selection timed out after 30000 ms con un tipo di topologia ReplicaSetNoPrimary significa che non è mai stato eseguito rs.initiate(): il replica set non ha ancora una configurazione. getaddrinfo ENOTFOUND seguito da un hash casuale significa che l'inizializzazione è stata avviata senza host: "mongodb:27017" esplicito. MongoDB ha quindi pubblicato un hostname del container non risolvibile. Eseguire la diagnosi con sudo docker compose exec mongodb mongosh --eval 'rs.status()': se restituisce l'errore MongoServerError: no replset config has been received, inizializzare il replica set; se mostra un membro il cui name è un hash casuale, ripetere l'inizializzazione usando il nome del servizio.
L'interfaccia web viene caricata, ma il login rimane in attesa e non viene mai completato. Aprire la console del browser: verrà visualizzato WebSocket connection to 'wss://chat.example.com/websocket' failed. Nella maggior parte dei casi si tratta di una mancata corrispondenza ROOT_URL oppure di un proxy che non inoltra gli header di upgrade. Verificare che ROOT_URL corrisponda all'indirizzo pubblico esatto, incluso https://, e che il blocco nginx location imposti Upgrade e Connection "upgrade" con proxy_http_version 1.1. Dopo ogni modifica, eseguire nuovamente docker compose up -d.
Un container continua a terminare e docker compose ps mostra Restarting. docker compose logs si interrompe a metà riga e sudo dmesg | tail mostra Out of memory: Killed process 12345 (mongod) generato dall'oom-killer; il codice di uscita è 137. Il server non dispone di RAM sufficiente. La soluzione corretta è usare un VPS più grande, con almeno 4 GB. Come misura temporanea, aggiungere swap e limitare la cache di MongoDB con --wiredTigerCacheSizeGB 1 nel relativo command. Tuttavia, in condizioni di carico reale, lo swap rimanda soltanto il successivo OOM:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfiledocker compose up non riesce con Error response from daemon: driver failed programming external connectivity ... bind: address already in use. Un altro processo utilizza già la porta 3000. Spesso si tratta di un container Rocket.Chat precedente che non è stato arrestato correttamente oppure di un'altra applicazione. Individuarlo con sudo ss -ltnp | grep :3000, quindi arrestare quel processo o container. In alternativa, modificare il lato host del mapping in 127.0.0.1:3001:3000 e aggiornare proxy_pass del proxy in modo coerente.
FAQ
Rocket.Chat richiede davvero un replica set MongoDB?
Sì, anche per un singolo server con un solo nodo del database. Rocket.Chat distribuisce i messaggi in tempo reale usando i change stream di MongoDB, disponibili solo con un replica set; un mongod autonomo non può aprirne uno. Non servono più macchine: è sufficiente eseguire un container MongoDB avviato con --replSet rs0 e inizializzare un set con un solo membro usando rs.initiate(). Se si salta questo passaggio, il driver non trova mai un primary, quindi Rocket.Chat entra in un ciclo continuo di riavvio con MongoServerSelectionError: Server selection timed out e non completa mai l'avvio.
Quanta RAM richiede Rocket.Chat self-hosted?
Pianifica almeno 4 GB nella pratica e 8 GB per un team con un carico elevato. Il processo Node di Rocket.Chat usa circa 1-1.5 GB e MongoDB riserva all'incirca metà della RAM restante per la cache WiredTiger. Su una macchina con 2 GB i due processi entrano quindi in conflitto e l'out-of-memory killer termina mongod sotto qualsiasi carico reale. Nei log compare Killed e il codice di uscita è 137. Due GB sono sufficienti solo per valutare il software con un paio di utenti di test.
Come posso pubblicare Rocket.Chat tramite HTTPS?
Esegui un reverse proxy sulla stessa VPS. Il proxy termina TLS e inoltra le richieste a 127.0.0.1:3000. Imposta inoltre ROOT_URL del container sull'indirizzo https:// pubblico. Il proxy deve inoltrare gli header di upgrade WebSocket, altrimenti il login si blocca. Certbot con nginx è la configurazione più semplice per una singola applicazione. Traefik è più pratico se esegui diversi container dietro un unico proxy e vuoi gestire automaticamente i certificati.
Come posso eseguire il backup di Rocket.Chat self-hosted?
Crea un dump coerente del database con mongodump invece di copiare il volume: docker compose exec -T mongodb mongodump --db rocketchat --archive --gzip > backup.archive.gz. L'archivio contiene utenti, canali, messaggi e impostazioni, oltre ai file caricati se hai lasciato lo storage su GridFS. Copialo fuori dal server, automatizza l'operazione ogni notte con cron ed esegui una mongorestore su una macchina temporanea, così verifichi che il ripristino funzioni davvero.
Come posso aggiornare Rocket.Chat senza compromettere MongoDB?
Aggiorna Rocket.Chat una versione major alla volta. Il software esegue le migration all'avvio e rifiuta di saltare versioni major. Leggi le note di ogni release prima di modificare il tag dell'immagine fissato. Verifica quali versioni di MongoDB sono supportate dalla release di destinazione usando curl -s https://releases.rocket.chat/<version>/info | jq .compatibleMongoVersions. Quando aggiorni MongoDB, procedi una versione major alla volta e imposta setFeatureCompatibilityVersion con confirm: true dopo ogni passaggio. Esegui sempre prima un mongodump.