Deploy Zulip — Self-Hosted Topic-Based Team Chat
Self-host Zulip — async team chat organized by topics
PostgreSQL
Just deployed
/var/lib/postgresql/data
Redis
Just deployed
/data
Zulip
Just deployed
/data
RabbitMQ
Just deployed
/var/lib/rabbitmq
Memcached
Just deployed
Deploy and Host Zulip on Railway
Zulip is the open-source team chat built around topics — a Slack alternative designed for asynchronous, distributed teams. Unlike Slack, Mattermost, or Teams, where messages flow in one chronological stream, every Zulip message lives under a named topic within a channel. You can follow, catch up on, or mute individual conversations without scrolling an entire channel — the reason remote teams across time zones prefer it. This template deploys Zulip's full five-service stack, correctly wired with matched secrets, so it works on the first deploy.
What This Template Deploys
| Service | Purpose |
|---|---|
| Zulip | The chat application server, web UI, and API |
| PostgreSQL | Messages and data, on Zulip's full-text-search Postgres image |
| RabbitMQ | Async task queue — email, notifications, webhooks |
| Memcached / Redis | Object caching, rate limiting, and session caching |
All five run on Railway's private network; only Zulip is public. This is the same architecture Zulip's own maintainers ship — the complexity is what enables the real-time features and reliability.
About Hosting
Zulip is the most capable self-hosted async chat, and its two defining traits — the topic model and the five-service stack — are worth understanding.
Topics are the whole point — and why async teams choose it. In Zulip, every message belongs to a named topic inside a channel, so a channel is a set of threaded conversations rather than one endless stream. Someone catching up hours later reads only the topics that matter and mutes the rest, instead of scrolling a firehose. For distributed teams spanning time zones, this changes how communication scales. If your team is co-located and wants a flat Slack-style feed, a simpler chat server fits better; if you're async, Zulip is purpose-built for you.
Five services with matched secrets — the wiring that trips up manual setups. Zulip runs as an app server plus PostgreSQL, Memcached, RabbitMQ, and Redis. The critical detail: each backend service's password must match the corresponding SECRETS_* value in the Zulip container — Postgres, RabbitMQ, Memcached, and Redis passwords are all paired, and a single mismatch breaks startup. This template generates and matches all of them, the exact step a hand-rolled deploy gets wrong.
Set SECRETS_secret_key and keep it stable. Zulip needs a 64-character Django secret key (openssl rand -hex 32) to sign sessions. This template generates it; keep it stable across redeploys, or active sessions are invalidated.
SETTING_EXTERNAL_HOST must be your Railway domain, with DISABLE_HTTPS=True. Zulip builds links against SETTING_EXTERNAL_HOST, so it must be your Railway domain. Set DISABLE_HTTPS=True because Railway terminates TLS at its edge — Zulip serves HTTP internally while Railway provides HTTPS.
It's the heaviest chat option — size it right. Five services and a 2 GB RAM minimum make Zulip more resource-hungry than simpler chat servers. Allocate accordingly and scale as your team grows.
Typical cost: ~$15–25/month on Railway across the five services. Zulip is Apache-2.0 licensed and free — no per-user fees, ever.
How It Compares
| Zulip (self-hosted) | Slack | Mattermost | Microsoft Teams | |
|---|---|---|---|---|
| Conversation model | Topic threads | Chronological | Chronological | Chronological |
| Best for | Async / distributed | Real-time teams | Slack-style self-host | Enterprise |
| Cost model | Flat infra | Per user | Flat infra | Per user |
| Integrations | 120+ | Many | Many | Many |
| Data ownership | Full — your infra | Vendor | Full | Microsoft |
| Self-hostable | Yes | No | Yes | No |
Slack and Teams are polished but bill per user and keep your data on their servers. Mattermost is a great self-hosted choice if you want a familiar flat Slack-style feed with a lighter footprint. Zulip is different by design: its topic-threading model is purpose-built for asynchronous, distributed teams who catch up on specific conversations across time zones — self-hosted, with 120+ integrations, at flat cost.
Deploy in Under 5 Minutes
- Click Deploy on Railway — all five services build and wire together with matched secrets (~5 minutes)
- Confirm
SETTING_EXTERNAL_HOSTis your Railway domain andDISABLE_HTTPS=True - Confirm
SECRETS_secret_keyis set and the backend passwords are matched (handled by the template) - Open your Railway domain and create your organization and first admin account
- Create channels, define topics, and invite your team
Set the SETTING_EMAIL_* variables to enable invitations and notifications.
Common Use Cases
- Async chat for distributed teams — topic threading so people across time zones catch up on what matters
- Slack alternative without per-user fees — unlimited members at flat infrastructure cost
- Open-source communities — large public communities use Zulip's topics to keep discussions organized
- Data-sovereign team chat — messages and files stay entirely on your own infrastructure
Configuration
| Variable | Required | Description |
|---|---|---|
SETTING_EXTERNAL_HOST | Required | Your Railway public domain — used for links and request validation |
DISABLE_HTTPS | Required | True — Railway terminates TLS, so Zulip serves HTTP internally |
SECRETS_secret_key | Required | 64-char Django secret (openssl rand -hex 32) — keep stable |
SETTING_ZULIP_ADMINISTRATOR | Recommended | Admin email — receives server notifications, becomes first admin |
SECRETS_postgres_password | Auto-matched | Must match the PostgreSQL service password |
SECRETS_rabbitmq_password / _memcached_password / _redis_password | Auto-matched | Must match their service passwords |
SETTING_EMAIL_* | Optional | SMTP settings for invitations and notifications |
The backend passwords must match, and the secret key must be stable. Each
SECRETS_*password has to equal its service's password, or Zulip won't start — this template matches all of them.SECRETS_secret_keysigns sessions; keep it constant across redeploys.
Set the external host and disable HTTPS in-app.
SETTING_EXTERNAL_HOSTmust be your Railway domain, andDISABLE_HTTPS=Truebecause Railway handles TLS at its edge.
Dependencies for Zulip Hosting
- Railway account — ~$15–25/month across the five services
- PostgreSQL (Zulip's search image), Memcached, RabbitMQ, and Redis (all included and wired)
- At least 2 GB RAM — Zulip is the heaviest self-hosted chat option
- Optional: SMTP credentials for invitations and email notifications
Deployment Dependencies
- Zulip GitHub Repository
- Zulip Docker Documentation
- Zulip Production Settings
- Railway Private Networking
Implementation Details
The template runs the official ghcr.io/zulip/zulip-server image alongside four backend services: PostgreSQL (Zulip's full-text-search image), Memcached with SASL auth, RabbitMQ for the async task queue, and Redis for rate limiting and caching. All five communicate over the private network, with only the Zulip app exposed.
The defining requirement is secret matching: each backend password must equal the corresponding SECRETS_postgres_password, SECRETS_rabbitmq_password, SECRETS_memcached_password, and SECRETS_redis_password in the Zulip container, and a mismatch prevents startup. This template pairs all of them, along with the 64-character SECRETS_secret_key that signs sessions and must stay stable. SETTING_EXTERNAL_HOST is set to the Railway domain and DISABLE_HTTPS=True because Railway terminates TLS while Zulip serves HTTP internally. The entrypoint generates config from these variables on first boot and runs migrations. Messages persist in PostgreSQL — the component to back up. The five-service architecture and 2 GB RAM minimum make Zulip the most resource-intensive self-hosted chat, a trade for its topic-threading model.
Frequently Asked Questions
How is Zulip different from Slack or Mattermost? Zulip organizes messages by named topics within channels, not one chronological stream. This makes catching up on specific conversations easy for async, distributed teams — where flat Slack-style chat forces you to scroll everything. Mattermost is the better pick if you specifically want a familiar flat feed.
Why won't a manual Zulip deploy start? Usually mismatched secrets — each backend password must equal its SECRETS_* value in the Zulip container. This template matches them automatically, the most common manual-setup failure.
Why DISABLE_HTTPS=True? Railway terminates TLS at its edge, so Zulip serves plain HTTP internally while Railway provides HTTPS externally. SETTING_EXTERNAL_HOST must be your Railway domain for links to resolve.
Does my data persist? Yes. Messages live in PostgreSQL, which survives redeploys. Back it up regularly since it holds your team's history.
How many integrations does it have? Over 120, plus a full API and webhooks, so alerts and events route into the relevant topics.
Why Deploy Zulip on Railway?
Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.
By deploying Zulip on Railway you get topic-based team chat with the hard part solved — all five services wired, the backend secrets matched, the external host and TLS settings correct, and automatic HTTPS. Async-first communication with 120+ integrations, self-hosted on infrastructure you own.
Template Content
PostgreSQL
zulip/zulip-postgresql:14Redis
redis:8.2.1SETTING_EMAIL_HOST
SMTP host
SETTING_EMAIL_PORT
SMTP port (587 for TLS)
SECRETS_email_password
SMTP password
SETTING_EMAIL_HOST_USER
SMTP username
SETTING_ZULIP_ADMINISTRATOR
Admin email
SETTING_NOREPLY_EMAIL_ADDRESS
Sender email
RabbitMQ
rabbitmq:4.0Memcached
memcached:alpine