Compose: Upgrading from the legacy zulip/docker-zulip image

Zulip Server 11.x and earlier shipped as zulip/docker-zulip on Docker Hub. The 12.x series moved to ghcr.io/zulip/zulip-server, with a number of breaking configuration changes along the way. This page covers the one-time work needed to migrate; once you’re on the new image, future upgrades follow the standard Compose: Upgrading flow.

If you’ve already migrated, you don’t need this page.

Plan and back up

  1. Back up first. This is a major upgrade and the volume layout changes (see below); a backup is the simplest way to make rolling back tractable. See backing up Zulip data.

    docker compose exec zulip /sbin/entrypoint.sh app:backup
    docker compose run --rm -v zulip:/data -v $(pwd):/backup zulip \
      tar czf /backup/backup.tar.gz -C /data .
    
  2. Snapshot your current configuration. The checkout in the next section will overwrite your edited docker-compose.yml. Save a reference copy first so you can translate settings from it:

    cp docker-compose.yml ../docker-compose.yml.legacy-backup
    

What changed

Image

zulip/docker-zulip on Docker Hub is replaced by ghcr.io/zulip/zulip-server on GitHub Container Registry. The Docker Hub image will receive no further updates after the Zulip Server 11.x series is end-of-life.

Configuration layout

  • Local configurations are expected to live in compose.override.yaml, which you copy from the tracked compose.override.yaml.example template. The shipped compose.yaml is no longer expected to be edited.

  • Secrets are no longer environment variables in two places in compose.yaml; they have moved to use Docker secrets. See Compose: Secrets management.

Setting renames and removals

  • DB_HOST and DB_HOST_PORT have been replaced by SETTING_REMOTE_POSTGRES_HOST and SETTING_REMOTE_POSTGRES_PORT, respectively, to align with standard Zulip settings.

  • DB_USER and DB_NAME have been replaced with CONFIG_postgresql__database_user and CONFIG_postgresql__database_name, respectively.

  • REMOTE_POSTGRES_SSLMODE has been removed; the standard spelling SETTING_REMOTE_POSTGRES_SSLMODE is used instead.

  • DISABLE_HTTPS and SSL_CERTIFICATE_GENERATION have been replaced with the single CERTIFICATES setting; the default is now HTTP-only, since most Docker deployments are behind an existing proxy. See Compose: Configuring TLS.

  • SPECIAL_SETTING_DETECTION_MODE has been removed; its behavior was confusing and at odds with its name.

  • NGINX_PROXY_BUFFERING has been removed; setting it could only break things.

  • NGINX_WORKERS has been replaced with the generic CONFIG_application_server__nginx_worker_processes.

  • PROXY_ALLOW_ADDRESSES and PROXY_ALLOW_RANGES have been replaced with the generic CONFIG_http_proxy__allow_addresses and CONFIG_http_proxy__allow_ranges.

  • QUEUE_WORKERS_MULTIPROCESS has been replaced with the generic CONFIG_application_server__queue_workers_multiprocess.

Volume layout

Contents of the named zulip Docker volume have been reorganized: certificates are stored in certs/ subdirectories (self-signed, certbot, and manual), and LINK_SETTINGS_TO_DATA contents are stored in etc-zulip/ rather than settings/etc-zulip/. Files are moved to these new locations automatically on first startup with the new image.

Migrate to the 12.x layout

  1. Discard local edits to the tracked compose file. git checkout refuses to overwrite a file with uncommitted modifications, and you already have your reference copy saved from the previous section. Either reset:

    git checkout -- docker-compose.yml
    

    or stash:

    git stash push -m "legacy compose customizations" docker-compose.yml
    
  2. Fetch and check out the first 12.x release tag, on a local branch so subsequent upgrades follow the same pattern:

    git fetch --tags
    git checkout -B release 12.0-0
    

    The compose configuration file is now compose.yaml (not docker-compose.yml).

  3. Move your secrets into a .env file. See Store secrets in a .env file for the full mapping; copy each ZULIP__* value out of your old docker-compose.yml.legacy-backup and into .env.

  4. Build your override file from the example, then translate settings:

    cp compose.override.yaml.example compose.override.yaml
    

    Edit it to translate your previous local settings, applying the renames in the section above. The skeleton looks like:

    secrets:
      zulip__postgres_password:
        environment: "ZULIP__POSTGRES_PASSWORD"
      zulip__memcached_password:
        environment: "ZULIP__MEMCACHED_PASSWORD"
      zulip__rabbitmq_password:
        environment: "ZULIP__RABBITMQ_PASSWORD"
      zulip__redis_password:
        environment: "ZULIP__REDIS_PASSWORD"
      zulip__secret_key:
        environment: "ZULIP__SECRET_KEY"
      zulip__email_password:
        environment: "ZULIP__EMAIL_PASSWORD"
    services:
      zulip:
        environment:
          # Include all settings starting with SETTING_ from your old
          # docker-compose.yml, plus any of the renamed settings above.
    
  5. Set CERTIFICATES if needed. If you had not previously set DISABLE_HTTPS, or had set SSL_CERTIFICATE_GENERATION, you will need to set CERTIFICATES; see Compose: Configuring TLS.

Bring the new image up

docker compose pull
docker compose up -d

The first boot performs the volume reorganization described above, along with any database migrations. Watch the logs with docker compose logs -f zulip, looking for === End Initial Configuration Phase === to confirm a clean start.

See also