Rocket.Chat у Docker Compose: self-hosting на VPS
Запустіть Rocket.Chat на VPS через Docker Compose: налаштуйте replica set MongoDB з одного вузла, TLS і резервні копії, а також виправте типові помилки.
Що ви створюєте
Приватний командний чат, який повністю належить вам: Rocket.Chat працює на вашому VPS у Docker Compose, TLS завершується на проксі, а всі повідомлення зберігаються в базі даних MongoDB, яку можна резервно копіювати та переносити. Rocket.Chat — зріла альтернатива Slack і Teams з відкритим кодом. Вона підтримує канали, особисті повідомлення, гілки, обмін файлами, голосовий зв’язок і відеозв’язок. Усе працює на обладнанні, яке ви орендуєте та контролюєте. Застосунок складається з одного контейнера й запускається за кілька хвилин. Усе, що фактично може працювати неправильно, пов’язане з базою даних поруч із ним. Тому більша частина цього посібника присвячена MongoDB, зокрема одній вимозі, яка вперше дивує майже всіх: Rocket.Chat не працює з автономним екземпляром MongoDB. Йому потрібен replica set, навіть якщо цей «набір» складається з одного вузла.
Передумови та розрахунок RAM, про який зазвичай не говорять
Обирайте конфігурацію сервера реалістично. Практичний мінімум для невеликої команди — 2 vCPU і 4 GB RAM. Власне процес Rocket.Chat на Node.js потребує приблизно 1–1.5 GB, а кеш WiredTiger у MongoDB за замовчуванням займає близько половини доступної RAM, що залишилася. На VPS із 2 GB обидва компоненти запускаються, але щойно надходить реальний трафік, вони починають конкурувати за пам’ять: MongoDB збільшує кеш, Node збільшує heap, ядру бракує сторінок пам’яті, а OOM killer завершує процес, який займає найбільше пам’яті, зазвичай mongod. У контейнері з’являється повідомлення Killed, Docker перезапускає його, і під навантаженням, яке сервер мав би легко витримувати, чат стає недоступним кожні кілька хвилин. 2 GB достатньо, щоб протестувати систему з двома користувачами, але для командного сервера цього замало. Почніть із 4 GB, а якщо очікуєте десятки одночасних користувачів, відеодзвінки або накопичення історії завантажень, виділіть 8 GB.
До початку роботи також підготуйте три речі. Потрібне доменне ім’я з A-записом, що вказує на публічну IP-адресу VPS. Для функцій реального часу Rocket.Chat і мобільних клієнтів потрібне стабільне ім’я хоста, а не звичайна IP-адреса. Порти 80 і 443 мають бути відкриті у firewall сервера та в мережевому firewall вашого провайдера. У більшості панелей це окремі елементи керування. Також потрібен чистий Ubuntu 24.04 KVM VPS із root або sudo. Якщо ви ще вирішуєте, чи варто починати саме з chat server, ознайомтеся з посібником про те, що варто self-hosting у 2026 році, де розглянуто компроміси.
Встановіть Docker Engine і плагін Compose
Використовуйте власний apt-репозиторій Docker, а не пакет docker.io, який постачається з Ubuntu, і не застарілий окремий бінарний файл docker-compose на Python. Сучасний Compose — це плагін Docker, який викликають як docker compose, через пробіл, а не через дефіс. Старий 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, плагін не встановлено. Виправте це зараз, щоб уникнути незрозумілих помилок надалі.
Файл compose: MongoDB як одновузловий набір реплік
Саме тут часто припускаються помилок, тому читайте уважно. Rocket.Chat використовує потоки змін MongoDB, щоб передавати нові повідомлення підключеним клієнтам у реальному часі. Потоки змін доступні лише в наборі реплік. Якщо вказати Rocket.Chat звичайний автономний mongod, він підключиться, не зможе відкрити потік змін і назавжди зациклиться на перезапуску. Виправлення просте: запустіть один звичайний контейнер MongoDB із --replSet, а потім ініціалізуйте набір з одним учасником.
Створіть робочий каталог і 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. Якщо його не вказати, драйвер вважатиме сервер автономним, навіть якщо він є набором реплік, і потоки змін усе одно не працюватимуть. MONGO_OPLOG_URL вказує на базу даних local, де зберігається oplog. Сучасний Rocket.Chat надає перевагу потокам змін, але це налаштування не заважає роботі й забезпечує сумісність зі старішими шляхами виконання коду. depends_on використовує condition: service_healthy, тому Compose очікує, доки MongoDB відповість на ping, і лише після цього запускає Rocket.Chat. Для цього й потрібна перевірка стану.
Зафіксуйте точні теги версій для обох образів: 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}' повертає compatibleMongoVersions: ["8.0"] для 8.5.1, тому mongo:8.0 є єдиним підтримуваним рушієм. Документ також містить прапорець lts, який показує, чи є цей реліз довгостроковою версією підтримки, яку варто зафіксувати для сервера, що не потребує постійного адміністрування. Не кожен проєкт публікує образ із версією. У такому разі версію потрібно зафіксувати в джерельному коді: self-hosting трекера тренувань openGym означає перейти на конкретний git-тег і зібрати код із нього, а не стежити за гілкою, яка постійно змінюється.
Ініціалізуйте replica set
Запустіть stack:
sudo docker compose up -dRocket.Chat одразу почне аварійно завершувати роботу, а Docker постійно перезапускатиме його. Це очікувано, оскільки replica set ще не створено. Створіть його один раз вручну:
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". Якщо виконати звичайний rs.initiate() без списку members, MongoDB оголосить replica set під внутрішнім hostname контейнера — випадковим хешем на кшталт a1b2c3d4e5f6. Rocket.Chat, підключаючись зі свого контейнера, не зможе розв’язати це ім’я. Через це MongoDB driver не зможе виконати DNS-розв’язання і безкінечно циклічно записуватиме в журнал MongoServerSelectionError: getaddrinfo ENOTFOUND a1b2c3d4e5f6. Завжди ініціалізуйте replica set із явним іменем service, яке відповідає вашому MONGO_URL.
Перший запуск: відстежуйте запуск
Після того як набір стане primary, під час наступного перезапуску Rocket.Chat успішно підключиться та розпочне міграції під час першого запуску. Переглядайте журнали:
sudo docker compose logs -f rocketchatПотрібний вам рядок — банер запуску:
+--------------------------------------------+
SERVER RUNNING
Rocket.Chat Version: 8.5.1
NodeJS Version: 22.22.3 - x64
+--------------------------------------------+Перший запуск повільний: застосунок виконує міграції бази даних і створює індекси, тому зачекайте хвилину-дві, перш ніж починати пошук проблеми. Якщо в журналі натомість повторюється MongoServerSelectionError: Server selection timed out after 30000 ms з описом топології типу ReplicaSetNoPrimary, replica set не ініціалізовано; якщо повторюється getaddrinfo ENOTFOUND із випадковим хешем, його ініціалізовано з неправильним хостом. У будь-якому разі поверніться на один крок назад. Коли побачите SERVER RUNNING, Rocket.Chat почне прослуховувати 127.0.0.1:3000, і настане час налаштувати перед ним справжнє hostname та TLS.
Розмістіть його за TLS
Ніколи не відкривайте Rocket.Chat через звичайний HTTP. Якщо один раз увійти через http://, пароль адміністратора стане доступним будь-кому на мережевому шляху. Завершуйте TLS у reverse proxy на тому самому сервері та передавайте запити до 127.0.0.1:3000. Важливі дві речі: проксі має передавати заголовки оновлення WebSocket, оскільки Rocket.Chat працює в реальному часі й без них не працюватиме, а значення ROOT_URL у контейнері має точно відповідати публічній HTTPS-адресі, яку вводять користувачі.
Почніть зі звичайного HTTP server block nginx, який проксуватиме запити до застосунку та передаватиме заголовки оновлення. Збережіть його як /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 — TLS-сертифікати Let's Encrypt за допомогою Certbot і nginx: certbot --nginx змінює наведений вище блок на місці, додає listen 443 ssl;, рядки ssl_certificate і автоматичне перенаправлення з 80 на 443, а також налаштовує автоматичне поновлення. Якщо ви вже запускаєте кілька контейнерів за одним проксі, зручнішим варіантом буде Traefik з автоматичним TLS для багатьох Docker-застосунків: додайте labels маршрутизатора й сервісу до сервісу rocketchat, і Traefik сам запитуватиме та поновлюватиме сертифікат, без жодного блока nginx. У будь-якому разі встановіть ROOT_URL у https://chat.example.com в compose.yml і повторно виконайте sudo docker compose up -d, щоб контейнер отримав це значення. Якщо сервер має бути доступним лише з вашої внутрішньої мережі, а не з публічного інтернету, розмістіть перед ним self-hosted WireGuard VPN на VPS і прив’яжіть проксі до адреси тунелю.
Майстер початкового налаштування
Відкрийте https://chat.example.com, і Rocket.Chat проведе вас через короткий майстер. Спочатку налаштуйте обліковий запис адміністратора: вкажіть справжнє ім’я, ім’я користувача, адресу електронної пошти та надійний пароль. Це єдиний наявний обліковий запис, тому не втрачайте ці дані. Далі введіть інформацію про організацію та сервер: назву, галузь, розмір, назву сайту й мову за замовчуванням. Це косметичні параметри, тому заповніть їх і продовжуйте. Потім виберіть параметр, який справді має значення: зареєструвати цей робочий простір у Rocket.Chat Cloud або залишити його автономним.
Реєстрація вмикає push-сповіщення для мобільних пристроїв через шлюз Rocket.Chat і доступ до marketplace додатків, але створює зв’язок із control plane Rocket.Chat Cloud. Автономний режим зберігає сервер повністю приватним і не залежним від зовнішніх сервісів. Однак push-сповіщення в iOS та Android перестають працювати, оскільки Apple і Google не дозволяють самостійно зібраним застосункам зберігати push-сертифікати, а офіційні застосунки передають сповіщення через хмарний шлюз. Виберіть автономний режим, якщо приватність є головною вимогою, а користувачі працюють у вебзастосунку. Виберіть реєстрацію, якщо push-сповіщення на мобільних пристроях необхідні. Пізніше вибір можна змінити в розділі Admin.
Обмежте доступ, перш ніж запрошувати користувачів
У Rocket.Chat за замовчуванням увімкнена open registration, а параметр Registration Form має значення Public, тому будь-хто, хто знайде URL, може створити обліковий запис. На публічному hostname це відкритий доступ. Перейдіть до Admin → Settings → Accounts → Registration і встановіть для Registration Form значення Disabled, щоб створювати облікові записи вручну або за посиланням-запрошенням, або значення Secret URL. Там само вимкніть Allow Anonymous Read і Allow Anonymous Write, якщо вам спеціально не потрібен публічний канал лише для читання. Якщо ручне створення кожного облікового запису здається вам незручним і це не єдиний сервіс, у який входить ваша команда, налаштуйте OAuth login у Rocket.Chat через self-hosted Authentik SSO-сервер, щоб додавання та видалення користувачів виконувалося в одному місці, а не окремо для кожного застосунку.
Також визначте, де зберігатимуться завантажені файли. За замовчуванням сховище File Upload використовує GridFS, яке зберігає всі зображення та вкладення безпосередньо в MongoDB. Це просто, але означає, що база даних і кожна mongodump, яку ви створюєте, безперервно збільшуються, коли користувачі вставляють знімки екрана. У розділі Admin → Settings → File Upload можна перемкнути сховище на локальну файлову систему або S3-compatible bucket і встановити обмеження максимального розміру файлу. Для невеликої команди GridFS підходить, але врахуйте, що з часом резервні копії займатимуть більше місця.
Резервне копіювання за допомогою mongodump
Усі ваші дані зберігаються у томі mongodb_data. Не копіюйте том безпосередньо під час роботи бази даних. Створіть узгоджений дамп за допомогою mongodump і передайте його у файл на хості:
sudo docker compose exec -T mongodb mongodump --db rocketchat --archive --gzip > rocketchat-$(date +%F).archive.gzЦей один стиснений за допомогою gzip архів містить увесь робочий простір: користувачів, канали, повідомлення, налаштування та, якщо ви залишили завантаження у GridFS, також файли. Якщо ви перенесли завантаження у файлову систему або S3, створюйте резервну копію цього сховища окремо. Для відновлення в новому стеку спочатку ініціалізуйте набір реплік, а потім виконайте:
sudo docker compose exec -T mongodb mongorestore --archive --gzip --drop < rocketchat-2026-07-15.archive.gzСкопіюйте архів за межі сервера: у об’єктне сховище, на інший сервер або в будь-яке інше місце, де вихід VPS з ладу не знищить резервну копію. Запускайте створення дампу щоночі через cron. Резервна копія, з якої ви жодного разу не виконували відновлення, — це лише надія, а не резервна копія. Один раз перевірте відновлення на тимчасовому VPS, щоб переконатися, що воно працює, перш ніж це знадобиться.
Оновлення: фіксуйте теги, читайте примітки, дотримуйтеся матриці сумісності MongoDB
Два правила роблять оновлення передбачуваними. По-перше, оновлюйте 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, переходьте по одній мажорній версії за раз і після кожного кроку встановлюйте feature-compatibility version. У 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 rocketchat заповнюється значенням MongoServerSelectionError. MongoDB працює, але драйвер не може вибрати primary, а точний рядок помилки вказує, яку саме помилку допущено. Server selection timed out after 30000 ms із типом топології ReplicaSetNoPrimary означає, що ви не виконали rs.initiate(), тому набір ще не має конфігурації. getaddrinfo ENOTFOUND, після якого вказано випадковий хеш, означає, що ініціалізацію виконано без явного host: "mongodb:27017", тому MongoDB оголосила ім’я контейнера, яке неможливо розпізнати. Діагностуйте проблему за допомогою sudo docker compose exec mongodb mongosh --eval 'rs.status()': якщо команда повертає помилку MongoServerError: no replset config has been received, ініціалізуйте набір; якщо вона показує учасника, у якого name має значення випадкового хешу, повторіть ініціалізацію, використавши ім’я сервісу.
Вебінтерфейс завантажується, але індикатор входу працює без кінця, і вхід не завершується. Відкрийте консоль браузера. Ви побачите WebSocket connection to 'wss://chat.example.com/websocket' failed. Майже завжди причина полягає в невідповідності ROOT_URL або в тому, що проксі не передає заголовки для оновлення з’єднання. Переконайтеся, що ROOT_URL точно відповідає публічній адресі, включно з https://, а ваш блок nginx location встановлює Upgrade і Connection "upgrade" зі значенням proxy_http_version 1.1. Після будь-якої зміни повторно виконайте docker compose up -d.
Контейнер постійно завершує роботу, а docker compose ps показує, що він Restarting. docker compose logs обривається посередині рядка, а sudo dmesg | tail показує Out of memory: Killed process 12345 (mongod) від oom-killer; код завершення — 137. На сервері недостатньо оперативної пам’яті. Правильне рішення — використати більший VPS, щонайменше на 4 GB. Як тимчасовий захід додайте swap і обмежте кеш MongoDB за допомогою --wiredTigerCacheSizeGB 1 у його command, але swap лише відтермінує наступне завершення через OOM під реальним навантаженням:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfiledocker compose up завершується з помилкою Error response from daemon: driver failed programming external connectivity ... bind: address already in use. Порт 3000 уже зайнятий, часто попереднім контейнером Rocket.Chat, який завершився некоректно, або іншим застосунком. Знайдіть процес за допомогою sudo ss -ltnp | grep :3000, зупиніть цей процес або контейнер або змініть порт на стороні хоста у відображенні на 127.0.0.1:3001:3000 і відповідно оновіть proxy_pass у конфігурації проксі.
FAQ
Чи справді Rocket.Chat потребує replica set MongoDB?
Так, навіть якщо на одному сервері працює один вузол бази даних. Rocket.Chat доставляє повідомлення в реальному часі за допомогою change streams MongoDB. Change streams доступні лише для replica set, тому standalone mongod не може відкрити такий потік. Кілька машин не потрібні: достатньо запустити один контейнер MongoDB з --replSet rs0 та ініціалізувати set з одним учасником за допомогою rs.initiate(). Якщо пропустити цей крок, драйвер не знаходить primary, тому Rocket.Chat зациклено перезапускається з MongoServerSelectionError: Server selection timed out і не завершує запуск.
Скільки RAM потрібно для self-hosted Rocket.Chat?
Практичний мінімум — 4 GB, а для активної команди плануйте 8 GB. Процес Node у Rocket.Chat використовує приблизно від 1 до 1.5 GB, а MongoDB займає близько половини доступної RAM під кеш WiredTiger. Тому на сервері з 2 GB ці процеси конкурують за пам’ять, і OOM killer завершує mongod під реальним навантаженням. У журналах з’являється Killed, а код завершення має значення 137. Двох GB достатньо лише для оцінювання програмного забезпечення з кількома тестовими користувачами.
Як розмістити Rocket.Chat за HTTPS?
Запустіть reverse proxy на тому самому VPS. Він має завершувати TLS і переспрямовувати запити до 127.0.0.1:3000. Для контейнера встановіть ROOT_URL у значення вашої публічної адреси https://. Проксі має передавати заголовки оновлення WebSocket, інакше вхід до системи зависатиме. Certbot з nginx — найпростіший варіант для одного застосунку. Traefik зручніший, якщо за одним проксі працює кілька контейнерів і потрібне автоматичне керування сертифікатами.
Як створити резервну копію self-hosted Rocket.Chat?
Створіть узгоджений дамп бази даних за допомогою mongodump, а не копіюйте volume: docker compose exec -T mongodb mongodump --db rocketchat --archive --gzip > backup.archive.gz. Цей архів містить користувачів, канали, повідомлення та налаштування, а також завантажені файли, якщо сховище залишилося на GridFS. Скопіюйте архів із сервера, автоматизуйте щоденне створення за допомогою cron і перевірте mongorestore на тимчасовому сервері, щоб переконатися, що відновлення справді працює.
Як оновити Rocket.Chat без проблем із MongoDB?
Оновлюйте Rocket.Chat по одній major-версії за раз. Під час запуску він виконує міграції та відмовляється пропускати major-версії. Перед зміною pinned image tag прочитайте примітки до відповідного релізу. Перевірте, які версії MongoDB підтримує цільовий реліз, за допомогою curl -s https://releases.rocket.chat/<version>/info | jq .compatibleMongoVersions. Під час оновлення MongoDB переходьте по одній major-версії за раз і після кожного переходу встановлюйте setFeatureCompatibilityVersion за допомогою confirm: true. Завжди спочатку створюйте mongodump.