Zulip в Docker Compose: готовый файл
В большом чате Slack или Mattermost обсуждение трёх параллельных вопросов превращается в одну ленту, где сообщения перемешаны и непонятно, кто на что отвечает. Zulip решает эту проблему иначе: каждое сообщение живёт внутри темы (topic) внутри канала (stream), поэтому пять разговоров в одном канале не сливаются в кашу — их видно и читаешь отдельно, в любом порядке. Для команд от 20-30 человек и выше это ощутимо меняет качество асинхронного общения. Ниже — рабочий docker-compose.yml для self-hosted Zulip на своём сервере, с HTTPS, первым запуском и бэкапом.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Почему Zulip, а не Slack-клон
Тредовая модель — не единственное отличие, но самое заметное. В Slack и Mattermost канал — это одна лента сообщений; тема появляется только как "ответ в треде", который легко потерять. В Zulip тема — обязательный атрибут каждого сообщения: при отправке вы указываете и канал, и тему, и после это видно как отдельную нить в списке слева. Заходя в канал через день, вы видите не 200 вперемешку прокрученных сообщений, а список тем — прочитал нужные, остальные свернул.
Второе отличие — Zulip изначально спроектирован как self-hosted продукт с полноценным open-source ядром (Apache 2.0), а не урезанная community-версия коммерческого продукта. Из коробки в нём есть организации (realms) — можно держать несколько независимых пространств на одном сервере, каждое на своём поддомене.
Минусы тоже стоит назвать честно: порог входа для новых пользователей выше — привычка "просто писать в канал" ломается, пока не привыкнешь указывать тему. И официальный docker-образ тяжелее Mattermost — внутри него сразу несколько процессов (веб-сервер, Tornado для realtime, очереди воркеров), поэтому и требования к ресурсам выше.
Архитектура: пять контейнеров вместо двух
В отличие от Mattermost, где хватает приложения и Postgres, официальный стек Zulip (docker-zulip) состоит из пяти сервисов:
- zulip — само приложение: Django-бэкенд, Tornado для WebSocket/long-polling, очереди задач и встроенный nginx, который сам терминирует TLS;
- database — PostgreSQL (используется отдельный образ
zulip/zulip-postgresqlс нужными расширениями для полнотекстового поиска); - rabbitmq — очередь сообщений, через неё Zulip асинхронно обрабатывает отправку писем, пуши, индексацию;
- redis — кэш сессий и rate-limiting;
- memcached — кэш объектов приложения (профили пользователей, настройки каналов).
Важная особенность: контейнер zulip сам поднимает nginx внутри себя и слушает 80/443 напрямую — отдельный внешний реверс-прокси не обязателен, если вы готовы отдать серверу порты 80/443 целиком под Zulip. Это удобно для выделенного сервера под мессенджер и неудобно, если на том же хосте уже висит nginx под другие сайты — тогда придётся либо выносить Zulip на отдельный IP, либо ставить внешний прокси перед встроенным (об этом — в разделе про HTTPS).
Ресурсы: по официальным рекомендациям минимум — 2 vCPU и 4 ГБ RAM для команды до 100 человек, но на практике база и RabbitMQ ощутимо едят память при старте, поэтому для комфортной работы закладывайте 4 ГБ как нижнюю границу, а не как запас. На команду в 200+ человек — 4 vCPU / 8 ГБ и SSD под базу, полнотекстовый поиск Zulip активно нагружает диск.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверГотовый docker-compose.yml
Создайте рабочую директорию и сгенерируйте секреты — Zulip требует несколько случайных паролей для внутренних сервисов:
mkdir -p zulip && cd zulip
openssl rand -hex 32 # secret_key
openssl rand -hex 24 # postgres_password
openssl rand -hex 24 # rabbitmq_password
openssl rand -hex 24 # redis_password
Сохраните вывод четырёх команд — они понадобятся в .env. Файл .env:
# внутренние секреты (вставьте значения из openssl выше)
SECRET_KEY=вставьте_secret_key
POSTGRES_PASSWORD=вставьте_postgres_password
RABBITMQ_PASSWORD=вставьте_rabbitmq_password
REDIS_PASSWORD=вставьте_redis_password
# домен и админ
EXTERNAL_HOST=zulip.example.com
ZULIP_ADMIN_EMAIL=admin@example.com
# почта для приглашений и уведомлений
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=zulip@example.com
SMTP_PASSWORD=пароль_smtp
Файл docker-compose.yml:
services:
database:
image: zulip/zulip-postgresql:14
restart: unless-stopped
env_file: .env
environment:
POSTGRES_DB: zulip
POSTGRES_USER: zulip
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- ./volumes/postgresql:/var/lib/postgresql/data
networks:
- zulip
memcached:
image: memcached:1.6-alpine
restart: unless-stopped
command: memcached -m 512
networks:
- zulip
rabbitmq:
image: rabbitmq:3.12-alpine
restart: unless-stopped
env_file: .env
environment:
RABBITMQ_DEFAULT_USER: zulip
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD}
RABBITMQ_NODENAME: rabbit@localhost
volumes:
- ./volumes/rabbitmq:/var/lib/rabbitmq
networks:
- zulip
redis:
image: redis:7-alpine
restart: unless-stopped
env_file: .env
command: sh -c 'redis-server --requirepass "$$REDIS_PASSWORD"'
volumes:
- ./volumes/redis:/data
networks:
- zulip
zulip:
image: zulip/docker-zulip:latest
restart: unless-stopped
depends_on:
- database
- memcached
- rabbitmq
- redis
env_file: .env
environment:
DB_HOST: database
DB_HOST_PORT: 5432
DB_USER: zulip
SETTING_MEMCACHED_LOCATION: memcached:11211
SETTING_RABBITMQ_HOST: rabbitmq
SETTING_REDIS_HOST: redis
SECRETS_secret_key: ${SECRET_KEY}
SECRETS_postgres_password: ${POSTGRES_PASSWORD}
SECRETS_rabbitmq_password: ${RABBITMQ_PASSWORD}
SECRETS_redis_password: ${REDIS_PASSWORD}
SETTING_EXTERNAL_HOST: ${EXTERNAL_HOST}
SETTING_ZULIP_ADMINISTRATOR: ${ZULIP_ADMIN_EMAIL}
SSL_CERTIFICATE_GENERATION: self-signed
SETTING_EMAIL_HOST: ${SMTP_HOST}
SETTING_EMAIL_PORT: ${SMTP_PORT}
SETTING_EMAIL_HOST_USER: ${SMTP_USER}
SECRETS_email_password: ${SMTP_PASSWORD}
SETTING_EMAIL_USE_TLS: "yes"
ports:
- "80:80"
- "443:443"
volumes:
- ./volumes/zulip:/data
networks:
- zulip
networks:
zulip:
driver: bridge
Тег zulip/docker-zulip:latest в проде лучше зафиксировать на конкретной версии (посмотрите актуальные теги на Docker Hub перед деплоем) — так апгрейд происходит осознанно, командой, а не сам собой при пересоздании контейнера.
SSL_CERTIFICATE_GENERATION: self-signed даёт встроенному nginx самоподписанный сертификат для старта — этого достаточно, чтобы поднять сервис и проверить работу, но браузер будет ругаться на "небезопасное соединение". Для реального сертификата смотрите раздел про HTTPS ниже.
Первый запуск и создание организации
Поднимаете стек и ждёте, пока применятся миграции базы — при первом старте это занимает пару минут:
docker compose up -d
docker compose logs -f zulip
В логах должна появиться строка о готовности nginx слушать порт. Дальше — ключевое отличие от Mattermost: в Zulip нельзя просто зайти на сайт и зарегистрироваться, создание первой организации требует ссылки, сгенерированной вручную из контейнера:
docker compose exec -u zulip zulip \
/home/zulip/deployments/current/manage.py generate_realm_creation_link
Команда выведет одноразовую ссылку вида https://zulip.example.com/new/xxxxxxxx — откройте её в браузере, укажите название организации, поддомен и создайте аккаунт администратора. Именно так задаётся первая realm; для дополнительных организаций на том же сервере генерируется новая ссылка той же командой.
HTTPS: свой сертификат или внешний прокси
Проще всего дать встроенному nginx получить сертификат Let's Encrypt самому — домен должен уже указывать на IP сервера, порт 80 открыт:
environment:
SSL_CERTIFICATE_GENERATION: certbot
SETTING_ZULIP_ADMINISTRATOR: admin@example.com
После смены значения пересоздайте контейнер zulip (docker compose up -d zulip) — при старте он сам запросит сертификат через certbot и настроит автопродление внутри себя.
Если на сервере уже работает внешний nginx или Caddy под другие сайты и порты 80/443 заняты, встроенный сервер Zulip лучше отключить и проксировать на внутренний порт. Общая схема — в статье про nginx как обратный прокси; для Zulip добавьте проброс WebSocket (Upgrade/Connection), иначе живые обновления в интерфейсе будут обрываться. Автоматический SSL без ручного certbot проще получить через Caddy с авто-SSL — конфиг там короче.
Резервное копирование
У Zulip есть встроенная утилита бэкапа, которая корректно сохраняет и базу, и загруженные файлы в один архив — это надёжнее, чем вручную собирать pg_dump и volume отдельно, потому что учитывает специфику схемы Zulip:
docker compose exec -u zulip zulip \
/home/zulip/deployments/current/manage.py backup
Архив сохраняется внутри контейнера в /data/backups/ — не забудьте скопировать его на хост или сразу в удалённое хранилище:
docker compose exec zulip ls /data/backups/
docker cp $(docker compose ps -q zulip):/data/backups/. ./backups/
Для регулярного автоматического бэкапа оберните обе команды в скрипт и повесьте на cron с ротацией старых архивов. Если нужен более гибкий инструмент с версионированием и дедупликацией (например, если бэкапите сразу несколько сервисов на сервере), общий подход разобран в статье про BorgBackup в Docker Compose — архив Zulip можно включить в тот же репозиторий бэкапа.
Восстановление на том же или новом сервере:
docker compose exec -u zulip zulip \
/home/zulip/deployments/current/manage.py restore /data/backups/имя_архива.tar.gz
Перед восстановлением на чистый сервер стек должен быть поднят (все пять контейнеров запущены), но организация ещё не создана — restore сам развернёт базу и файлы из архива.
Обновление и обслуживание
Обновление образа — обычный цикл docker compose, но перед ним обязателен свежий бэкап через manage.py backup, потому что апгрейд Zulip прогоняет миграции базы, и откат возможен только из бэкапа:
# сначала бэкап (см. выше)
docker compose pull zulip
docker compose up -d zulip
docker compose logs -f zulip # проверить, что миграции прошли без ошибок
Zulip выпускает мажорные релизы нечасто — переходить на каждую промежуточную версию не обязательно, но и перепрыгивать через несколько мажорных версий за раз официально не поддерживается: если сервер давно не обновлялся, обновляйтесь последовательно, проверяя логи после каждого шага.
Из практических моментов эксплуатации:
- держите базу, RabbitMQ и Redis в отдельной bridge-сети без публикации портов наружу — в конфиге выше у них нет секции
ports, наружу торчат только 80/443 контейнераzulip; - периодически проверяйте
docker compose logs zulip | grep -i error— Zulip логирует в общий поток и ошибки очередей, и проблемы с отправкой почты; - если сервер тормозит при росте числа пользователей, сначала проверьте память RabbitMQ и PostgreSQL — они первыми упираются в лимиты при росте истории сообщений;
- пароли в
.env— единственное место, где они лежат в открытом виде; ограничьте права на файл (chmod 600 .env).
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Чем Zulip принципиально отличается от Mattermost или Rocket.Chat, если оба тоже self-hosted?
Ключевое отличие — обязательные темы (topics) внутри каналов вместо плоской ленты. Остальное сравнимо по функциям (каналы, интеграции, API, боты); выбор часто определяется тем, нужна ли команде тредовая модель или привычнее линейный чат, как в Mattermost или Rocket.Chat.
Обязательно ли использовать встроенный nginx Zulip, или можно поставить свой прокси перед ним?
Не обязательно. Встроенный nginx удобен, если сервер целиком отдан под Zulip; если на хосте уже есть свой прокси под другие сайты, отключите публикацию 80/443 у контейнера zulip и проксируйте на его внутренний порт через ваш nginx или Caddy.
Что делать, если нужно несколько независимых команд на одном сервере?
Использовать несколько организаций (realms) — каждая создаётся отдельной ссылкой через generate_realm_creation_link и живёт на своём поддомене того же EXTERNAL_HOST. Отдельно поднимать второй стек контейнеров для этого не нужно.
Zulip точно бесплатный и без ограничений по числу пользователей в self-hosted версии?
Основной сервер (Zulip Server) — open source под Apache 2.0, без лимитов на пользователей и каналы. Платная облачная версия Zulip Cloud — отдельный продукт, к self-hosted установке отношения не имеет.
Сколько занимает первый запуск и почему контейнер zulip долго не отвечает?
При первом старте применяются миграции базы и инициализация полнотекстового поиска — на слабом диске это может занять несколько минут. Если контейнер не отвечает дольше 5-10 минут, смотрите docker compose logs zulip на предмет ошибок подключения к database или rabbitmq.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →