Zulip is an open-source chat server similar to Microsoft Teams, Rocket Chat, or Slack. It is written in Python and uses Django, PostgreSQL, and JavaScript. It integrates with over 90 third-party plugins, including Github, Jira, Stripe, Zendesk, Sentry, etc. You can expand the integrations by connecting them with Zapier and IFTTT. It comes with features like private messaging, group chats, threaded conversations, custom channels, video calls, drag-and-drop file uploads, custom emojis, Giphy integration, Image and tweet previews, and many more. Zulip comes with desktop and mobile apps for every platform, making it platform-agnostic.
This tutorial teaches you how to install and configure Zulip Chat on a Rocky Linux 9 server.
Prerequisites
- A server running Rocky Linux 9.
- At least 2GB RAM if you expect less than 100 users. For 100+ users, get a 4GB RAM and 2 CPU server.
- A non-root user with sudo privileges.
- A domain name configured to point to the server,
zulip.example.com
. - Everything is updated.
$ sudo dnf update
- Few packages that your system needs.
$ sudo dnf install wget curl nano unzip yum-utils policycoreutils-python-utils -y
Some of these packages may already be installed on your system.
Step 1 – Configure Firewall
Before installing any packages, the first step is configuring the firewall to open ports for HTTP, and HTTPS. Rocky Linux uses Firewalld Firewall. Check the firewall’s status.
$ sudo firewall-cmd --state
running
The firewall works with different zones, and the public zone is the default one that we will use. List all the services and ports active on the firewall.
$ sudo firewall-cmd --zone=public --list-all
It should show the following output.
public
target: default
icmp-block-inversion: no
interfaces: enp1s0
sources:
services: cockpit dhcpv6-client ssh
ports:
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
Open the HTTP, and HTTPS ports in the firewall.
$ sudo firewall-cmd --zone=public --add-service=http
$ sudo firewall-cmd --zone=public --add-service=https
Recheck the status of the firewall.
$ sudo firewall-cmd --zone=public --list-all
You should see a similar output.
public
target: default
icmp-block-inversion: no
interfaces: enp1s0
sources:
services: cockpit dhcpv6-client http https ssh
ports:
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
Make all the changes permanent and reload the firewall to enable the changes.
$ sudo firewall-cmd --runtime-to-permanent
$ sudo firewall-cmd --reload
Step 2 – Install Docker and Docker Compose
Install the official Docker repository.
$ sudo dnf install yum-utils
$ sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
Install Docker.
$ sudo dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Enable and run the Docker daemon.
$ sudo systemctl enable docker --now
Check the status of the Docker service.
$ sudo systemctl status nginx
? nginx.service - nginx - high performance web server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: disabled)
Active: active (running) since Tue 2024-02-06 07:18:56 UTC; 5s ago
Docs: http://nginx.org/en/docs/
Process: 22762 ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf (code=exited, status=0/SUCCESS)
Main PID: 22763 (nginx)
Tasks: 5 (limit: 50339)
Memory: 4.9M
CPU: 16ms
CGroup: /system.slice/nginx.service
??22763 "nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf"
??22764 "nginx: worker process"
??22765 "nginx: worker process"
??22766 "nginx: worker process"
??22767 "nginx: worker process"
Add your system user to the Docker group to avoid using sudo
to run Docker commands.
$ sudo usermod -aG docker $(whoami)
Login again to your server after logging out to enable the change.
Step 3 – Install Nginx
For the production environment, it is recommended to run the Synapse server using an Nginx proxy.
Rocky Linux 9 ships with an older version of Nginx. You need to use the official Nginx repository to install the latest version.
Create and open the file /etc/yum.repos.d/nginx.repo
for editing.
$ sudo nano /etc/yum.repos.d/nginx.repo
Paste the following code in it.
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
Once you are finished, save the file by pressing Ctrl + X and entering Y when prompted.
We will be installing the Nginx mainline so enable the package for it.
$ sudo dnf config-manager --enable nginx-mainline
Install Nginx.
$ sudo dnf install nginx -y
Verify the installation.
$ nginx -v
nginx version: nginx/1.25.3
Enable and start the Nginx server service.
$ sudo systemctl enable nginx --now
Check the status of the service.
$ sudo systemctl status nginx
? nginx.service - nginx - high performance web server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: disabled)
Active: active (running) since Tue 2024-02-06 07:18:56 UTC; 5s ago
Docs: http://nginx.org/en/docs/
Process: 22762 ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf (code=exited, status=0/SUCCESS)
Main PID: 22763 (nginx)
Tasks: 5 (limit: 50339)
Memory: 4.9M
CPU: 16ms
CGroup: /system.slice/nginx.service
??22763 "nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf"
??22764 "nginx: worker process"
??22765 "nginx: worker process"
??22766 "nginx: worker process"
??22767 "nginx: worker process"
Step 4 – Install SSL
We need to install Certbot to generate the SSL certificate. We will use the Snapd package installer for that. Since Rocky Linux doesn’t ship with it, install the Snapd installer. It requires the EPEL (Extra Packages for Enterprise Linux) repository to work.
Install EPEL repository.
$ sudo dnf install epel-release -y
Install Snapd package.
$ sudo dnf install snapd -y
Enable and Start the Snap service.
$ sudo systemctl enable snapd --now
Install the Snap core package, and ensure that your version of Snapd is up to date.
$ sudo snap install core && sudo snap refresh core
Create necessary links for Snapd to work.
$ sudo ln -s /var/lib/snapd/snap /snap
$ echo 'export PATH=$PATH:/var/lib/snapd/snap/bin' | sudo tee -a /etc/profile.d/snapd.sh
Install Certbot.
$ sudo snap install --classic certbot
Use the following command to ensure that the Certbot command can be run by creating a symbolic link to the /usr/bin
directory.
$ sudo ln -s /snap/bin/certbot /usr/bin/certbot
Check the Certbot version.
$ certbot --version
certbot 2.8.0
Run the following command to generate an SSL Certificate.
$ sudo certbot certonly --nginx --agree-tos --no-eff-email --staple-ocsp --preferred-challenges http -m [email protected] -d zulip.example.com
The above command will download a certificate to the /etc/letsencrypt/live/zulip.example.com
directory on your server.
Generate a Diffie-Hellman group certificate.
$ sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 4096
Check the Certbot renewal scheduler service.
$ systemctl list-timers
You will find snap.certbot.renew.service
as one of the services scheduled to run.
NEXT LEFT LAST PASSED UNIT ACTIVATES ----------------------------------------------------------------------------------------------------------------------------------
Tue 2024-02-06 07:30:00 UTC 6min left Tue 2024-02-06 07:20:11 UTC 3min 41s ago sysstat-collect.timer sysstat-collect.service
Tue 2024-02-06 08:22:43 UTC 58min left Tue 2024-02-06 07:22:39 UTC 1min 13s ago dnf-makecache.timer dnf-makecache.service
Tue 2024-02-06 09:36:00 UTC 2h 12min left - - snap.certbot.renew.timer snap.certbot.renew.service
Do a dry run of the process to check whether the SSL renewal is working fine.
$ sudo certbot renew --dry-run
If you see no errors, you are all set. Your certificate will renew automatically.
Step 5 – Configure SELinux
Apply the policy to allow connections to be made to outside hosts.
$ sudo setsebool -P httpd_can_network_connect 1
Apply the policy to allow Nginx to give access to PostgreSQL.
$ sudo setsebool -P httpd_can_network_connect_db 1
Step 6 – Prepare and Configure Zulip for Installation
First, create a secret key for Zulip. Save the generated key because we will need it later.
$ openssl rand -base64 32
sLIeucGPMCNbR0LwcRhyXafXmputmtse6+EYU04+9JY=
Create a directory for the Zulip Docker Compose file and switch to it.
$ mkdir ~/docker-zulip
$ cd ~/docker-zulip
Create and open the environment file for editing.
$ nano .env
Paste the following code in it.
[email protected]
EXTERNAL_HOST=zulip.example.com
ZULIP_AUTH_BACKENDS=EmailAuthBackend
ZULIP_PUSH_NOTIFICATION_BOUNCER_URL=https://push.zulipchat.com
DISABLE_HTTPS=true
SSL_CERTIFICATE_GENERATION=self-signed
EMAIL_HOST=email-smtp.us-west-2.amazonaws.com
EMAIL_HOST_USER=AMAZONSESUSERNAME
EMAIL_PASSWORD=AMAZONSESPASSWORD
EMAIL_PORT=465
EMAIL_USE_SSL=True
EMAIL_USE_TLS=False
[email protected]
ZULIP_GIT_URL=https://github.com/zulip/zulip.git
ZULIP_GIT_REF=8.2
SECRET_KEY=sLIeucGPMCNbR0LwcRhyXafXmputmtse6+EYU04+9JY=
POSTGRES_USER=zulip
POSTGRES_DB=zulip
POSTGRES_PASSWORD=REPLACE_WITH_SECURE_POSTGRES_PASSWORD
REDIS_PASSWORD=REPLACE_WITH_SECURE_REDIS_PASSWORD
MEMCACHED_PASSWORD=REPLACE_WITH_SECURE_MEMCACHED_PASSWORD
RABBITMQ_DEFAULT_USER=zulip
RABBITMQ_DEFAULT_PASS=REPLACE_WITH_SECURE_RABBITMQ_PASSWORD
Save the file by pressing Ctrl + X and entering Y when prompted once finished.
Let us go through all the variables we defined.
- ZULIP_ADMINISTRATOR – is the primary email address of the administrator account created during installation.
- EXTERNAL_HOST – is the domain name we will use for our Zulip install.
- ZULIP_AUTH_BACKENDS – defines the method for logging in. We are using
EmailAuthBackend
which means Zulip will ask for an email address and password. There are other authentication methods that use Google, GitHub, and Apple credentials for logging in. You can find more information in the Zulip documentation and the defaultsettings.py
file. - ZULIP_PUSH_NOTIFICATION_BOUNCER_URL – is the URL Zulip will use for sending mobile push notifications. For the community version of Zulip for an organization with 10 or fewer users, push notifications are free. Your organization may still qualify for unlimited push notifications for free depending on requirements. Check Zulip billing documentation for details.
- DISABLE_HTTPS – We have set this to true to disable SSL for Zulip because we will handle it outside the container.
- SSL_CERTIFICATE_GENERATION – Since we have disabled SSL for Zulip, we need to set it to generate self-signed certificates.
- EMAIL_HOST – The SMTP email host we need for enabling Email notifications. Here we are using the Amazon SES Email service for our tutorial.
- EMAIL_HOST_USER – The SMTP email username.
- EMAIL_PASSWORD – The SMTP email password.
- EMAIL_PORT – The SMTP email port.
- EMAIL_USE_SSL – Whether the SMTP host supports SSL authentication.
- EMAIL_USE_TLS – Whether the SMTP host supports TLS authentication.
- EMAIL_NOREPLY_ADD – The no-reply email address used for sending notification emails.
- ZULIP_GIT_URL – We are using the Zulip docker image but if you want to build a custom image, you need to input the URL of the Zulip Git repository.
- ZULIP_GIT_REF – The version of Zulip to use from the Git repository for building the docker image.
- SECRET_KEY – A strong secret key Zulip needs for authentication. Enter the key we created earlier.
- POSTGRES_USER – Choose a username for your PostgreSQL user.
- POSTGRES_DB – Choose the database name for your PostgreSQL database.
- POSTGRES_PASSWORD – Choose a strong password for the PostgreSQL database.
- REDIS_PASSWORD – Choose a strong password for the Redis database.
- MEMCACHED_PASSWORD – Choose a strong password for the Memcached service.
- RABBITMQ_DEFAULT_USER – Choose a username for the RabbitMQ service.
- RABBITMQ_DEFAULT_PASS – Choose a strong password for the RabbitMQ service.
Create and open the Docker compose file for editing.$ nano docker-compose.yml
Add the following code to define the database
service.
services:
database:
image: "zulip/zulip-postgresql:14"
container_name: zulip-db
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- "postgresql-14:/var/lib/postgresql/data:rw"
networks:
network:
ipv4_address: 10.5.0.2
The database
service contains the following options:
- image – This tells Docker to pull the
zulip-postgresql:14
image. Even though the latest version of Zulip supports PostgreSQL 16, the corresponding docker image supports only PostgreSQL 14. - container_name – specifies a name for the container.
- restart – this defines the container restart policy. We have set the container to restart automatically unless it is stopped manually.
- environment – You need to set environment variables for the container. We have set three variables defining the PostgreSQL database, username, and password.
- volumes – Here we create a named volume called
postgresql-14
which points to the PostgreSQL data directory in the container. - networks – we use this to give the container a static IP address which can be used to access it from other containers.
Next, below the database
service section, add the definition for the memcached
service.
memcached:
image: "memcached:alpine"
restart: unless-stopped
container_name: zulip-memcached
command:
- "sh"
- "-euc"
- |
echo 'mech_list: plain' > "$$SASL_CONF_PATH"
echo "zulip@$$HOSTNAME:$$MEMCACHED_PASSWORD" > "$$MEMCACHED_SASL_PWDB"
echo "zulip@localhost:$$MEMCACHED_PASSWORD" >> "$$MEMCACHED_SASL_PWDB"
exec memcached -S
environment:
SASL_CONF_PATH: "/home/memcache/memcached.conf"
MEMCACHED_SASL_PWDB: "/home/memcache/memcached-sasl-db"
MEMCACHED_PASSWORD: ${MEMCACHED_PASSWORD}
networks:
network:
ipv4_address: 10.5.0.3
The memcached
service contains the following definitions specific to it:
- image – Here we will use the
memcached:alpine
image for the container. An alpine image keeps the image size down since it only ships with the bare essentials. - command – Here we will run some commands which are run post container creation to configure Memcached and set Zulip credentials. These commands also override the default commands declared by the image.
- environment – Here we set the location of the Memcached configuration file, the password database file, and the password.
- network – Here we set a unique private IP address for the docker container.
Next, add the definition for the rabbitmq
service below the memcached
section.
rabbitmq:
image: "rabbitmq:alpine"
restart: unless-stopped
container_name: zulip-rabbitmq
environment:
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
volumes:
- "rabbitmq:/var/lib/rabbitmq:rw"
networks:
network:
ipv4_address: 10.5.0.4
The rabbitmq
service contains the following definitions specific to it:
- image – Similarly as before, we will use the alpine image for the RabbitMQ container.
- environment – We use the environment section to set RabbitMQ’s default username and password.
- volumes – Here we create a named volume called
rabbitmq
which points to the RabbitMQ directory in the container. - network – Same as before, we set a unique IP address for the container.
Next, add the definition for the redis
service below the rabbitmq
section.
redis:
image: "redis:alpine"
restart: unless-stopped
container_name: zulip-redis
command:
- "sh"
- "-euc"
- |
echo "requirepass '$$REDIS_PASSWORD'" > /etc/redis.conf
exec redis-server /etc/redis.conf
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD}
volumes:
- "redis:/data:rw"
networks:
network:
ipv4_address: 10.5.0.5
The redis
service contains the following definitions specific to it:
- image – Here we will use the alpine image for the Redis container.
- command – We will use the commands to configure the Redis password and start the server with it.
- volumes – Here we create a named volume called
redis
which points to the data directory in the container. - network – Same as before, we set a unique IP address for the Redis container.
Finally, add the definition for the zulip
service below the redis
section.
zulip:
image: "zulip/docker-zulip:8.2-0"
restart: unless-stopped
container_name: zulip
ports:
- "8080:80"
environment:
DB_HOST: "database"
DB_HOST_PORT: "5432"
DB_USER: ${POSTGRES_USER}
DISABLE_HTTPS: ${DISABLE_HTTPS}
SSL_CERTIFICATE_GENERATION: ${SSL_CERTIFICATE_GENERATION}
LOADBALANCER_IPS: 10.5.0.0/16
SETTING_MEMCACHED_LOCATION: "memcached:11211"
SETTING_RABBITMQ_HOST: "rabbitmq"
SETTING_REDIS_HOST: "redis"
SECRETS_email_password: ${EMAIL_PASSWORD}
SECRETS_rabbitmq_password: ${RABBITMQ_DEFAULT_PASS}
SECRETS_postgres_password: ${POSTGRES_PASSWORD}
SECRETS_memcached_password: ${MEMCACHED_PASSWORD}
SECRETS_redis_password: ${REDIS_PASSWORD}
SECRETS_secret_key: ${SECRET_KEY}
SETTING_EXTERNAL_HOST: ${EXTERNAL_HOST}
SETTING_ZULIP_ADMINISTRATOR: ${ZULIP_ADMINISTRATOR}
SETTING_EMAIL_HOST: ${EMAIL_HOST}
SETTING_EMAIL_HOST_USER: ${EMAIL_HOST_USER}
SETTING_EMAIL_PORT: ${EMAIL_PORT}
SETTING_EMAIL_USE_SSL: ${EMAIL_USE_SSL}
SETTING_EMAIL_USE_TLS: ${EMAIL_USE_TLS}
ZULIP_AUTH_BACKENDS: ${ZULIP_AUTH_BACKENDS}
SETTING_NOREPLY_EMAIL_ADDRESS: ${EMAIL_NOREPLY_ADD}
# Uncomment this when configuring the mobile push notifications service
# SETTING_PUSH_NOTIFICATION_BOUNCER_URL: ${ZULIP_PUSH_NOTIFICATION_BOUNCER_URL}
volumes:
- "zulip:/data:rw"
ulimits:
nofile:
soft: 1000000
hard: 1048576
networks:
network:
ipv4_address: 10.5.0.6
The zulip
service contains the following definitions specific to it:
- ports – Zulip container runs nginx as a part of the process. Here we have mapped port 80 of the container to port 8080 on the host server. We will use it later to configure Nginx as a proxy manager outside the container.
- environment – Here we configure environment variables to configure Zulip. Most of the environment variables are picked from the
.env
file which we explained earlier. The database host is set to thedatabase
container service and the port to default PostgreSQL port 5432. TheLOADBALANCER_IPS
variable is set to the docker network subnet. This is required to make Zulip work from behind a reverse proxy server. TheMEMCACHED_LOCATION
is set to thememcached
service with its default port 11211. Similarly, we have set the RabbitMQ and Redis hosts to point to their respective container services. - volumes – Here we set a named volume and set it to the
/data
directory in read-write mode. - ulimits – Here we set the soft and hard limits for the resource usage of the user for the container.
- networks – Same as before, we set a unique IP address for the Zulip container.
And at last, paste the following code.
volumes:
zulip:
postgresql-14:
rabbitmq:
redis:
networks:
network:
driver: bridge
ipam:
config:
- subnet: 10.5.0.0/16
gateway: 10.5.0.1
Here we define the named volumes we created earlier for our containers. Next, we define the bridge network to connect all the containers and provide subnet and gateway IP addresses.
Once finished, save the file by pressing Ctrl + X and entering Y when prompted once finished.
The finished Docker compose file will look like the following.
services:
database:
image: "zulip/zulip-postgresql:14"
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- "postgresql-14:/var/lib/postgresql/data:rw"
networks:
network:
ipv4_address: 10.5.0.2
memcached:
image: "memcached:alpine"
restart: unless-stopped
command:
- "sh"
- "-euc"
- |
echo 'mech_list: plain' > "$$SASL_CONF_PATH"
echo "zulip@$$HOSTNAME:$$MEMCACHED_PASSWORD" > "$$MEMCACHED_SASL_PWDB"
echo "zulip@localhost:$$MEMCACHED_PASSWORD" >> "$$MEMCACHED_SASL_PWDB"
exec memcached -S
environment:
SASL_CONF_PATH: "/home/memcache/memcached.conf"
MEMCACHED_SASL_PWDB: "/home/memcache/memcached-sasl-db"
MEMCACHED_PASSWORD: ${MEMCACHED_PASSWORD}
networks:
network:
ipv4_address: 10.5.0.3
rabbitmq:
image: "rabbitmq:alpine"
restart: unless-stopped
environment:
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
volumes:
- "rabbitmq:/var/lib/rabbitmq:rw"
networks:
network:
ipv4_address: 10.5.0.4
redis:
image: "redis:alpine"
restart: unless-stopped
command:
- "sh"
- "-euc"
- |
echo "requirepass '$$REDIS_PASSWORD'" > /etc/redis.conf
exec redis-server /etc/redis.conf
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD}
volumes:
- "redis:/data:rw"
networks:
network:
ipv4_address: 10.5.0.5
zulip:
image: "zulip/docker-zulip:8.1-0"
restart: unless-stopped
#build:
# context: .
# args:
# ZULIP_GIT_URL: ${ZULIP_GIT_URL}
# ZULIP_GIT_REF: ${ZULIP_GIT_REF}
# Set this up if you plan to use your own CA certificate bundle for building
# CUSTOM_CA_CERTIFICATES: ${ZULIP_CUSTOM_CA_CERTIFICATES}
ports:
- "8080:80"
environment:
DB_HOST: 10.5.0.2
DB_HOST_PORT: "5432"
DB_USER: ${POSTGRES_USER}
DISABLE_HTTPS: ${DISABLE_HTTPS}
LOADBALANCER_IPS: 10.5.0.6
SSL_CERTIFICATE_GENERATION: ${SSL_CERTIFICATE_GENERATION}
SETTING_MEMCACHED_LOCATION: "memcached:11211"
SETTING_RABBITMQ_HOST: "rabbitmq"
SETTING_REDIS_HOST: "redis"
SECRETS_email_password: ${EMAIL_PASSWORD}
SECRETS_rabbitmq_password: ${RABBITMQ_DEFAULT_PASS}
SECRETS_postgres_password: ${POSTGRES_PASSWORD}
SECRETS_memcached_password: ${MEMCACHED_PASSWORD}
SECRETS_redis_password: ${REDIS_PASSWORD}
SECRETS_secret_key: ${SECRET_KEY}
SETTING_EXTERNAL_HOST: ${EXTERNAL_HOST}
SETTING_ZULIP_ADMINISTRATOR: ${ZULIP_ADMINISTRATOR}
SETTING_EMAIL_HOST: ${EMAIL_HOST}
SETTING_EMAIL_HOST_USER: ${EMAIL_HOST_USER}
SETTING_EMAIL_PORT: ${EMAIL_PORT}
SETTING_EMAIL_USE_SSL: ${EMAIL_USE_SSL}
SETTING_EMAIL_USE_TLS: ${EMAIL_USE_TLS}
ZULIP_AUTH_BACKENDS: ${ZULIP_AUTH_BACKENDS}
SETTING_NOREPLY_EMAIL_ADDRESS: ${EMAIL_NOREPLY_ADD}
# Uncomment this when configuring the mobile push notifications service
# SETTING_PUSH_NOTIFICATION_BOUNCER_URL: ${ZULIP_PUSH_NOTIFICATION_BOUNCER_URL}
volumes:
- "zulip:/data:rw"
ulimits:
nofile:
soft: 1000000
hard: 1048576
networks:
network:
ipv4_address: 10.5.0.6
volumes:
zulip:
postgresql-14:
rabbitmq:
redis:
networks:
network:
driver: bridge
ipam:
config:
- subnet: 10.5.0.0/16
gateway: 10.5.0.1
Step 7 – Install Zulip
Start the Zulip container using the following command.
$ docker compose up -d
Check the status of the containers using the following commands.
$ docker ps
You should see a similar output.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fea5d02f53d7 rabbitmq:alpine "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 4369/tcp, 5671-5672/tcp, 15691-15692/tcp, 25672/tcp zulip-rabbitmq
01cb77f16c1a zulip/zulip-postgresql:14 "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 5432/tcp zulip-db
f5b6523a3a8c zulip/docker-zulip:8.2-0 "/sbin/entrypoint.sh…" 2 minutes ago Up 2 minutes 443/tcp, 0.0.0.0:8080->80/tcp, :::8080->80/tcp zulip
c0a358209b09 redis:alpine "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 6379/tcp zulip-redis
27be352a0a35 memcached:alpine "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 11211/tcp zulip-memcached
You can also use the following command for the same.
$ docker compose ps
In this case, your output would look like the following.
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
zulip zulip/docker-zulip:8.2-0 "/sbin/entrypoint.sh…" zulip About a minute ago Up About a minute 443/tcp, 0.0.0.0:8080->80/tcp, :::8080->80/tcp
zulip-db zulip/zulip-postgresql:14 "docker-entrypoint.s…" database About a minute ago Up About a minute 5432/tcp
zulip-memcached memcached:alpine "docker-entrypoint.s…" memcached About a minute ago Up About a minute 11211/tcp
zulip-rabbitmq rabbitmq:alpine "docker-entrypoint.s…" rabbitmq About a minute ago Up About a minute 4369/tcp, 5671-5672/tcp, 15691-15692/tcp, 25672/tcp
zulip-redis redis:alpine "docker-entrypoint.s…" redis About a minute ago Up About a minute 6379/tcp
The Zulip container will take some time to start working. You can follow the progress using the following command.
$ docker logs zulip --follow
You will see a long list of commands that are run to set up the container. Once you see the following output, it means Zulip is installed.
2024-02-21 09:02:55,310 INFO success: go-camo entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,311 INFO success: smokescreen entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,311 INFO success: zulip-django entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,311 INFO success: zulip-tornado entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,311 INFO success: zulip_deliver_scheduled_emails entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,311 INFO success: zulip_deliver_scheduled_messages entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,311 INFO success: process-fts-updates entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,311 INFO success: cron entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,311 INFO success: nginx entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,311 INFO success: zulip_events_deferred_work entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,311 INFO success: zulip_events_digest_emails entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,311 INFO success: zulip_events_email_mirror entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,312 INFO success: zulip_events_embed_links entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,312 INFO success: zulip_events_embedded_bots entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,312 INFO success: zulip_events_invites entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,312 INFO success: zulip_events_email_senders entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,312 INFO success: zulip_events_missedmessage_emails entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,312 INFO success: zulip_events_missedmessage_mobile_notifications entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,312 INFO success: zulip_events_outgoing_webhooks entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,312 INFO success: zulip_events_user_activity entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,312 INFO success: zulip_events_user_activity_interval entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2024-02-21 09:02:55,312 INFO success: zulip_events_user_presence entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
Press Ctrl + C to exit the screen. Zulip is installed. However, we still need to configure Nginx to serve Zulip.
Step 8 – Configure Nginx
Open the file /etc/nginx/nginx.conf
for editing.
$ sudo nano /etc/nginx/nginx.conf
Add the following line before the line
include /etc/nginx/conf.d/*.conf;server_names_hash_bucket_size 64;
Save the file by pressing Ctrl + X and entering Y when prompted.
Create and open the file /etc/nginx/conf.d/zulip.conf
for editing.
$ sudo nano /etc/nginx/conf.d/zulip.conf
Paste the following code in it.
server {
listen 80;
listen [::]:80;
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name zulip.example.com;
ssl_certificate /etc/letsencrypt/live/zulip.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/zulip.example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/zulip.example.com/chain.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
resolver 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001] 8.8.8.8 8.8.4.4 [2001:4860:4860::8888] [2001:4860:4860::8844] valid=60s;
resolver_timeout 2s;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
tcp_nopush on;
gzip on;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 20m;
proxy_pass http://127.0.0.1:8080;
}
}
Save the file by pressing Ctrl + X and entering Y when prompted once finished.
Verify the Nginx configuration file syntax.
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Restart the Nginx service.
$ sudo systemctl restart nginx
Step 9 – Accessing Zulip Interface
Visit https://zulip.example.com/
in your browser, and the following screen will appear.
Using the link New Organization on the top will take you to the following page.
As you can see, Zulip doesn’t let you create an organization from the front end. Go back to the terminal and run the following command to create the new organization page URL. We will talk more about how to use Zulip commands in the next section.$ docker compose exec -u zulip zulip /home/zulip/deployments/current/manage.py generate_realm_creation_link Please visit the following secure single-use link to register your new Zulip organization: https://zulip.example.com/new/svvss33z4qmwewmanbbcalen
Open the URL https://zulip.example.com/new/cjjivovgcillw44z7gsnobkv
in your browser and you will be taken to the following page.
Enter your organization’s name, organization type, language, and email ID to start creating your organization. Click the Create organization button to proceed.
You will be asked to set up an account on the next screen.
Enter your name, choose a password for logging in, and click the Sign up button to proceed.
Once finished, the Zulip dashboard will open, and you can start using it.
Step 10 – Zulip Server Commands
From time to time, to run Zulip Server commands, you will need access to the container shell. To access the shell as zulip
user, use the following command.
$ docker compose exec -u zulip zulip bash
Or you can use the following command.
$ docker exec -itu zulip zulip bash
To run a command inside a container, you can run it using a single command as follows.
$ docker compose exec -u zulip zulip /home/zulip/deployments/current/manage.py help <subcommand>
However, there is a simpler method of doing it. We can create a shell script for running commands. Create and open zulip_manage.sh
for editing.
$ nano zulip_manage.sh
Paste the following code in it.
#!/bin/sh
docker compose exec -u zulip zulip /home/zulip/deployments/current/manage.py "$@"
Save the file by pressing Ctrl + X and entering Y when prompted.
Make the script executable.
$ chmod +x zulip_manage.sh
To stop the Zulip server, use the following command.
$ docker exec -u zulip zulip /home/zulip/deployments/current/scripts/stop-server
process-fts-updates: stopped
zulip-django: stopped
zulip-tornado: stopped
zulip-workers:zulip_events_deferred_work: stopped
zulip-workers:zulip_events_digest_emails: stopped
zulip-workers:zulip_events_email_mirror: stopped
zulip-workers:zulip_events_email_senders: stopped
zulip-workers:zulip_events_embed_links: stopped
zulip-workers:zulip_events_embedded_bots: stopped
zulip-workers:zulip_events_invites: stopped
zulip-workers:zulip_events_missedmessage_emails: stopped
zulip-workers:zulip_events_missedmessage_mobile_notifications: stopped
zulip-workers:zulip_events_outgoing_webhooks: stopped
zulip-workers:zulip_events_user_activity: stopped
zulip-workers:zulip_events_user_activity_interval: stopped
zulip-workers:zulip_events_user_presence: stopped
zulip_deliver_scheduled_emails: stopped
zulip_deliver_scheduled_messages: stopped
Zulip stopped successfully!
To start the server again, use the following command.
$ docker exec -u zulip zulip /home/zulip/deployments/current/scripts/start-server
2024-02-21 09:18:52,932 start-server: Running syntax and database checks
System check identified no issues (28 silenced).
2024-02-21 09:18:54,944 start-server: Starting Tornado process
zulip-tornado: started
2024-02-21 09:18:56,089 start-server: Starting django server
zulip-django: started
2024-02-21 09:18:58,133 start-server: Starting workers
process-fts-updates: started
zulip-workers:zulip_events_deferred_work: started
zulip-workers:zulip_events_digest_emails: started
zulip-workers:zulip_events_email_mirror: started
zulip-workers:zulip_events_email_senders: started
zulip-workers:zulip_events_embed_links: started
zulip-workers:zulip_events_embedded_bots: started
zulip-workers:zulip_events_invites: started
zulip-workers:zulip_events_missedmessage_emails: started
zulip-workers:zulip_events_missedmessage_mobile_notifications: started
zulip-workers:zulip_events_outgoing_webhooks: started
zulip-workers:zulip_events_user_activity: started
zulip-workers:zulip_events_user_activity_interval: started
zulip-workers:zulip_events_user_presence: started
zulip_deliver_scheduled_emails: started
zulip_deliver_scheduled_messages: started
2024-02-21 09:19:19,571 start-server: Done!
Zulip started successfully!
Restart the server in a similar manner.
$ docker exec -u zulip zulip /home/zulip/deployments/current/scripts/restart-server
There are a lot of management tasks you can achieve using the manage.py
script shipped with Zulip.
You can run the script using the following command. We will use the help
sub-command to list all the possible operations one can perform.
$ ./zulip_manage.sh help
Type 'manage.py help <subcommand>' for help on a specific subcommand.
Available subcommands:
[analytics]
check_analytics_state
clear_analytics_tables
clear_single_stat
populate_analytics_db
update_analytics_counts
[django]
dbshell
makemigrations
migrate
shell
showmigrations
[zerver]
add_users_to_streams
archive_messages
audit_fts_indexes
backup
bulk_change_user_name
change_password
change_realm_subdomain
change_user_email
change_user_role
check_redis
checkconfig
compilemessages
convert_gitter_data
convert_mattermost_data
convert_rocketchat_data
convert_slack_data
create_default_stream_groups
create_realm
create_realm_internal_bots
create_stream
create_user
deactivate_realm
deactivate_user
delete_old_unclaimed_attachments
delete_realm
delete_user
deliver_scheduled_emails
deliver_scheduled_messages
edit_linkifiers
email_mirror
enqueue_digest_emails
enqueue_file
export
export_search
export_single_user
export_usermessage_batch
fetch_tor_exit_nodes
fill_memcached_caches
generate_realm_creation_link
get_migration_status
import
list_realms
logout_all_users
makemessages
merge_streams
process_queue
promote_new_full_members
purge_queue
query_ldap
rate_limit
reactivate_realm
realm_domain
register_server
remove_users_from_stream
reset_authentication_attempt_count
restore_messages
runtornado
scrub_realm
send_custom_email
send_password_reset_email
send_realm_reactivation_email
send_test_email
send_to_email_mirror
send_webhook_fixture_message
send_welcome_bot_message
show_admins
soft_deactivate_users
sync_ldap_user_data
transfer_uploads_to_s3
unarchive_stream
Docker’s entrypoint.sh file also provides with few other options. Run the following command to see them.
$ docker exec -it zulip bash /sbin/entrypoint.sh app:help
You should see the following output.
Available commands:
> app:help - Show this help menu and exit
> app:version - Container Zulip server version
> app:managepy - Run Zulip's manage.py script (defaults to "shell")
> app:backup - Create backups of Zulip instances
> app:restore - Restore backups of Zulip instances
> app:certs - Create self-signed certificates
> app:run - Run the Zulip server
> [COMMAND] - Run given command with arguments in shell
Step 11 – Test Outgoing Email
To test your outgoing email configuration, you can send a test mail using the following command.
$ ~/docker-zulip/zulip_manage.sh send_test_email [email protected]
If you run into any trouble, read:
The most common error is not setting `ADD_TOKENS_TO_NOREPLY_ADDRESS=False` when
using an email provider that doesn’t support that feature.
Sending 2 test emails from:
* [email protected]
* [email protected]
Successfully sent 2 emails to [email protected]
, [email protected]
Step 12 – Upgrading Zulip
The first step in upgrading Zulip is to stop the existing containers.
$ cd ~/docker-zulip
$ docker compose stop
Open the docker-compose.yml
file for editing.
$ nano docker-compose.yml
Check the latest available version from Zulip’s DockerHub Tags page. Modify the version in the following section.
.....
zulip:
image: "zulip/docker-zulip:8.2-0"
restart: unless-stopped
container_name: zulip
....
Make any other changes you need. You will need to refer to Zulip’s changelog and Zulip’s Docker GitHub repository for it.
Once finished, save the file by pressing Ctrl + X and entering Y when prompted.
Start the containers again. The Zulip container will be recreated with the changes.
$ docker compose up -d
Remove any old containers.
$ docker compose rm
Conclusion
This concludes our tutorial on installing and configuring the Zulip Chat server on a Rocky Linux 9 server. You can follow Zulip’s official documentation to explore in detail. If you have any questions, post them in the comments below.
Đăng ký liền tay Nhận Ngay Bài Mới
Subscribe ngay
Cám ơn bạn đã đăng ký !
Lỗi đăng ký !
Add Comment