zulip/docs/production/deployment.md

16 KiB

Deployment options

The default Zulip installation instructions will install a complete Zulip server, with all of the services it needs, on a single machine.

For production deployment, however, it's common to want to do something more complicated. This page documents the options for doing so.

Installing Zulip from Git

To install a development version of Zulip from Git, just clone the Git repository from GitHub:

# First, install Git if you don't have it installed already
sudo apt install git
git clone https://github.com/zulip/zulip.git zulip-server-git

and then continue the normal installation instructions. You can also upgrade Zulip from Git.

The most common use case for this is upgrading to main to get a feature that hasn't made it into an official release yet (often support for a new base OS release). See upgrading to main for notes on how main works and the support story for it, and upgrading to future releases for notes on upgrading Zulip afterwards.

In particular, we are always very glad to investigate problems with installing Zulip from main; they are rare and help us ensure that our next major release has a reliable install experience.

Zulip in Docker

Zulip has an officially supported, experimental docker image. Please note that Zulip's normal installer has been extremely reliable for years, whereas the Docker image is new and has rough edges, so we recommend the normal installer unless you have a specific reason to prefer Docker.

Zulip installer details

The Zulip installer does the following:

  • Creates the zulip user, which the various Zulip servers will run as.
  • Creates /home/zulip/deployments/, which the Zulip code for this deployment (and future deployments when you upgrade) goes into. At the very end of the install process, the script moves the Zulip code tree it's running from (which you unpacked from a tarball above) to a directory there, and makes /home/zulip/deployments/current as a symbolic link to it.
  • Installs Zulip's various dependencies.
  • Configures the various third-party services Zulip uses, including PostgreSQL, RabbitMQ, Memcached and Redis.
  • Initializes Zulip's database.

Advanced installer options

The Zulip installer supports the following advanced installer options as well as those mentioned in the install documentation:

  • --postgresql-version: Sets the version of PostgreSQL that will be installed. We currently support PostgreSQL 12, 13, 14, 15, and 16, with 16 being the default.

  • --postgresql-database-name=exampledbname: With this option, you can customize the default database name. If you do not set this. The default database name will be zulip. This setting can only be set on the first install.

  • --postgresql-database-user=exampledbuser: With this option, you can customize the default database user. If you do not set this. The default database user will be zulip. This setting can only be set on the first install.

  • --postgresql-missing-dictionaries: Set postgresql.missing_dictionaries (docs) in the Zulip settings, which omits some configuration needed for full-text indexing. This should be used with cloud managed databases like RDS. This option conflicts with --no-overwrite-settings.

  • --no-init-db: This option instructs the installer to not do any database initialization. This should be used when you already have a Zulip database.

  • --no-overwrite-settings: This option preserves existing /etc/zulip configuration files.

Installing on an existing server

Zulip's installation process assumes it is the only application running on the server; though installing alongside other applications is not recommended, we do have some notes on the process.

Deployment hooks

Zulip's upgrades have a hook system which allows for arbitrary user-configured actions to run before and after an upgrade; see the upgrading documentation for details on how to write your own.

Zulip message deploy hook

Zulip can use its deploy hooks to send a message immediately before and after conducting an upgrade. To configure this:

  1. Add , zulip::hooks::zulip_notify to the puppet_classes line in /etc/zulip/zulip.conf
  2. Add a [zulip_notify] section to /etc/zulip/zulip.conf:
    [zulip_notify]
    bot_email = your-bot@zulip.example.com
    server = zulip.example.com
    stream = deployments
    
  3. Add the api key for the bot user in /etc/zulip/zulip-secrets.conf as zulip_release_api_key:
    # Replace with your own bot's token, found in the Zulip UI
    zulip_release_api_key = abcd1234E6DK0F7pNSqaMSuzd8C5i7Eu
    
  4. As root, run /home/zulip/deployments/current/scripts/zulip-puppet-apply.

Sentry deploy hook

Zulip can use its deploy hooks to create Sentry releases, which can help associate Sentry error logging with specific releases. If you are deploying Zulip from Git, it can be aware of which Zulip commits are associated with the release, and help identify which commits might be relevant to an error.

