Zulip server release checklist

This document has reminders of things one might forget to do when preparing a new release.

A week before the release

  • For a major release (e.g. 4.0):

    • Upgrade all Python dependencies in requirements to latest upstream versions so they can burn in (use pip list --outdated).

    • Upgrade all puppet dependencies in puppet/deps.yaml

    • Upgrade all puppet-installed dependencies (e.g. Smokescreen, go, etc) in puppet/zulip/manifests/common.pp

    • Upload strings to Transifex using push-translations. Post a Transifex Announcement notifying translators that we’re approaching a release.

    • Merge draft updates to the changelog with changes since the last release. While doing so, take notes on things that might need follow-up work or documentation before we can happily advertise them in a release blog post.

    • Inspect all TODO/compatibility comments for whether we can remove any backwards-compatibility code in this release.

  • Create a burn-down list of issues that need to be fixed before we can release, and make sure all of them are being worked on.

  • Draft the release blog post (a.k.a. the release notes) in Paper. In it, list the important changes in the release, from most to least notable.

Final release preparation

  • Update the Paper blog post draft with any new commits.

  • Except minor releases: Download updated translation strings from Transifex and commit them.

  • Use build-release-tarball to generate a release tarball.

  • Test the new tarball extensively, both new install and upgrade from last release, on Ubuntu 20.04.

  • Repeat until release is ready.

  • Send around the Paper blog post draft for review.

  • Move the blog post draft to Ghost:

    • Use “··· > Export > Markdown” to get a pretty good markdown conversion, then insert that as a Markdown block in Ghost.

    • Proofread, especially for formatting.

    • Tag the post with “Release announcements” first, then any other tags (e.g. “Security”).

Executing the release

  • Create the release commit, on main (for major releases) or on the release branch (for minor releases):

    • Copy the Markdown release notes for the release into docs/overview/changelog.md.

    • Verify the changelog passes lint, and has the right release date.

    • Except minor releases: Adjust the changelog.md heading to have the stable release series boilerplate.

    • Update ZULIP_VERSION and LATEST_RELEASE_VERSION in version.py.

    • Except minor releases: Update API_FEATURE_LEVEL to a feature level for the final release, and document a reserved range.

  • Tag that commit with an unsigned Git tag named the release number.

  • Use build-release-tarball to generate a final release tarball.

  • Push the tag and release commit.

  • Upload the tarball using tools/upload-release.

  • Post the release by editing the latest tag on GitHub; use the text from changelog.md for the release notes.

    Note: This will trigger the GitHub action for updating DigitalOcean one-click app image. The action uses the latest release tarball published on download.zulip.com for creating the image.

  • Update the Docker image:

    • Update ZULIP_GIT_REF in Dockerfile

    • Update README.md

    • Update the image in docker-compose.yml, as well as the ZULIP_GIT_REF

    • Update the image in kubernetes/zulip-rc.yml

    • Build the image: docker build . -t zulip/docker-zulip:4.11-0 --no-cache

    • Also tag it with latest: docker build . -t zulip/docker-zulip:latest

    • Push those tags: docker push zulip/docker-zulip:4.11-0; docker push zulip/docker-zulip:latest

    • Update the latest version in the README in Docker Hub.

    • Commit the changes and push them to main.

  • Publish the blog post; check the box to “send by email.”

  • Announce the release, pointing to the blog post, via:


  • The DigitalOcean one-click image will report in an internal channel once it is built, and how to test it. Verify it, then publish it publish it to DigitalOcean marketplace.

  • Update the CI targets:

    • For major releases only: In all of the following steps, also bump up the series that are being tested.

    • Update the version in tools/ci/build-docker-images

    • Run tools/ci/build-docker-images

    • Push at least the latest of those, e.g. using docker push zulip/ci:bullseye-4.11; update the others at your discretion.

    • Update the docker_image in the production_upgrade step of .github/workflows/production-suite.yml.

    • Commit those two changes in a PR.

  • Following a major release (e.g. 4.0):

    • Create a release branch (e.g. 4.x).

    • On the release branch, update ZULIP_VERSION in version.py to the present release with a +git suffix, e.g. 4.0+git.

    • On main, update ZULIP_VERSION to the future major release with a -dev+git suffix, e.g. 5.0-dev+git. Make a Git tag for this update commit with a -dev suffix, e.g. 5.0-dev. Push the tag to both zulip.git and zulip-internal.git to get a correct version number for future Cloud deployments.

    • Consider removing a few old releases from ReadTheDocs; we keep about two years of back-versions.

    • Update Transifex to add the new 4.x style release branch resources and archive the previous release branch’s resources with the “Translations can’t translate this resource” setting.

  • Following a minor release (e.g. 3.2):

    • On the release branch, update ZULIP_VERSION to the present release with a +git suffix, e.g. 3.2+git.

    • On main, update LATEST_RELEASE_VERSION with the released version.

    • On main, cherry-pick the changelog changes from the release branch.