From Ghost to Zulip: Setting Up My Local Development Environment
A complete, beginner-friendly guide to setting up a local development environment for contributing to Zulip using WSL2.

Introduction
Setting up Zulip locally is not a typical Django setup.
After contributing to Ghost, I wanted to challenge myself with a larger backend system, one that reflects real-world complexity. Zulip stood out because of its scale, architecture, and active open-source community.
Zulip is built using:
Django
Tornado
PostgreSQL
RabbitMQ
While the setup process is well-documented, I still faced some issues, especially around Python virtual environments and WSL configuration.
In this post, I’ll walk through:
How I set up Zulip locally on WSL2
The issues I faced during setup
How I fixed them, including the tricky
activate_this.pyproblem
If you’re setting up Zulip for the first time, this guide should save you time and effort.
Who This Post Is For
This post is helpful if you are:
Contributing to Zulip for the first time
Moving from smaller projects to large backend systems
Using WSL2 on Windows
Facing Python 3.12 or
activate_this.pyissues during setup
Prerequisites for Zulip Local Setup
Before starting, make sure your system meets these requirements.
General Requirements
2GB or more RAM
Stable internet connection
GitHub account
Windows Requirements
Windows 10 or 11 (64-bit)
Virtualisation enabled (VT-x / AMD-V)
Administrator access
Git and GitHub Setup
If Git is already configured, you can skip this step.
Generate an SSH key:
ssh-keygen -t ed25519 -C "your_email@example.com"
Copy the public key:
cat ~/.ssh/id_ed25519.pub
Add it to:
GitHub → Settings → SSH and GPG Keys
This allows you to clone Zulip securely.
Setting Up WSL2 for Zulip Development
If you’re already using native Ubuntu or WSL2, you can skip this section.
Enable Virtualization
Enable VT-x / AMD-V in your BIOS.
Install WSL2
wsl --install
This installs Ubuntu automatically.
Use a Fresh WSL Instance
A clean WSL setup helps avoid conflicts with existing Python or Node installations.
Enable systemd (Important)
Zulip relies on system services like PostgreSQL, Redis, and RabbitMQ.
Edit the config file:
sudo nano /etc/wsl.conf
Add:
[boot]
systemd=true
Restart WSL:
wsl --shutdown
Install Required Services
Update your system:
sudo apt update && sudo apt upgrade
Install required services:
sudo apt install rabbitmq-server memcached redis-server postgresql
Configure RabbitMQ
Edit the config file:
sudo nano /etc/rabbitmq/rabbitmq-env.conf
Add:
NODE_IP_ADDRESS=127.0.0.1
NODE_PORT=5672
Important WSL Tips
Use WSL’s Native Disk
Avoid working inside /mnt/c/....
Always work from:
cd ~
Generate SSH Key Inside WSL
ssh-keygen -t ed25519 -C "your_email@example.com"
cat ~/.ssh/id_ed25519.pub
Add this key to GitHub as well.
Cloning the Zulip Repository
Fork Zulip
Go to:
https://github.com/zulip/zulip
Click Fork.
Clone Your Fork
git clone --config pull.rebase git@github.com:YOURUSERNAME/zulip.git
cd zulip
git remote add -f upstream https://github.com/zulip/zulip.git
Running Zulip Locally
Install Dependencies
This step installs all required dependencies and may take 5–10 minutes.
./tools/provision
Activate the Virtual Environment
source .venv/bin/activate
Start the Development Server
./tools/run-dev
Visit:
http://localhost:9991
Common Zulip Setup Issues (and Fixes)
Issue 1: Missing activate_this.py
Error
FileNotFoundError: No such file or directory ... activate_this.py
Why This Happens
Zulip now uses uv, a modern Python package manager.
Unlike virtualenv, it does not automatically create activate_this.py.
Some scripts still expect this file.
Fix
Manually create it:
cat > .venv/bin/activate_this.py << 'EOF'
import os
import site
import sys
bin_dir = os.path.dirname(os.path.abspath(__file__))
os.environ["PATH"] = os.pathsep.join([bin_dir] + os.environ.get("PATH", "").split(os.pathsep))
base = os.path.dirname(bin_dir)
os.environ["VIRTUAL_ENV"] = base
site_packages = os.path.join(base, 'lib', 'python{}.{}'.format(*sys.version_info[:2]), 'site-packages')
prev = set(sys.path)
site.addsitedir(site_packages)
sys.real_prefix = sys.prefix
sys.prefix = base
new = list(sys.path)
sys.path[:] = [i for i in new if i not in prev] + [i for i in new if i in prev]
EOF
Issue 2: Django Not Found (Python 3.12)
Error
ModuleNotFoundError: No module named 'django'
Root Cause
Older scripts used:
sys.version[:3]
With Python 3.12, this incorrectly returns "3.1".
Fix
Use:
'{}.{}'.format(*sys.version_info[:2])
Then re-run:
./tools/provision
Key Learnings from Setting Up Zulip
Technical Learnings
Modern tools like UV change long-standing Python assumptions
Understanding virtual environment internals is useful for debugging
A proper WSL2 + systemd setup is critical for backend services
Soft Learnings
Large codebases require patience
Error messages often point to the real issue
Zulip’s documentation and community are extremely helpful
Conclusion
Moving from Ghost to Zulip has been a rewarding step in my open-source journey.
Zulip’s development environment is complex but well-architected, making it a great project for anyone who wants to grow in backend engineering.
If you’re stuck on setup, especially around activate_this.py you’re not alone. Once the environment is running, contributing becomes much smoother.
Resources
Zulip GitHub: https://github.com/zulip/zulip
Dev Setup Docs: https://zulip.readthedocs.io/en/latest/development/overview.html
Zulip Community: https://chat.zulip.org
WSL2 Docs: https://docs.microsoft.com/en-us/windows/wsl
UV Package Manager: https://github.com/astral-sh/uv