To do so:

  1. Enable Sentry error logging.
  2. Add a new internal Sentry integration named "Release annotator".
  3. Grant the internal integration the permissions of "Admin" on "Release".
  4. Add , zulip::hooks::sentry to the puppet_classes line in /etc/zulip/zulip.conf
  5. Add a [sentry] section to /etc/zulip/zulip.conf:
    [sentry]
    organization = your-organization-name
    project = your-project-name
    
  6. Add the authentication token for your internal Sentry integration to your /etc/zulip/zulip-secrets.conf:
    # Replace with your own token, found in Sentry
    sentry_release_auth_token = 6c12f890c1c864666e64ee9c959c4552b3de473a076815e7669f53793fa16afc
    
  7. As root, run /home/zulip/deployments/current/scripts/zulip-puppet-apply.

If you are deploying Zulip from Git, you will also need to:

  1. In your Zulip project, add the GitHub integration.
  2. Configure the zulip/zulip GitHub project for your Sentry project. You should do this even if you are deploying a private fork of Zulip.
  3. Additionally grant the internal integration "Read & Write" on "Organization"; this is necessary to associate the commits with the release.

Running Zulip's service dependencies on different machines

Zulip has full support for each top-level service living on its own machine.

You can configure remote servers for PostgreSQL, RabbitMQ, Redis, in /etc/zulip/settings.py; just search for the service name in that file and you'll find inline documentation in comments for how to configure it.

Since some of these services require some configuration on the node itself (e.g. installing our PostgreSQL extensions), we have designed the Puppet configuration that Zulip uses for installing and upgrading configuration to be completely modular.

For example, to install a Zulip Redis server on a machine, you can run the following after unpacking a Zulip production release tarball:

env PUPPET_CLASSES=zulip::profile::redis ./scripts/setup/install

All puppet modules under zulip::profile are allowed to be configured stand-alone on a host. You can see most likely manifests you might want to choose in the list of includes in [the main manifest for the default all-in-one Zulip server][standalone.pp], though it's also possible to subclass some of the lower-level manifests defined in that directory if you want to customize. A good example of doing this is in the [kandra Puppet configuration][zulipchat-puppet] that we use as part of managing chat.zulip.org and zulip.com.

Using Zulip with Amazon RDS as the database

You can use DBaaS services like Amazon RDS for the Zulip database. The experience is slightly degraded, in that most DBaaS provides don't include useful dictionary files in their installations and don't provide a way to provide them yourself, resulting in a degraded full-text search experience around issues dictionary files are relevant (e.g. stemming).

You also need to pass some extra options to the Zulip installer in order to avoid it throwing an error when Zulip attempts to configure the database's dictionary files for full-text search; the details are below.

Step 1: Set up Zulip

Follow the standard instructions, with one change. When running the installer, pass the --no-init-db flag, e.g.:

sudo -s  # If not already root
./zulip-server-*/scripts/setup/install --certbot \
    --email=YOUR_EMAIL --hostname=YOUR_HOSTNAME \
    --no-init-db --postgresql-missing-dictionaries

The script also installs and starts PostgreSQL on the server by default. We don't need it, so run the following command to stop and disable the local PostgreSQL server.

sudo service postgresql stop
sudo update-rc.d postgresql disable

This complication will be removed in a future version.

Step 2: Create the PostgreSQL database

Access an administrative psql shell on your PostgreSQL database, and run the commands in scripts/setup/create-db.sql to:

  • Create a database called zulip.
  • Create a user called zulip.
  • Now log in with the zulip user to create a schema called zulip in the zulip database. You might have to grant create privileges first for the zulip user to do this.

Depending on how authentication works for your PostgreSQL installation, you may also need to set a password for the Zulip user, generate a client certificate, or similar; consult the documentation for your database provider for the available options.

Step 3: Configure Zulip to use the PostgreSQL database

