---
title: "Deploy Zulip"
description: "Team chat where every conversation gets its own named topic"
category: "Other"
url: https://railway.com/deploy/zulip-chat
---

# Deploy Zulip

Team chat where every conversation gets its own named topic

**[Deploy Zulip on Railway](https://railway.com/template/zulip-chat)**

- **Creator:** A3A
- **Category:** Other
- **Total deploys:** 1

## Template content

### memcached https://cdn.worldvectorlogo.com/logos/memcached.svg

- **Image:** memcached:alpine
- **Start command:** `/bin/sh -euc 'echo "mech_list: plain" > "$SASL_CONF_PATH"; printf "zulip@localhost:%s\nzulip@%s:%s\n" "$MEMCACHED_PASSWORD" "$(hostname)" "$MEMCACHED_PASSWORD" > "$MEMCACHED_SASL_PWDB"; exec memcached -S -m 512 -c 4096'`

### Redis https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/redis.svg

- **Image:** redis:8.2
- **Start command:** `/bin/sh -c "rm -rf $RAILWAY_VOLUME_MOUNT_PATH/lost+found/ && exec docker-entrypoint.sh redis-server --requirepass $REDIS_PASSWORD --save 60 1 --dir $RAILWAY_VOLUME_MOUNT_PATH"`

### postgres https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/postgresql.svg

- **Image:** zulip/zulip-postgresql:14
- **Start command:** `docker-entrypoint.sh postgres -c shared_buffers=512MB -c effective_cache_size=1536MB -c work_mem=8MB -c maintenance_work_mem=128MB`

### rabbitmq https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/rabbitmq.svg

- **Image:** rabbitmq:4.2-management
- **Start command:** `/bin/sh -euc 'mkdir -p /etc/rabbitmq/conf.d; MEM=$(cat /sys/fs/cgroup/memory.max 2>/dev/null || echo max); case "$MEM" in max|"") MEM=2147483648;; esac; { echo "default_user = $RABBITMQ_DEFAULT_USER"; echo "default_pass = $RABBITMQ_DEFAULT_PASS"; echo "total_memory_available_override_value = $MEM"; echo "consumer_timeout = 86400000"; } > /etc/rabbitmq/conf.d/10-railway.conf; exec docker-entrypoint.sh rabbitmq-server'`
- **Health check:** /

### mailpit https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mailpit.svg

- **Image:** axllent/mailpit:latest
- **Health check:** /livez
- **Public domain:** Yes

### zulip https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/zulip.svg

- **Source:** https://github.com/gridalpha/zulip-railway
- **Public domain:** Yes

## Documentation

![Zulip logo](https://static.zulipchat.com/static/images/landing-page/hello/generated/screen-1-2x.jpg)

# Deploy and Host Zulip on Railway

Zulip is an open-source team chat application built around a threading model no other chat tool copies well: every message belongs to a named *topic* inside a channel, so a busy channel reads like a set of short email threads rather than one scrolling firehose. Open-source projects, research groups and distributed engineering teams pick it over Slack for that reason — someone who was asleep for eight hours can skim topic names and read only the conversations that concern them. It ships web, desktop and mobile clients, a REST API, 100+ integrations, and history no retention paywall truncates.

Self-host Zulip on Railway and this template gives you the whole production topology, not a single container that quietly drops half of Zulip's features. The `zulip` service runs the Django application, the Tornado event server, the queue worker and nginx under one supervisor tree, with a volume at `/data` for uploads and secrets. Behind it sit `postgres` (Zulip's own PostgreSQL build, carrying the `pgroonga` extension its search depends on), `rabbitmq` for event queues, `memcached` for the object cache, managed `Redis` for rate limiting and presence, and `mailpit` to capture outgoing mail. Only the app and the Mailpit inbox get public URLs.

![Diagram of the Zulip service topology on Railway with its five backing services](https://res.cloudinary.com/rroe4rtk/image/upload/v1787724497/zulip-architecture.png)

## Getting Started with Zulip on Railway

Set `ZULIP_ADMIN_EMAIL` and `ZULIP_ADMIN_PASSWORD` before deploying — those become the organization owner's credentials, and are the only two values the template needs from you. The first boot runs Zulip's migrations and then creates your organization and its owner, which takes a few minutes; the deployment goes healthy once `/health` can reach PostgreSQL, RabbitMQ, Redis and memcached. Open the public URL, sign in, and accept the one-time prompt about email visibility. You land in `#general` with a Welcome Bot conversation waiting.

The first useful thing to do is start a conversation: pick a channel, click **Start new conversation**, name a topic such as `deployment`, and send a message. Type a word from it into the search box — matches come back highlighted, the quickest confirmation that PostgreSQL and its search extension are wired up. Invite your team from the right sidebar; the invitation lands in the bundled Mailpit inbox, which has its own URL behind the credentials in `MP_UI_AUTH`. Before inviting real users, point `SETTING_EMAIL_HOST` at a real SMTP relay.

![Zulip channel conversation threaded under a named topic](https://res.cloudinary.com/rroe4rtk/image/upload/v1787724498/zulip-channel-conversation.png)
![Zulip full-text search highlighting matches across a channel](https://res.cloudinary.com/rroe4rtk/image/upload/v1787724499/zulip-message-search.png)
![Zulip organization settings panel for automated messages](https://res.cloudinary.com/rroe4rtk/image/upload/v1787724500/zulip-organization-settings.png)
![Mailpit inbox holding Zulip's outgoing notification emails](https://res.cloudinary.com/rroe4rtk/image/upload/v1787724501/zulip-mailpit-inbox.png)

## About Hosting Zulip

Channels hold topics, topics hold messages, and every view — inbox, recent conversations, combined feed — is built on that hierarchy. Teams self-host Zulip when they want history on infrastructure they control, when per-seat pricing stops making sense, or when compliance keeps conversations out of a SaaS.

- Topic-based threading in every channel, with per-topic mute, resolve and follow
- Unlimited searchable history, with operators for sender, channel, topic and attachments
- 100+ integrations, incoming and outgoing webhooks, interactive bots and a REST API
- LaTeX, syntax-highlighted code, polls and to-do lists in messages
- Email, LDAP, SAML, OpenID Connect and social authentication backends
- Importers for Slack, Mattermost, Rocket.Chat and Teams exports

PostgreSQL stores messages, users and the full-text index. RabbitMQ carries internal event queues — outgoing email, notifications, link previews, thumbnails — so slow work never blocks a request. memcached caches rendered messages and user objects, Redis backs rate limiting and presence, and Mailpit stands in for an SMTP relay so nothing is half-configured on day one.

## Why Deploy Zulip on Railway

Railway removes the parts of a Zulip install that normally take an afternoon:

- The five-service topology is provisioned and wired on the private network
- TLS, a public domain and HTTP-to-HTTPS redirects are handled at the edge
- Volumes for uploads, database and queue state are attached and mounted
- The health check exercises all four backing services on every probe
- Vertical scaling is a slider, not a migration

## Common Use Cases

- **Open-source community chat** — public channels with permanent, searchable history
- **Distributed engineering teams** — topics that survive timezone gaps instead of scrolling away
- **Incident and on-call coordination** — one topic per incident, resolved when the incident is
- **Research groups and classrooms** — LaTeX rendering and long-form threads

## Dependencies for Zulip

- `gridalpha/zulip-railway` — app service, built on `ghcr.io/zulip/zulip-server:12.2-0`
- `zulip/zulip-postgresql:14` — PostgreSQL 14 with `pgroonga` and Zulip's search schema
- `rabbitmq:4.2-management` — event queues for email, notifications, previews
- `memcached:alpine` — object cache, authenticated with SASL
- `redis:8.2` — rate limiting and user presence
- `axllent/mailpit:latest` — outgoing mail capture with a browsable inbox

### Environment Variables Reference

| Variable | Purpose |
|---|---|
| `ZULIP_ADMIN_EMAIL` | Organization owner login, made on first boot |
| `ZULIP_ADMIN_PASSWORD` | Password for that owner account |
| `SETTING_EXTERNAL_HOST` | Hostname Zulip serves; matches the public domain |
| `LOADBALANCER_IPS` | Proxy ranges trusted for forwarded headers |
| `SETTING_EMAIL_HOST` | SMTP host; point at a real relay |
| `ZULIP_AUTH_BACKENDS` | Comma-separated authentication backends |
| `CONFIG_application_server__uwsgi_processes` | Web worker count; raise with more RAM |

### Deployment Dependencies

- [Zulip source](https://github.com/zulip/zulip) and [server docs](https://zulip.readthedocs.io/)
- [Official image and Compose files](https://github.com/zulip/docker-zulip)
- [Environment variable reference](https://zulip.readthedocs.io/projects/docker/en/latest/reference/environment-vars.html)
- [REST API reference](https://zulip.com/api/)

## Hardware Requirements for Self-Hosting Zulip

| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 2 vCPU | 4 vCPU |
| RAM | 2 GB | 4 GB and up |
| Storage | 5 GB volume | 20 GB and up, per upload volume |
| Runtime | PostgreSQL 14, RabbitMQ, memcached, Redis | Same, each on its own service |

Zulip's guidance is 2 GB of RAM for a small server and 4 GB for a busy one; here the app container settles around 1.8 GB. Raise `CONFIG_application_server__uwsgi_processes` and the memory limit together as the organization grows — Zulip sizes that count from total system memory, which is wrong inside a container.

## Self-Hosting Zulip with Docker

Zulip publishes an official image and a Compose file. To pull the server image:

```
docker pull ghcr.io/zulip/zulip-server:12.2-0
```

Running it by hand means standing up PostgreSQL, RabbitMQ, memcached and Redis yourself, then passing each connection through `SETTING_*` and `SECRETS_*` variables. Compose is shorter:

```
git clone https://github.com/zulip/docker-zulip.git
cd docker-zulip
cp compose.override.yaml.example compose.override.yaml
docker compose up -d
```

Behind a reverse proxy, leave `CERTIFICATES` unset so Zulip serves plain HTTP and list the proxy's addresses in `LOADBALANCER_IPS` — Zulip refuses forwarded headers from an unlisted address and answers with a configuration error page.

## How Much Does Zulip Cost to Self-Host?

Zulip Server is free and open source under the Apache 2.0 licence, with no seat limits, no history cap and no paid feature tier — the self-hosted build is the same software Zulip Cloud runs. Cloud charges per user per month above its free plan, so a team of twenty saves real money hosting it. On Railway you pay only for the compute, memory and storage the six services use.

## FAQ

**What is Zulip?**
An open-source team chat application whose channels are subdivided into named topics, so conversations stay separated and readable even when a channel is busy. It has web, desktop and mobile clients and unlimited searchable history.

**Why does Zulip need its own PostgreSQL image?**
Zulip's search uses the `pgroonga` extension and a schema created when the database is first initialized. A stock PostgreSQL image has neither, so search fails on a server that otherwise looks healthy.

**Why are RabbitMQ, memcached and Redis included?**
RabbitMQ queues background work — outgoing email, notifications, link previews — so it never blocks a web request. memcached caches rendered messages and user objects, and Redis backs rate limiting and presence. Zulip expects all three.

**How do I send real email from self-hosted Zulip?**
Point `SETTING_EMAIL_HOST`, `SETTING_EMAIL_PORT`, `SETTING_EMAIL_HOST_USER` and `SECRETS_email_password` at your SMTP provider and set `SETTING_EMAIL_USE_TLS` to `True`. Until then Mailpit captures it.

**How do I add users to my Zulip organization?**
Signup is invite-only by default. Use **Invite users** in the right sidebar to email an invitation or generate a reusable link — a link works even before a real SMTP relay is configured.


## Similar templates

- [Rocky Linux](https://railway.com/deploy/rocky-linux) — Hosted Rocky Linux 9 workspace with SSH and persistent storage. 🚀
- [Foundry Virtual Tabletop](https://railway.com/deploy/X5tR6G) — A Self-Hosted & Modern Roleplaying Platform
- [Letta Code Remote](https://railway.com/deploy/letta-code-remote) — Run a Letta Code agent 24/7. No inbound ports, just deploy.

Open this page in a browser: https://railway.com/deploy/zulip-chat