In /etc/zulip/settings.py on your Zulip server, configure the following settings with details for how to connect to your PostgreSQL server. Your database provider should provide these details.

  • REMOTE_POSTGRES_HOST: Name or IP address of the PostgreSQL server.
  • REMOTE_POSTGRES_PORT: Port on the PostgreSQL server.
  • REMOTE_POSTGRES_SSLMODE: SSL Mode used to connect to the server.

If you're using password authentication, you should specify the password of the zulip user in /etc/zulip/zulip-secrets.conf as follows:

postgres_password = abcd1234

Now complete the installation by running the following commands.

# Ask Zulip installer to initialize the PostgreSQL database.
su zulip -c '/home/zulip/deployments/current/scripts/setup/initialize-database'

# And then generate a realm creation link:
su zulip -c '/home/zulip/deployments/current/manage.py generate_realm_creation_link'

Using an alternate port

If you'd like your Zulip server to use an HTTPS port other than 443, you can configure that as follows:

  1. Edit EXTERNAL_HOST in /etc/zulip/settings.py, which controls how the Zulip server reports its own URL, and restart the Zulip server with /home/zulip/deployments/current/scripts/restart-server.

  2. Add the following block to /etc/zulip/zulip.conf:

    [application_server]
    nginx_listen_port = 12345
    
  3. As root, run /home/zulip/deployments/current/scripts/zulip-puppet-apply. This will convert Zulip's main nginx configuration file to use your new port.

We also have documentation for a Zulip server using HTTP for use behind reverse proxies.

Customizing the outgoing HTTP proxy

To protect against SSRF, Zulip 4.8 and above default to routing all outgoing HTTP and HTTPS traffic through Smokescreen, an HTTP CONNECT proxy; this includes outgoing webhooks, website previews, and mobile push notifications. By default, the Camo image proxy will be automatically configured to use a custom outgoing proxy, but does not use Smokescreen by default because Camo includes similar logic to deny access to private subnets. You can override this default configuration if desired.

To use a custom outgoing proxy:

  1. Add the following block to /etc/zulip/zulip.conf, substituting in your proxy's hostname/IP and port:

    [http_proxy]
    host = 127.0.0.1
    port = 4750
    
  2. As root, run /home/zulip/deployments/current/scripts/zulip-puppet-apply. This will reconfigure and restart Zulip.

If you have a deployment with multiple frontend servers, or wish to install Smokescreen on a separate host, you can apply the zulip::profile::smokescreen Puppet class on that host, and follow the above steps, setting the [http_proxy] block to point to that host.

If you wish to disable the outgoing proxy entirely, follow the above steps, configuring an empty host value.

Optionally, you can also configure the Smokescreen ACL list. By default, Smokescreen denies access to all non-public IP addresses, including 127.0.0.1, but allows traffic to all public Internet hosts.

In Zulip 4.7 and older, to enable SSRF protection via Smokescreen, you will need to explicitly add the zulip::profile::smokescreen Puppet class, and configure the [http_proxy] block as above.

S3 file storage requests and outgoing proxies

By default, the S3 file storage backend bypasses the Smokescreen proxy, because when running on EC2 it may require metadata from the IMDS metadata endpoint, which resides on the internal IP address 169.254.169.254 and would thus be blocked by Smokescreen.

If your S3-compatible storage backend requires use of Smokescreen or some other proxy, you can override this default by setting S3_SKIP_PROXY = False in /etc/zulip/settings.py.

PostgreSQL warm standby

Zulip's configuration allows for warm standby database replicas as a disaster recovery solution; see the linked PostgreSQL documentation for details on this type of deployment. Zulip's configuration builds on top of wal-g, our streaming database backup solution, and thus requires that it be configured for the primary and all secondary warm standby replicas.

In addition to having wal-g backups configured, warm standby replicas should configure the hostname of their primary replica, and username to use for replication, in /etc/zulip/zulip.conf:

[postgresql]
replication_user = replicator
replication_primary = hostname-of-primary.example.com

The postgres user on the replica will need to be able to authenticate as the replication_user user, which may require further configuration of pg_hba.conf and client certificates on the replica. If you are using password authentication, you can set a postgresql_replication_password secret in /etc/zulip/zulip-secrets.conf.