Logo
  • Zulip homepage
  • Overview
    • Zulip overview
      • Getting started
    • Zulip architectural overview
      • Key codebases
      • Usage assumptions and concepts
      • Components
        • Django and Tornado
        • nginx
        • Supervisor
        • memcached
        • Redis
        • RabbitMQ
        • PostgreSQL
        • Nagios
      • Glossary
    • Directory structure
      • Core Python files
      • HTML templates
      • JavaScript, TypeScript, and other frontend assets
      • Tests
      • Management commands
      • Scripts
      • API and bots
      • Production Puppet configuration
      • Additional Django apps
      • Jinja2 compatibility files
      • Translation files
      • Documentation
    • Release lifecycle
      • Server and web app versions
        • Stable releases
        • Git versions
        • What version am I running?
        • Versioned documentation
      • Client apps
        • Mobile app
        • Desktop app
        • Terminal app
      • Server and client app compatibility
        • Upgrade nag
      • Operating system support
      • API bindings
    • Roadmap
      • Server and web app roadmap
      • Mobile app roadmap
    • Version history
      • Zulip Server 11.x series (development)
        • Zulip Server 11.0
        • Zulip Server 10.3
      • Zulip Server 10.x series
        • Zulip Server 10.2
        • Zulip Server 10.1
        • Zulip Server 10.0
      • Zulip Server 9.x series
        • Zulip Server 9.4
        • Zulip Server 9.3
        • Zulip Server 9.2
        • Zulip Server 9.1
        • Zulip Server 9.0
      • Zulip Server 8.x series
        • Zulip Server 8.5
        • Zulip Server 8.4
        • Zulip Server 8.3
        • Zulip Server 8.2
        • Zulip Server 8.1
        • Zulip Server 8.0
      • Zulip Server 7.x series
        • Zulip Server 7.5
        • Zulip Server 7.4
        • Zulip Server 7.3
        • Zulip Server 7.2
        • Zulip Server 7.1
        • Zulip Server 7.0
      • Zulip Server 6.x series
        • Zulip Server 6.2
        • Zulip Server 6.1
        • Zulip Server 6.0
      • Zulip Server 5.x series
        • Zulip Server 5.7
        • Zulip Server 5.6
        • Zulip Server 5.5
        • Zulip Server 5.4
        • Zulip Server 5.3
        • Zulip Server 5.2
        • Zulip Server 5.1
        • Zulip Server 5.0
      • Zulip Server 4.x series
        • Zulip Server 4.11
        • Zulip Server 4.10
        • Zulip Server 4.9
        • Zulip Server 4.8
        • Zulip Server 4.7
        • Zulip Server 4.6
        • Zulip Server 4.5
        • Zulip Server 4.4
        • Zulip Server 4.3
        • Zulip Server 4.2
        • Zulip Server 4.1
        • Zulip Server 4.0
      • Zulip Server 3.x series
        • Zulip Server 3.4
        • Zulip Server 3.3
        • Zulip Server 3.2
        • Zulip Server 3.1
        • Zulip Server 3.0
      • Zulip Server 2.1.x series
        • Zulip Server 2.1.8
        • Zulip Server 2.1.7
        • Zulip Server 2.1.6
        • Zulip Server 2.1.5
        • Zulip Server 2.1.4
        • Zulip Server 2.1.3
        • Zulip Server 2.1.2
        • Zulip Server 2.1.1
        • Zulip Server 2.1.0
      • Zulip Server 2.0.x series
        • Zulip Server 2.0.8
        • Zulip Server 2.0.7
        • Zulip Server 2.0.6
        • Zulip Server 2.0.5
        • Zulip Server 2.0.4
        • Zulip Server 2.0.3
        • Zulip Server 2.0.2
        • Zulip Server 2.0.1
        • Zulip Server 2.0.0
      • Zulip Server 1.9.x series
        • Zulip Server 1.9.2
        • Zulip Server 1.9.1
        • Zulip Server 1.9.0
      • Zulip Server 1.8.x series
        • Zulip Server 1.8.1
        • Zulip Server 1.8.0
      • Zulip Server 1.7.x series
        • Zulip Server 1.7.2
        • Zulip Server 1.7.1
        • Zulip Server 1.7.0
      • Zulip Server 1.6.x and older
        • Zulip Server 1.6.0
        • Zulip Server 1.5.2
        • Zulip Server 1.5.1
        • Zulip Server 1.5.0
        • Zulip Server 1.4.3
        • Zulip Server 1.4.2
        • Zulip Server 1.4.1
        • Zulip Server 1.4.0
        • Zulip Server 1.3.13
        • Zulip Server 1.3.12
        • Zulip Server 1.3.11
        • Zulip Server 1.3.10
        • Zulip Server 1.3.9
        • Zulip Server 1.3.8
        • Zulip Server 1.3.7
      • Upgrade notes
  • Zulip in production
    • Requirements and scalability
      • Server
        • General
        • Operating system
        • Hardware specifications
        • Network and security specifications
      • Credentials needed
        • SSL certificate
        • Outgoing email
      • Scalability
    • Install a Zulip server
      • Installation process overview
      • Step 0: Set up a base server
      • Step 1: Download the latest release
      • Step 2: Install Zulip
        • Installer options
      • Step 3: Create a Zulip organization, and log in
      • Getting started with Zulip
    • Troubleshooting and monitoring
      • Overview and resources
      • Using supervisorctl
        • Checking status with supervisorctl status
        • Restarting services with supervisorctl restart
        • Stopping services with supervisorctl stop
      • Troubleshooting services
        • Restrict unattended upgrades
      • Monitoring
        • Nagios configuration
      • Memory leak mitigation
      • Troubleshooting the Zulip installer
        • The zulip user’s password.
    • Management commands
      • Running management commands
        • Accessing an organization’s string_id
      • manage.py shell
      • Other useful manage.py commands
      • Custom management commands
    • Server configuration
      • Server settings overview
      • Changing server settings
      • Customizing user onboarding
        • Navigation tour video
        • Terms of Service and Privacy policy
    • System configuration
      • Truthy values
      • [machine]
        • puppet_classes
        • pgroonga
        • timesync
      • [deployment]
        • deploy_options
        • git_repo_url
      • [application_server]
        • http_only
        • nginx_listen_port
        • nginx_worker_connections
        • queue_workers_multiprocess
        • rolling_restart
        • service_file_descriptor_limit
        • s3_memory_cache_size
        • s3_disk_cache_size
        • s3_cache_inactive_time
        • thumbnail_workers
        • email_senders_workers
        • nameserver
        • uwsgi_listen_backlog_limit
        • uwsgi_processes
        • access_log_retention_days
        • katex_server
        • katex_server_port
      • [postgresql]
        • effective_io_concurrency
        • listen_addresses
        • random_page_cost
        • replication_primary
        • replication_user
        • skip_backups
        • backups_disk_concurrency
        • backups_directory
        • backups_incremental
        • backups_storage_class
        • backups_compression_method
        • missing_dictionaries
        • ssl_ca_file
        • ssl_cert_file
        • ssl_key_file
        • ssl_mode
        • version
      • [memcached]
        • memory
        • max_item_size
        • size_reporting
      • [loadbalancer]
        • ips
        • rejects_http_requests
      • [http_proxy]
        • host
        • port
        • listen_address
        • enable_for_camo
      • [sentry]
        • organization
        • project
    • Mobile push notification service
      • Signing up
      • Plan management
        • Plan management for a Zulip organization
        • Plan management for an entire Zulip server
      • Why a push notification service is necessary
      • Security and privacy
        • Uploading basic metadata
        • Uploading usage statistics
      • Rate limits
      • Updating your server’s registration
      • Moving your registration to a new server
        • Transferring your registration if you lost the original credentials
      • Deactivating your server’s registration
        • Pausing use of the Mobile Push Notification Service
      • Sending push notifications directly from your server
    • Upgrade Zulip
      • Upgrading to a release
        • What to expect during an upgrade
      • Upgrading from a Git repository
        • Upgrading to an unreleased version of Zulip
      • Updating settings.py inline documentation
      • Troubleshooting and rollback
        • Rolling back to a prior version
      • Deployment hooks
      • Preserving local changes to service configuration files
        • nginx configuration changes
      • Upgrading PostgreSQL
      • Upgrading the operating system
        • Upgrading from Ubuntu 22.04 Jammy to 24.04 Noble
        • Upgrading from Ubuntu 20.04 Focal to 22.04 Jammy
        • Upgrading from Ubuntu 18.04 Bionic to 20.04 Focal
        • Upgrading from Ubuntu 16.04 Xenial to 18.04 Bionic
        • Upgrading from Debian 11 to 12
        • Upgrading from Debian 10 to 11
        • Upgrading from Debian 9 to 10
    • Modify Zulip
      • Making changes
        • Upgrading to future releases
        • Making changes with docker-zulip
      • Applying changes from main
        • Applying a small change
        • Upgrading to main
      • Contributing patches
    • Security model
      • Secure your Zulip server like your email server
      • Encryption and authentication
        • Passwords
      • Messages and history
      • Users and bots
      • User-uploaded content and user-generated requests
      • Rate limiting
      • Final notes and security response
    • Authentication methods
      • Email and password
      • Plug-and-play SSO (Google, GitHub, GitLab)
      • LDAP (including Active Directory)
        • Synchronizing data
        • Multiple LDAP searches
        • Restricting access to an LDAP group
        • Restricting LDAP user access to specific organizations
        • Troubleshooting
      • SAML
        • IdP-initiated SSO
        • Restricting access to specific organizations
        • Synchronizing user role or custom profile fields during login
        • SCIM
        • Using Keycloak as a SAML IdP
        • Using Authentik as a SAML IdP
        • SAML Single Logout
      • Apache-based SSO with REMOTE_USER
        • Setup instructions for Apache-based SSO
        • Troubleshooting Apache-based SSO
        • Life of an Apache-based SSO login attempt
      • Sign in with Apple
      • OpenID Connect
      • JWT
      • Configuring a custom Python wrapper around the authenticate mechanism
      • Adding more authentication backends
      • Development only
    • Backups, export and import
      • Backups
        • Restoring backups
        • What is included
        • Restore from manual backups
      • Data export
        • Consider upgrading
        • Preventing changes during the export
        • Export your Zulip data
      • Import into a new Zulip server
        • Import options
        • Logging in
        • Deleting and re-importing
      • Compliance exports
      • Database-only backup tools
        • Streaming backups to S3
        • Streaming backups to local disk
    • PostgreSQL database details
      • Separate PostgreSQL database
        • Cloud-provider-managed PostgreSQL (e.g., Amazon RDS)
        • Remote PostgreSQL database
      • PostgreSQL warm standby
      • PostgreSQL vacuuming alerts
    • File upload backends
      • S3 backend configuration
        • Google Cloud Platform
      • S3 local caching
      • nginx DNS nameserver configuration
      • S3 bucket policy
      • Migrating from local uploads to Amazon S3 backend
      • S3 data storage class
      • Data export bucket
    • Installing SSL certificates
      • Manual install
        • Testing
      • Certbot (recommended)
        • At initial Zulip install
        • After Zulip is already installed
        • How it works
        • Renewal
      • Self-signed certificate
      • Troubleshooting
        • The Android app can’t connect to the server
        • The iOS app can’t connect to the server
        • The Android app connects to the server on some devices but not others
    • Outgoing email
      • How to configure
      • Email services
        • Free outgoing email services
        • Using system email
        • Using Gmail for outgoing email
        • Logging outgoing email to a file for prototyping
      • Troubleshooting
        • Advanced troubleshooting
    • Deployment options
      • Installing Zulip from Git
      • Zulip in Docker
      • Zulip installer details
        • Advanced installer options
      • Installing on an existing server
      • Deployment hooks
        • Zulip message deploy hook
        • Sentry deploy hook
      • Running Zulip’s service dependencies on different machines
      • Using an alternate port
      • Customizing the outgoing HTTP proxy
        • S3 file storage requests and outgoing proxies
    • Reverse proxies
      • Installer options
        • Configuring Zulip to allow HTTP
        • Configuring Zulip to trust proxies
      • nginx configuration
      • Apache2 configuration
      • HAProxy configuration
      • Other proxies
    • Hosting multiple organizations
      • Subdomains
        • SSL certificates
        • Other hostnames
        • The root domain
        • Changing subdomains
        • Authentication
        • The system bot realm
        • Migrating / troubleshooting
      • Open realm creation
    • Incoming email integration
      • Local delivery setup
      • Polling setup
    • Video call providers
      • Jitsi
      • Zoom
        • Server to Server OAuth app
        • General OAuth app
        • Configure your Zulip server
      • BigBlueButton
    • AI integrations
      • Built-in AI features
        • Data privacy
        • General configurations
        • Topic summarization beta
    • GIPHY GIF integration
      • Apply for API key
    • SCIM provisioning
      • Server configuration
      • Additional options
  • Contributing to Zulip
    • Contributing guide
      • Learning from the docs
      • Getting started
        • Learning how to use Git (the Zulip way)
        • Setting up your development environment and diving in
      • Finding an issue to work on
        • Where to look for an issue
        • Picking an issue to work on
        • Claiming an issue
      • Getting help
      • What makes a great Zulip contributor?
      • Submitting a pull request
      • Beyond the first issue
      • Common questions
      • Outreach programs
    • Zulip Code of Conduct
      • Expected behavior
      • Unacceptable behavior
      • Reporting and enforcement
      • Scope
      • License and attribution
      • Moderating the Zulip community
    • How we communicate
      • Providing suggestions and feedback
      • Handling disagreements
      • Expressing your appreciation
    • Asking great questions
      • Where to ask your question
      • How to ask a great question
      • Follow the community guidelines
    • Design discussions
      • Guidelines for all participants
      • Participant roles
      • Guidelines for code contributors
        • When to post
        • Guidelines for requesting design feedback
      • Guidelines for community moderators
        • Improving the quality of discussions
        • Moving threads to the most appropriate channel
      • Guidelines for decision makers
        • Managing the discussion
        • From discussion to decision
    • Commit discipline
      • Each commit must be coherent
      • Commits should generally be minimal
        • When not to be overly minimal
      • Write a clean commit history
      • Commit messages
        • Commit summary, part 1
        • Commit summary, part 2
        • Examples of good commit summaries
        • Commit description
        • Examples of good commit messages
    • Code style and conventions
      • Be consistent with existing code
        • Use the linters
        • Use tests to verify your logic
      • Follow Zulip conventions and practices
        • Observe a reasonable line length
        • Tag user-facing strings for translation
        • Correctly prepare paths destined for state or log files
        • Never include secrets inline with code
        • Familiarize yourself with rules about third-party code
      • Python-specific conventions and practices
      • JavaScript and TypeScript conventions and practices
        • Build DOM elements in Handlebars
        • Attach behaviors to event listeners
        • Declare variables using const and let
        • Manipulate objects and arrays with modern methods
      • HTML and CSS
      • Dangerous constructs in Django
        • Avoid excessive database queries
        • Never do direct database queries (UserProfile.objects.get(), Client.objects.get(), etc.)
        • Don’t use Django model objects as keys in sets/dicts
        • Don’t call user_profile.save() without update_fields
        • Don’t update important model objects with raw saves
        • Don’t use naive datetime objects
      • Dangerous constructs in JavaScript and TypeScript
        • Do not use for...in statements to traverse arrays
    • Reviewing Zulip code
      • Principles of code review
        • Reviewing your own code
        • Reviewing other contributors’ code
      • How to review code
        • Code review checklist
        • Automated testing
        • Manual testing
      • Review process and communication
        • Asking for a code review
        • Reviewing someone else’s code
        • Responding to review feedback
      • Additional resources
    • Submitting a pull request
      • Write clear code
      • Organize your proposed changes
      • Explain your changes
        • Discussions in the development community
      • Review your own work
      • Submit your pull request for review
      • Draft pull requests
      • Demonstrating visual changes
    • Pull request review process
      • Labels for managing the stages of pull request review
      • Stages of a pull request review
      • How to help move the review process forward
      • Follow-ups
    • Continuing unfinished work
      • Find work to be completed
      • Review existing work and feedback
      • Decide how to use prior work
      • Credit prior work in your commit history
      • Present your pull request
    • Using zulipbot
      • Usage
        • Contributing
    • Reporting bugs
      • What to include in a bug report
      • Filing a GitHub issue
      • Starting a conversation about a possible bug
      • Managing bug reports
    • Suggesting features and improvements
      • What to include in your proposal
      • Starting a conversation about a suggested feature or improvement
      • Filing a GitHub issue
      • Evaluation and onboarding feedback
    • Counting contributions
      • How the contribution stats are calculated
      • Old email addresses
      • Relevant source code
      • Attribution for non-code contributions
    • Licensing
      • Contributing your own work
      • Contributing someone else’s work
  • Development environment
    • Development environment installation
      • Requirements
      • Recommended setup
      • Vagrant setup
      • Advanced setup
      • Slow internet connections
      • Installing remotely
      • Next steps
    • Recommended setup
      • Requirements
      • Step 0: Set up Git & GitHub
      • Step 1: Install prerequisites
      • Step 2: Get Zulip code
      • Step 3: Start the development environment
      • Step 4: Developing
        • Where to edit files
        • VSCode setup (optional)
        • Understanding run-dev debugging output
        • Committing and pushing changes with Git
        • Maintaining the development environment
        • Rebuilding the development environment
        • Shutting down the development environment for use later
        • Resuming the development environment
      • Next steps
      • Troubleshooting and common errors
      • Specifying an Ubuntu mirror
      • Specifying a proxy
      • Using a different port for Vagrant
      • Customizing CPU and RAM allocation
    • Advanced setup
      • Installing directly on Ubuntu, Debian, CentOS, or Fedora
      • Installing using Vagrant with VirtualBox on Windows 10
        • Running Git BASH as an administrator
      • Using the Vagrant Hyper-V provider on Windows (beta)
        • Problems you may encounter
      • Newer versions of supported platforms
    • Using the development environment
      • Common
      • Server
      • Web
      • Mobile
    • Developing remotely
      • Connecting to the remote environment
      • Setting up user accounts
      • Setting up the development environment
      • Running the development server
      • Making changes to code on your remote development server
        • Editing locally
        • Editing remotely
        • Next steps
      • Using an nginx reverse proxy
    • Authentication in the development environment
      • Email and password
      • Google
      • GitHub
      • GitLab
      • Apple
      • SAML
      • When SSL is required
      • Testing LDAP in development
        • Testing avatar and custom profile field synchronization
        • Automated testing
      • Two factor authentication
      • Password form implementation
    • Testing the installer
      • Configuring
      • Running a test install
        • Build and unpack a release tarball
        • Test an install
        • See running containers after installation
        • Connect to a running container
        • Stopping and destroying containers
        • Iterating on the installer
  • Developer tutorials
    • Writing a new application feature
      • General process
        • Files impacted
        • Adding a field to the database
        • Backend changes
        • Frontend changes
        • Documentation changes
      • Example feature
        • Update the model
        • Create the migration
        • Test your migration changes
        • Handle database interactions
        • Update application state
        • Add a new view
        • Backend tests
        • Update the frontend
        • Frontend tests
        • Update documentation
    • Writing views in Zulip
      • What this covers
      • What is a view?
      • Modifying urls.py
      • Writing human-readable views
        • Decorators used for webpage views
        • Writing a template
      • Writing API REST endpoints
        • Request variables
        • Deciding which HTTP verb to use
        • Idempotency
        • Making changes to the database
        • Calling from the web application
        • Calling from an API client
      • Legacy endpoints used by the web client
      • Incoming webhook integrations
    • Life of a request
      • A request is sent to the server, and handled by nginx
      • Static files are served directly by nginx
      • nginx routes other requests between Django and Tornado
      • Django routes the request to a view in urls.py files
      • Views serving HTML are internationalized by server path
      • API endpoints use REST
        • PUT is only for creating new things
        • OPTIONS
        • Incoming webhook integrations may not be RESTful
      • Django calls rest_dispatch for REST endpoints, and authenticates
      • The view will authorize the user, extract request variables, and validate them
      • Results are given as JSON
    • Reading list
      • General programming/IT
      • Python
      • Java/Android
      • JavaScript/ECMAScript
      • TypeScript
      • Git/version control systems (VCS)
      • Computer science/algorithms
      • Community experience
      • Competitions/camps
      • Massive open online courses (MOOC) platforms
    • Screenshot and GIF software
      • Screenshot tools by platform
        • Browser
        • macOS
        • Windows
        • Linux
      • GIF tools by platform
        • Browser
        • macOS
        • Windows
        • Linux
    • Shell tips
      • The prompt ($)
      • Tilde character (~)
      • Change directory (cd)
      • Running commands as root (sudo)
      • Escaping characters
      • Sequencing commands
      • Splitting commands into multiple lines
      • Arguments
      • Shebang
      • Understanding commands
      • Cheatsheet
      • Git
  • Git guide
    • Quick start
    • Set up Git
      • Install and configure Git, join GitHub
      • Get a graphical client
    • Zulip-specific tools
      • Set up Git repo script
      • Configure continuous integration for your Zulip fork
      • Reset to pull request
      • Fetch a pull request and rebase
      • Fetch a pull request without rebasing
      • Push to a pull request
      • Delete unimportant branches
      • Merge conflict on pnpm-lock.yaml file
    • How Git is different
    • Important Git terms
      • branch
      • cache
      • checkout
      • commit
      • fast-forward
      • fetch
      • hash
      • head
      • HEAD
      • index
      • pull
      • push
      • rebase
    • Get Zulip code
      • Step 1a: Create your fork
      • Step 1b: Clone to your machine
      • Step 1c: Connect your fork to Zulip upstream
      • Step 2: Set up the Zulip development environment
      • Step 3: Configure continuous integration for your fork
    • Working copies
      • Workflows
      • Relevant Git commands
    • Using Git as you work
      • Know what branch you’re working on
      • Keep your fork up to date
      • Work on a feature branch
      • Run linters and tests locally
      • Stage changes
        • Get status of working directory
        • Stage additions with git add
        • Stage deletions with git rm
      • Commit changes
      • Push your commits to GitHub
      • Examine and tidy your commit history
      • Force-push changes to GitHub after you’ve altered your history
    • Pull requests
      • Draft pull requests
      • Create a pull request
        • Step 0: Make sure you’re on a feature branch (not main)
        • Step 1: Update your branch with git rebase
        • Step 2: Push your updated branch to your remote fork
        • Step 3: Open the pull request
      • Update a pull request
    • Collaborate
      • Fetch another contributor’s branch
      • Check out a pull request locally
    • Fixing commits
      • Fixing the last commit
        • Changing the last commit message
        • Changing the last commit
      • Fixing older commits
        • Changing commit messages
        • Deleting old commits
      • Squashing commits
      • Reordering commits
      • Pushing commits after tidying them
    • Reviewing changes
      • Changes on (local) working tree
      • Changes within branches
      • Changes between branches
    • Get and stay out of trouble
      • Undo a merge commit
      • Restore a lost commit
      • Recover from a git rebase failure
      • Working from multiple computers
    • Git cheat sheet
      • Common commands
      • Detailed cheat sheet
  • Code testing
    • Testing overview
      • Running tests
      • Major test suites
      • Other test suites
      • Internet access inside test suites
        • Documentation tests
    • Linters
      • Overview
      • Running the linters
      • General considerations
      • Lint checks
      • lint
        • Special options
        • Lint checks
      • Philosophy
        • Speed
        • Accuracy
        • Completeness
    • Backend Django tests
      • Overview
      • Running tests
      • Writing tests
        • Setting up data for tests
        • Testing code that accesses the filesystem
        • Testing with mocks
      • Zulip testing philosophy
        • Endpoint tests
        • Library tests
        • Fixture-driven tests
        • Mocks and stubs
        • Template tests
        • SQL performance tests
        • Event-based tests
        • Negative tests
      • Testing considerations
    • JavaScript/TypeScript unit tests
      • How the node tests work
      • Handling dependencies in unit tests
      • Creating new test modules
      • Verifying HTML templates with mock_template
      • Coverage reports
      • Editor debugger integration
      • Webstorm integration setup
      • Running tests with the debugger
    • Web frontend black-box Puppeteer tests
      • Running tests
      • How Puppeteer tests work
      • Debugging Puppeteer tests
      • Writing Puppeteer tests
    • Python static type checker (mypy)
      • Installing mypy
      • Running mypy on Zulip’s code locally
      • Mypy is there to find bugs in Zulip before they impact users
      • Mypy stubs for third-party modules
      • Working with types from django-stubs
      • Using @overload to accurately describe variations
      • Best practices
        • When is a type annotation justified?
        • Avoid the Any type
        • Avoid cast()
        • Avoid # type: ignore comments
        • Avoid other unchecked constructs
        • Use Optional and None correctly
        • Read-only types
        • Typing decorators
      • Troubleshooting advice
    • TypeScript static types
      • Type checking
      • Linting and style
      • Migration strategy
    • Continuous integration (CI)
      • Goals
      • GitHub Actions
        • Useful debugging tips and tools
        • Suites
        • Configuration
        • Images
        • Performance optimizations
    • Manual testing
      • Basic stuff
        • Message view
        • Messagebox
        • Message editing
        • Narrowing
        • Composing messages
        • Popover menus
        • Sidebar filtering
        • Channel permissions
        • Search
        • Channel settings
        • User settings
        • Keyboard shortcuts
        • Miscellaneous menu options
        • Inviting users/tutorial
        • To be continued…
    • Testing philosophy
      • Effective testing allows us to move quickly
      • Test suite performance and reliability are critical
      • Integration testing or unit testing?
      • Avoid duplicating code with security impact
      • Share test setup code
      • What isn’t tested probably doesn’t work
  • Subsystems documentation
    • Provisioning and third-party dependencies
      • Provisioning
        • PROVISION_VERSION
      • Philosophy on adding third-party dependencies
      • System packages
      • Python packages
      • JavaScript and other frontend packages
      • Node.js and pnpm
      • ShellCheck and shfmt
      • Puppet packages
      • Other third-party and generated files
        • Emoji
        • Translations data
        • Pygments data
      • Modifying provisioning
    • Settings system
      • Server settings
        • Testing non-default settings
      • Realm settings
    • HTML and CSS
      • Zulip CSS organization
      • Editing Zulip CSS
      • CSS style guidelines
        • Avoid duplicated code
        • Be consistent with existing similar UI
        • Use clear, unique names for classes and object IDs
      • Validating CSS
      • HTML templates
        • Behavior
        • Backend templates
        • Frontend templates
        • Toolchain
        • Translation
        • Tooltips
      • Static asset pipeline
        • Primary build process
        • Adding static files
        • How it works in production
        • ES6/TypeScript modules
    • Icons
      • Using icons
      • Adding a new icon
      • Preparing icons for use with Zulip
        • Correcting icons that include strokes
        • Correcting icons with an evenodd fill rule
        • Cleaning up the SVG code
      • Updating UI icons in the help center
    • Accessibility
      • Guidelines
      • Tools
      • GitHub issues
      • Additional resources
    • Real-time push and events
      • Generation system
      • Delivery system
      • The initial data fetch
        • Testing
        • Messages
      • Schema changes
    • Sending messages
      • Message lists
      • Compose area
      • Backend implementation
      • Local echo
        • Local echo in message editing
      • Putting it all together
      • Message editing
        • Inline URL previews
      • Soft deactivation
    • Notifications in Zulip
      • Important corner cases
      • The mobile/email notifications flow
    • Queue processors
      • Adding a new queue processor
      • Publishing events into a queue
      • Clearing a RabbitMQ queue
    • Unread counts and the pointer
      • Pointer logic
        • Recipient bar: message you clicked
        • Search, sidebar click, or new tab: unread/recent matching narrow
        • Unnarrow: previous sequence
        • Forced reload: state preservation
      • Unread count logic
      • Testing and development
    • Markdown implementation
      • Testing
      • Changing Zulip’s Markdown processor
      • Per-realm features
      • Zulip’s Markdown philosophy
      • Zulip’s changes to Markdown
        • Basic syntax
        • Lists
        • Links
        • Code
        • Other
    • Caching in Zulip
      • Backend caching with memcached
        • The core implementation
        • Cautions
        • Cache invalidation after writes
        • Production deployments and database migrations
        • Automated testing and memcached
        • Manual testing and memcached
        • Performance
      • In-process caching in Django
      • Browser caching of state
    • Performance and scalability
      • Load profiles
      • Major Zulip endpoints
        • Tornado
        • Presence
        • Fetching page_params
        • Fetching message history
        • User uploads
        • Sending and editing messages
        • Other endpoints
      • Queue processors and cron jobs
      • Service scalability
    • Realms in Zulip
      • Creating realms
        • Using unique link generator
      • Subdomains
        • Working with subdomains in development environment
    • Management commands
      • Writing management commands
    • Schema migrations
      • Automated testing for migrations
      • Schema and initial data changes
    • URL hashes and deep linking
      • Hashchange
      • Server-initiated reloads
      • All reloads
    • Emoji
      • Emoji codes
        • Custom emoji
      • Tooling
      • Picking emoji names
    • Onboarding Steps
      • Configuring a New Onboarding Step
        • Step 1: Add the Onboarding Step Name
        • Step 2: Display the Onboarding Step
        • Step 3: Mark the Onboarding Step as Read
    • Full-text search
      • The default full-text search implementation
      • Multi-language full-text search
        • Enabling PGroonga
        • Disabling PGroonga
    • Email
      • Development and testing
        • Testing in a real email client
        • Notes
      • Email templates
    • Analytics
      • Analytics backend overview
      • The *Count database tables
      • CountStats
      • The FillState table
      • Performance strategy
      • Backend testing
      • LoggingCountStats
      • Analytics UI development and testing
        • Setup and testing
        • Adding or editing /stats graphs
        • /activity page
    • Clients in Zulip
      • Analytics
      • Integrations
    • Logging and error reporting
      • Backend error reporting
        • Sentry error logging
        • Backend logging
      • Blueslip frontend error reporting
        • Sentry JavaScript error logging
      • Frontend performance reporting
    • Typing indicators
      • Privacy settings
      • Writing user
      • Server
      • Receiving user
      • Ecosystem
      • Roadmap
    • Upgrading Django
    • Zulip server release checklist
      • A week before the release
      • Final release preparation
      • Executing the release
      • Post-release
    • Zulip PyPI packages release checklist
      • Other PyPI packages maintained by Zulip
    • UI: input pills
      • Setup
      • Basic usage
      • Typeahead
        • onPillCreate and onPillRemove methods
    • Presence
    • Unread message synchronization
    • Billing (Development)
      • Common setup
      • Manual testing
        • Setup
        • Test card numbers
        • Flows to test
      • Upgrading Stripe API versions
      • Writing tests
    • Widgets
      • What is a widget?
      • /me messages
      • Polls, todo lists, and games
        • Data flow
        • Backward compatibility
        • Adding widgets
      • zform (trivia quiz bot)
      • Data flow
    • Slash commands
      • Data flow
      • Typeahead
    • Thumbnailing
      • libvips
      • Avatars
      • Emoji
      • Realm logos
      • Realm icons
      • File uploads
        • Images
        • Migrations
        • Videos and PDFs
  • Writing documentation
    • Documentation systems
      • Developer and sysadmin documentation
      • Core website documentation
      • User-facing documentation
        • Help center documentation
        • Integrations documentation
        • API documentation
      • Automated testing
    • Writing help center articles
      • Guide to writing help center articles
        • Getting started
        • Updating an existing article
        • Adding a new article
        • Redirecting an existing article
      • Writing style
        • User interface
        • Voice
        • Keyboard shortcuts
      • Markdown features
        • Images
        • Icons
        • Macros
        • Tips and warnings
        • Tab switcher
    • Documenting an integration
      • Markdown macros
      • Writing guidelines
        • General writing guidelines
        • Guidelines for specific steps
        • Screenshots
    • Documenting REST API endpoints
      • How it works
        • Title and description
        • Usage examples
        • Parameters
        • Response with examples
      • Step by step guide
      • Why a custom system?
      • Debugging schema validation errors
        • Deconstructing the error output
        • Adding a realm setting
    • OpenAPI configuration
      • Working with the zulip.yaml file
        • Configuration
        • Endpoint definitions
        • Schemas
      • Zulip Swagger YAML style:
      • Tips for working with YAML:
        • Formatting help:
        • Examples:
  • Translating Zulip
    • Translation guidelines
      • Translators’ workflow
        • Testing translations
        • Machine translation
        • Translation style guides
        • Capitalization
    • Internationalization for developers
      • How internationalization impacts Zulip’s UI
      • What should be marked for translation
      • How to mark a string for translation
      • Translation syntax in Zulip
        • Web application translations
        • Server translations
      • Translation process
      • Translation resource files
      • Working with Transifex
        • Transifex config
        • Transifex CLI setup
      • Additional resources
    • Chinese translation style guide(中文翻译指南)
      • Note(题记)
      • Terms(术语)
      • Phrases(习惯用语)
      • Others(其它)
    • Finnish translation style guide
      • Guidelines
        • Word order
        • Grammatical case (Sijamuodot)
        • Loan word (Lainasanat)
        • Please, in error messages
        • Zulip word inflection
        • Your -expression
        • Comma
      • Terms
      • Other
    • French translation style guide
      • Community
      • Rules
      • Terms
    • German translation style guide (Richtlinien für die deutsche Übersetzung)
      • Rules
        • Formal or informal?
        • Gender-inclusive language
        • Form of address
        • Form of instruction
        • Rules for labels
        • Concatenation of words
        • Anglicisms
        • Special characters
        • False friends
        • Other
      • Terms (Begriffe)
      • Phrases (Ausdrücke)
      • Other (Verschiedenes)
    • Hindi translation style guide (हिन्दी अनुवाद शैली मार्गदर्शक)
      • Terms (शर्तें)
      • Phrases (वाक्यांश)
      • Others (अन्य)
    • Japanese translation style guide
      • Rules
      • Terms
    • Polish translation style guide
      • Special terms used in Zulip
    • Russian translation style guide
      • Перевод некоторых терминов
    • Spanish translation style guide
      • Términos
      • Frases
      • Otros
    • Urdu translation style guide(انداذِ ترجمہ کا رہنما)
      • Terms(اصطلاحات)
      • Phrases (فِقْرے)
      • Others(مختلف دیگر)
  • Outreach programs
    • Outreach programs overview
      • About Zulip
      • Outreach program experience
    • How to apply
      • Application criteria
      • Getting started
      • Putting together your application
        • What to include
        • Project proposals
        • Circulating your application for feedback
    • How to have an amazing experience
      • Your goals
      • You and your mentor
      • Communication and check-ins
        • Getting feedback and advice
        • How to post your check-ins
      • Peer reviews
      • How do I figure out what to work on?
        • Prioritization
        • What about my proposal?
        • Tips for finding issues to pick up
        • Staying productive
      • How else can I contribute?
      • Timeline extensions for GSoC
    • GSoC project ideas
      • Project size
      • Focus areas
      • Project ideas by area
        • Full stack and web frontend focused projects
        • Terminal app
        • Desktop app
        • Mobile app
    • Mentoring
      • Who can mentor
      • Supporting your mentee
        • Establishing communication patterns
      • Managing challenges
  • Index
Zulip
  • Development environment
  • Recommended environment setup tutorial
  • View page source

Recommended environment setup tutorial

This section guides first-time contributors through installing the Zulip development environment on Windows, macOS, and Linux.

The recommended method for installing the Zulip development environment is to use WSL 2 on Windows, and Vagrant with Docker on macOS and Linux.

All of these recommended methods work by creating a container or VM for the Zulip server and related services, with the Git repository containing your source code mounted inside it. This strategy allows the environment to be as reliable and portable as possible. The specific technologies (Vagrant/Docker and WSL 2) were chosen based on what technologies have been most reliable through our experience supporting the thousands of people who’ve set up the Zulip development environment.

Contents:

  • Requirements

  • Step 0: Set up Git & GitHub

  • Step 1: Install prerequisites

  • Step 2: Get Zulip code

  • Step 3: Start the development environment

  • Step 4: Developing

  • Troubleshooting and common errors

  • Specifying an Ubuntu mirror

  • Specifying a proxy

  • Customizing CPU and RAM allocation

Requirements

Installing the Zulip development environment requires downloading several hundred megabytes of dependencies. You will need an active internet connection throughout the entire installation processes. (See Specifying a proxy if you need a proxy to access the internet.)

  • 2GB available RAM

  • active broadband internet connection

  • GitHub account

  • Windows 64-bit (Windows 10 recommended)

  • hardware virtualization enabled (VT-x or AMD-V)

  • administrator access

  • macOS (10.11 El Capitan or newer recommended)

  • Ubuntu 22.04, or 24.04

  • Debian 12

  • tested for Fedora 36

  • Any Linux distribution should work, if it supports Git, Vagrant and Docker. We don’t maintain documentation for installing Vagrant, Docker, and other dependencies on those systems, so you’ll want to roughly follow the Ubuntu/Debian instructions, using upstream documentation for installing dependencies.

Step 0: Set up Git & GitHub

You can skip this step if you already have Git, GitHub, and SSH access to GitHub working on your machine.

Follow our Git guide in order to install Git, set up a GitHub account, create an SSH key to access code on GitHub efficiently, etc. Be sure to create an SSH key and add it to your GitHub account using these instructions.

Step 1: Install prerequisites

Zulip’s development environment is most easily set up on Windows using the Windows Subsystem for Linux (WSL 2) installation method described here. We require version 0.67.6+ of WSL 2.

  1. Enable virtualization through your BIOS settings. This sequence depends on your specific hardware and brand, but here are some basic instructions.

  2. Install WSL 2, which includes installing an Ubuntu WSL distribution. Using an existing distribution will probably work, but a fresh distribution is recommended if you previously installed other software in your WSL environment that might conflict with the Zulip environment.

  3. It is required to enable systemd for WSL 2 to manage the database, cache and other services. To configure it, please follow these instructions. Then, you will need to restart WSL 2 before continuing.

  4. Launch the Ubuntu shell as an administrator and run the following command:

    $ sudo apt update && sudo apt upgrade
    
  5. Install dependencies with the following command:

    $ sudo apt install rabbitmq-server memcached redis-server postgresql
    
  6. Open /etc/rabbitmq/rabbitmq-env.conf using, for example:

    $ sudo nano /etc/rabbitmq/rabbitmq-env.conf
    

    Confirm the following lines are at the end of your file, and add them if not present:

    NODE_IP_ADDRESS=127.0.0.1
    NODE_PORT=5672
    

    Then save your changes (Ctrl+O, then Enter to confirm the path), and exit nano (Ctrl+X).

  7. Run the command below to make sure you are inside the WSL disk and not in a Windows mounted disk. You will run into permission issues if you run ./tools/provision from zulip in a Windows mounted disk.

    $ cd ~  # or cd /home/USERNAME
    
  8. Create a new SSH key for the WSL 2 virtual machine and add it to your GitHub account. Note that SSH keys linked to your Windows computer will not work within the virtual machine.

WSL 2 can be uninstalled by following Microsoft’s documentation

  1. Install Vagrant (latest).

  2. Install Docker Desktop (latest).

  3. Open the Docker desktop app’s settings panel, and choose osxfs (legacy) under “Choose file sharing implementation for your containers.”

1. Install Vagrant, Docker, and Git

Install vagrant:

$ wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
$ sudo apt update && sudo apt install vagrant

Install Docker and Git:

$ sudo apt install docker.io git

2. Add yourself to the docker group:

$ sudo adduser $USER docker
Adding user `YOURUSERNAME' to group `docker' ...
Adding user YOURUSERNAME to group docker
Done.

You will need to reboot for this change to take effect. If it worked, you will see docker in your list of groups:

$ groups | grep docker
YOURUSERNAME adm cdrom sudo dip plugdev lpadmin sambashare docker

3. Make sure the Docker daemon is running:

If you had previously installed and removed an older version of Docker, an Ubuntu bug may prevent Docker from being automatically enabled and started after installation. You can check using the following:

$ systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
   Active: active (running) since Mon 2019-07-15 23:20:46 IST; 18min ago

If the service is not running, you’ll see Active: inactive (dead) on the second line, and will need to enable and start the Docker service using the following:

$ sudo systemctl unmask docker
$ sudo systemctl enable docker
$ sudo systemctl start docker

1. Install Vagrant, Docker, and Git

$ sudo yum install vagrant git moby-engine

Fedora does not include the official docker-ce package in their repositories. They provide the package moby-engine which you can choose instead. In case you prefer the official docker distribution, you can follow their documentation to install Docker on Fedora.

2. Add yourself to the docker group:

$ sudo adduser $USER docker
Adding user `YOURUSERNAME' to group `docker' ...
Adding user YOURUSERNAME to group docker
Done.

You will need to reboot for this change to take effect. If it worked, you will see docker in your list of groups:

$ groups | grep docker
YOURUSERNAME adm cdrom sudo dip plugdev lpadmin sambashare docker

3. Make sure the Docker daemon is running:

If you had previously installed and removed an older version of Docker, an Ubuntu bug may prevent Docker from being automatically enabled and started after installation. You can check using the following:

$ systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
   Active: active (running) since Mon 2019-07-15 23:20:46 IST; 18min ago

If the service is not running, you’ll see Active: inactive (dead) on the second line, and will need to enable and start the Docker service using the following:

$ sudo systemctl unmask docker
$ sudo systemctl enable docker
$ sudo systemctl start docker

Step 2: Get Zulip code

  1. In your browser, visit https://github.com/zulip/zulip and click the Fork button. You will need to be logged in to GitHub to do this.

  2. Open Terminal (macOS/Linux) or Git BASH (Windows; must run as an Administrator).

  3. In Terminal/Git BASH, clone your fork of the Zulip repository and connect the Zulip upstream repository:

$ git clone --config pull.rebase git@github.com:YOURUSERNAME/zulip.git
$ cd zulip
$ git remote add -f upstream https://github.com/zulip/zulip.git

This will create a zulip directory and download the Zulip code into it.

Don’t forget to replace YOURUSERNAME with your Git username. You will see something like:

$ git clone --config pull.rebase git@github.com:YOURUSERNAME/zulip.git
Cloning into 'zulip'...
remote: Counting objects: 73571, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 73571 (delta 1), reused 0 (delta 0), pack-reused 73569
Receiving objects: 100% (73571/73571), 105.30 MiB | 6.46 MiB/s, done.
Resolving deltas: 100% (51448/51448), done.
Checking connectivity... done.
Checking out files: 100% (1912/1912), done.

Step 3: Start the development environment

Run the following to install the Zulip development environment and start it. (If Windows Firewall creates popups to block services, simply click Allow access.)

$ # Install/update the Zulip development environment
$ ./tools/provision
$ # Enter the Zulip Python environment
$ source .venv/bin/activate
$ # Start the development server
$ ./tools/run-dev

If you are facing problems or you see error messages after running ./tools/run-dev, you can try running ./tools/provision again.

Change into the zulip directory and tell Vagrant to start the Zulip development environment with vagrant up:

$ cd zulip
$ vagrant plugin install vagrant-vbguest
$ vagrant up --provider=virtualbox

The first time you run this command it will take some time because Vagrant does the following:

  • downloads the base Ubuntu 22.04 virtual machine/Docker image

  • configures this virtual machine/container for use with Zulip,

  • creates a shared directory mapping your clone of the Zulip code inside the virtual machine/container at ~/zulip

  • runs the ./tools/provision script inside the virtual machine/container, which downloads all required dependencies, sets up the Python environment for the Zulip development server, and initializes a default test database. We call this process “provisioning”, and it is documented in some detail in our dependencies documentation.

You will need an active internet connection during the entire process. (See Specifying a proxy if you need a proxy to access the internet.) vagrant up can fail while provisioning if your Internet connection is unreliable. To retry, you can use vagrant provision (vagrant up will just boot the guest without provisioning after the first time). Other common issues are documented in the Troubleshooting and common errors section. If that doesn’t help, please visit #provision help in the Zulip development community server for real-time help.

On Windows, you will see the message The system cannot find the path specified. several times. This is normal and is not a problem.

Once vagrant up has completed, connect to the development environment with vagrant ssh:

$ vagrant ssh

You should see output that starts like this:

Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-92-generic x86_64)

Congrats, you’re now inside the Zulip development environment!

You can confirm this by looking at the command prompt, which starts with (zulip-server) vagrant@. If it just starts with vagrant@, your provisioning failed and you should look at the troubleshooting section.

Next, start the Zulip server:

(zulip-server) vagrant@vagrant:/srv/zulip$ ./tools/run-dev

You will see something like:

Starting Zulip on:

        http://localhost:9991/

Internal ports:
   9991: Development server proxy (connect here)
   9992: Django
   9993: Tornado
   9994: webpack

Tornado server (re)started on port 9993

2023-12-15 20:57:14.206 INFO [process_queue] 13 queue worker threads were launched
frontend:
  frontend (webpack 5.89.0) compiled successfully in 8054 ms

Now the Zulip server should be running and accessible. Verify this by navigating to http://localhost:9991/devlogin in the browser on your main machine.

You should see something like this:

Image of Zulip devlogin

The Zulip server will continue to run and send output to the terminal window. When you navigate to Zulip in your browser, check your terminal and you should see something like:

2016-05-04 18:21:57,547 INFO     127.0.0.1       GET     302 582ms (+start: 417ms) / (unauth@zulip via ?)
[04/May/2016 18:21:57]"GET / HTTP/1.0" 302 0
2016-05-04 18:21:57,568 INFO     127.0.0.1       GET     301   4ms /login (unauth@zulip via ?)
[04/May/2016 18:21:57]"GET /login HTTP/1.0" 301 0
2016-05-04 18:21:57,819 INFO     127.0.0.1       GET     200 209ms (db: 7ms/2q) /login/ (unauth@zulip via ?)

Change into the zulip directory and tell Vagrant to start the Zulip development environment with vagrant up:

$ cd zulip
$ vagrant up --provider=docker

Important note: There is a known upstream issue on macOS that can cause provisioning to fail with ERR_PNPM_LINKING_FAILED or other errors. The temporary fix is to open the Docker desktop app’s settings panel, and choose osxfs (legacy) under “Choose file sharing implementation for your containers.” Once Docker restarts, you should be able to successfully run vagrant up --provider=docker. Back in Docker, you can return to using VirtioFS for better system performance while developing, but you may need to revert to osxfs (legacy) whenever you need to re-provision.

The first time you run this command it will take some time because Vagrant does the following:

  • downloads the base Ubuntu 22.04 virtual machine/Docker image

  • configures this virtual machine/container for use with Zulip,

  • creates a shared directory mapping your clone of the Zulip code inside the virtual machine/container at ~/zulip

  • runs the ./tools/provision script inside the virtual machine/container, which downloads all required dependencies, sets up the Python environment for the Zulip development server, and initializes a default test database. We call this process “provisioning”, and it is documented in some detail in our dependencies documentation.

You will need an active internet connection during the entire process. (See Specifying a proxy if you need a proxy to access the internet.) vagrant up can fail while provisioning if your Internet connection is unreliable. To retry, you can use vagrant provision (vagrant up will just boot the guest without provisioning after the first time). Other common issues are documented in the Troubleshooting and common errors section. If that doesn’t help, please visit #provision help in the Zulip development community server for real-time help.

Once vagrant up has completed, connect to the development environment with vagrant ssh:

$ vagrant ssh

You should see output that starts like this:

Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-92-generic x86_64)

Congrats, you’re now inside the Zulip development environment!

You can confirm this by looking at the command prompt, which starts with (zulip-server) vagrant@. If it just starts with vagrant@, your provisioning failed and you should look at the troubleshooting section.

Next, start the Zulip server:

(zulip-server) vagrant@vagrant:/srv/zulip$ ./tools/run-dev

You will see something like:

Starting Zulip on:

        http://localhost:9991/

Internal ports:
   9991: Development server proxy (connect here)
   9992: Django
   9993: Tornado
   9994: webpack

Tornado server (re)started on port 9993

2023-12-15 20:57:14.206 INFO [process_queue] 13 queue worker threads were launched
frontend:
  frontend (webpack 5.89.0) compiled successfully in 8054 ms

Now the Zulip server should be running and accessible. Verify this by navigating to http://localhost:9991/devlogin in the browser on your main machine.

You should see something like this:

Image of Zulip devlogin

The Zulip server will continue to run and send output to the terminal window. When you navigate to Zulip in your browser, check your terminal and you should see something like:

2016-05-04 18:21:57,547 INFO     127.0.0.1       GET     302 582ms (+start: 417ms) / (unauth@zulip via ?)
[04/May/2016 18:21:57]"GET / HTTP/1.0" 302 0
2016-05-04 18:21:57,568 INFO     127.0.0.1       GET     301   4ms /login (unauth@zulip via ?)
[04/May/2016 18:21:57]"GET /login HTTP/1.0" 301 0
2016-05-04 18:21:57,819 INFO     127.0.0.1       GET     200 209ms (db: 7ms/2q) /login/ (unauth@zulip via ?)

Change into the zulip directory and tell Vagrant to start the Zulip development environment with vagrant up:

$ cd zulip
$ vagrant up --provider=docker

The first time you run this command it will take some time because Vagrant does the following:

  • downloads the base Ubuntu 22.04 virtual machine/Docker image

  • configures this virtual machine/container for use with Zulip,

  • creates a shared directory mapping your clone of the Zulip code inside the virtual machine/container at ~/zulip

  • runs the ./tools/provision script inside the virtual machine/container, which downloads all required dependencies, sets up the Python environment for the Zulip development server, and initializes a default test database. We call this process “provisioning”, and it is documented in some detail in our dependencies documentation.

You will need an active internet connection during the entire process. (See Specifying a proxy if you need a proxy to access the internet.) vagrant up can fail while provisioning if your Internet connection is unreliable. To retry, you can use vagrant provision (vagrant up will just boot the guest without provisioning after the first time). Other common issues are documented in the Troubleshooting and common errors section. If that doesn’t help, please visit #provision help in the Zulip development community server for real-time help.

Once vagrant up has completed, connect to the development environment with vagrant ssh:

$ vagrant ssh

You should see output that starts like this:

Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-92-generic x86_64)

Congrats, you’re now inside the Zulip development environment!

You can confirm this by looking at the command prompt, which starts with (zulip-server) vagrant@. If it just starts with vagrant@, your provisioning failed and you should look at the troubleshooting section.

Next, start the Zulip server:

(zulip-server) vagrant@vagrant:/srv/zulip$ ./tools/run-dev

You will see something like:

Starting Zulip on:

        http://localhost:9991/

Internal ports:
   9991: Development server proxy (connect here)
   9992: Django
   9993: Tornado
   9994: webpack

Tornado server (re)started on port 9993

2023-12-15 20:57:14.206 INFO [process_queue] 13 queue worker threads were launched
frontend:
  frontend (webpack 5.89.0) compiled successfully in 8054 ms

Now the Zulip server should be running and accessible. Verify this by navigating to http://localhost:9991/devlogin in the browser on your main machine.

You should see something like this:

Image of Zulip devlogin

The Zulip server will continue to run and send output to the terminal window. When you navigate to Zulip in your browser, check your terminal and you should see something like:

2016-05-04 18:21:57,547 INFO     127.0.0.1       GET     302 582ms (+start: 417ms) / (unauth@zulip via ?)
[04/May/2016 18:21:57]"GET / HTTP/1.0" 302 0
2016-05-04 18:21:57,568 INFO     127.0.0.1       GET     301   4ms /login (unauth@zulip via ?)
[04/May/2016 18:21:57]"GET /login HTTP/1.0" 301 0
2016-05-04 18:21:57,819 INFO     127.0.0.1       GET     200 209ms (db: 7ms/2q) /login/ (unauth@zulip via ?)

Change into the zulip directory and tell Vagrant to start the Zulip development environment with vagrant up:

$ cd zulip
$ vagrant up --provider=docker

The first time you run this command it will take some time because Vagrant does the following:

  • downloads the base Ubuntu 22.04 virtual machine/Docker image

  • configures this virtual machine/container for use with Zulip,

  • creates a shared directory mapping your clone of the Zulip code inside the virtual machine/container at ~/zulip

  • runs the ./tools/provision script inside the virtual machine/container, which downloads all required dependencies, sets up the Python environment for the Zulip development server, and initializes a default test database. We call this process “provisioning”, and it is documented in some detail in our dependencies documentation.

You will need an active internet connection during the entire process. (See Specifying a proxy if you need a proxy to access the internet.) vagrant up can fail while provisioning if your Internet connection is unreliable. To retry, you can use vagrant provision (vagrant up will just boot the guest without provisioning after the first time). Other common issues are documented in the Troubleshooting and common errors section. If that doesn’t help, please visit #provision help in the Zulip development community server for real-time help.

Once vagrant up has completed, connect to the development environment with vagrant ssh:

$ vagrant ssh

You should see output that starts like this:

Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-92-generic x86_64)

Congrats, you’re now inside the Zulip development environment!

You can confirm this by looking at the command prompt, which starts with (zulip-server) vagrant@. If it just starts with vagrant@, your provisioning failed and you should look at the troubleshooting section.

Next, start the Zulip server:

(zulip-server) vagrant@vagrant:/srv/zulip$ ./tools/run-dev

You will see something like:

Starting Zulip on:

        http://localhost:9991/

Internal ports:
   9991: Development server proxy (connect here)
   9992: Django
   9993: Tornado
   9994: webpack

Tornado server (re)started on port 9993

2023-12-15 20:57:14.206 INFO [process_queue] 13 queue worker threads were launched
frontend:
  frontend (webpack 5.89.0) compiled successfully in 8054 ms

Now the Zulip server should be running and accessible. Verify this by navigating to http://localhost:9991/devlogin in the browser on your main machine.

You should see something like this:

Image of Zulip devlogin

The Zulip server will continue to run and send output to the terminal window. When you navigate to Zulip in your browser, check your terminal and you should see something like:

2016-05-04 18:21:57,547 INFO     127.0.0.1       GET     302 582ms (+start: 417ms) / (unauth@zulip via ?)
[04/May/2016 18:21:57]"GET / HTTP/1.0" 302 0
2016-05-04 18:21:57,568 INFO     127.0.0.1       GET     301   4ms /login (unauth@zulip via ?)
[04/May/2016 18:21:57]"GET /login HTTP/1.0" 301 0
2016-05-04 18:21:57,819 INFO     127.0.0.1       GET     200 209ms (db: 7ms/2q) /login/ (unauth@zulip via ?)

Step 4: Developing

Where to edit files

You’ll work by editing files on your host machine, in the directory where you cloned Zulip. Use your favorite editor (Sublime, Atom, Vim, Emacs, Notepad++, etc.).

When you save changes they will be synced automatically to the Zulip development environment on the virtual machine/container.

Each component of the Zulip development server will automatically restart itself or reload data appropriately when you make changes. So, to see your changes, all you usually have to do is reload your browser. More details on how this works are available below.

Zulip’s whitespace rules are all enforced by linters, so be sure to run tools/lint often to make sure you’re following our coding style (or use tools/setup-git-repo to run it on just the changed files automatically whenever you commit).

VSCode setup (optional)

The Visual Studio Code Remote - WSL extension is recommended for editing files when developing with WSL. When you have it installed, you can run:

$ code .

to open VS Code connected to your WSL environment. See the Remote development in WSL tutorial for more information.

If your preferred editor is Visual Studio Code, the Visual Studio Code Remote - SSH extension is recommended for editing files when developing with Vagrant. When you have it installed, you can run:

$ code .

to open VS Code connected to your Vagrant environment. See the Remote development over SSH tutorial for more information.

When using this plugin with Vagrant, you will want to run the command vagrant ssh-config from your zulip folder:

$ vagrant ssh-config
Host default
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /PATH/TO/zulip/.vagrant/machines/default/docker/private_key
  IdentitiesOnly yes
  LogLevel FATAL
  PubkeyAcceptedKeyTypes +ssh-rsa
  HostKeyAlgorithms +ssh-rsa

Then copy that config into your ~/.ssh/config file. You may want to change the host name from default to something more descriptive, like zulip. Finally, refresh the known remotes in Visual Studio Code’s Remote Explorer.

If your preferred editor is Visual Studio Code, the Visual Studio Code Remote - SSH extension is recommended for editing files when developing with Vagrant. When you have it installed, you can run:

$ code .

to open VS Code connected to your Vagrant environment. See the Remote development over SSH tutorial for more information.

When using this plugin with Vagrant, you will want to run the command vagrant ssh-config from your zulip folder:

$ vagrant ssh-config
Host default
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /PATH/TO/zulip/.vagrant/machines/default/docker/private_key
  IdentitiesOnly yes
  LogLevel FATAL
  PubkeyAcceptedKeyTypes +ssh-rsa
  HostKeyAlgorithms +ssh-rsa

Then copy that config into your ~/.ssh/config file. You may want to change the host name from default to something more descriptive, like zulip. Finally, refresh the known remotes in Visual Studio Code’s Remote Explorer.

If your preferred editor is Visual Studio Code, the Visual Studio Code Remote - SSH extension is recommended for editing files when developing with Vagrant. When you have it installed, you can run:

$ code .

to open VS Code connected to your Vagrant environment. See the Remote development over SSH tutorial for more information.

When using this plugin with Vagrant, you will want to run the command vagrant ssh-config from your zulip folder:

$ vagrant ssh-config
Host default
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /PATH/TO/zulip/.vagrant/machines/default/docker/private_key
  IdentitiesOnly yes
  LogLevel FATAL
  PubkeyAcceptedKeyTypes +ssh-rsa
  HostKeyAlgorithms +ssh-rsa

Then copy that config into your ~/.ssh/config file. You may want to change the host name from default to something more descriptive, like zulip. Finally, refresh the known remotes in Visual Studio Code’s Remote Explorer.

If your preferred editor is Visual Studio Code, the Visual Studio Code Remote - SSH extension is recommended for editing files when developing with Vagrant. When you have it installed, you can run:

$ code .

to open VS Code connected to your Vagrant environment. See the Remote development over SSH tutorial for more information.

When using this plugin with Vagrant, you will want to run the command vagrant ssh-config from your zulip folder:

$ vagrant ssh-config
Host default
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /PATH/TO/zulip/.vagrant/machines/default/docker/private_key
  IdentitiesOnly yes
  LogLevel FATAL
  PubkeyAcceptedKeyTypes +ssh-rsa
  HostKeyAlgorithms +ssh-rsa

Then copy that config into your ~/.ssh/config file. You may want to change the host name from default to something more descriptive, like zulip. Finally, refresh the known remotes in Visual Studio Code’s Remote Explorer.

Understanding run-dev debugging output

It’s good to have the terminal running ./tools/run-dev up as you work since error messages including tracebacks along with every backend request will be printed there.

See Logging for further details on the run-dev console output.

Committing and pushing changes with Git

When you’re ready to commit or push changes via Git, you will do this by running Git commands in Terminal (macOS/Linux) or Git BASH (Windows) in the directory where you cloned Zulip on your main machine.

If you’re new to working with Git/GitHub, check out our Git & GitHub guide.

Maintaining the development environment

If after rebasing onto a new version of the Zulip server, you receive new errors while starting the Zulip server or running tests, this is probably not because Zulip’s main branch is broken. Instead, this is likely because we’ve recently merged changes to the development environment provisioning process that you need to apply to your development environment. To update your environment, you’ll need to re-provision using tools/provision from your Zulip checkout; this should complete in about a minute.

After provisioning, you’ll want to (re)start the Zulip development server.

If you run into any trouble, #provision help in the Zulip development community server is a great place to ask for help.

If after rebasing onto a new version of the Zulip server, you receive new errors while starting the Zulip server or running tests, this is probably not because Zulip’s main branch is broken. Instead, this is likely because we’ve recently merged changes to the development environment provisioning process that you need to apply to your development environment. To update your environment, you’ll need to re-provision your Vagrant machine using vagrant provision (this just runs tools/provision from your Zulip checkout inside the Vagrant guest); this should complete in about a minute.

After provisioning, you’ll want to (re)start the Zulip development server.

If you run into any trouble, #provision help in the Zulip development community server is a great place to ask for help.

If after rebasing onto a new version of the Zulip server, you receive new errors while starting the Zulip server or running tests, this is probably not because Zulip’s main branch is broken. Instead, this is likely because we’ve recently merged changes to the development environment provisioning process that you need to apply to your development environment. To update your environment, you’ll need to re-provision your Vagrant machine using vagrant provision (this just runs tools/provision from your Zulip checkout inside the Vagrant guest); this should complete in about a minute.

After provisioning, you’ll want to (re)start the Zulip development server.

If you run into any trouble, #provision help in the Zulip development community server is a great place to ask for help.

If after rebasing onto a new version of the Zulip server, you receive new errors while starting the Zulip server or running tests, this is probably not because Zulip’s main branch is broken. Instead, this is likely because we’ve recently merged changes to the development environment provisioning process that you need to apply to your development environment. To update your environment, you’ll need to re-provision your Vagrant machine using vagrant provision (this just runs tools/provision from your Zulip checkout inside the Vagrant guest); this should complete in about a minute.

After provisioning, you’ll want to (re)start the Zulip development server.

If you run into any trouble, #provision help in the Zulip development community server is a great place to ask for help.

If after rebasing onto a new version of the Zulip server, you receive new errors while starting the Zulip server or running tests, this is probably not because Zulip’s main branch is broken. Instead, this is likely because we’ve recently merged changes to the development environment provisioning process that you need to apply to your development environment. To update your environment, you’ll need to re-provision your Vagrant machine using vagrant provision (this just runs tools/provision from your Zulip checkout inside the Vagrant guest); this should complete in about a minute.

After provisioning, you’ll want to (re)start the Zulip development server.

If you run into any trouble, #provision help in the Zulip development community server is a great place to ask for help.

Rebuilding the development environment

If you ever want to recreate your development environment again from scratch (e.g., to test a change you’ve made to the provisioning process, or because you think something is broken), you can do so using the following steps:

  1. To find the distribution name to unregister (delete), open Command Prompt or PowerShell and use the following command:

$ wsl --list --verbose

If you are unsure about which distribution to unregister, you can log into the WSL distributions to ensure you are deleting the one containing your development environment using the command:

wsl -d <Distribution Name>
  1. To uninstall your WSL distribution, enter the command:

$ wsl --unregister <Distribution Name>

For more information, checkout the official documentation for WSL commands

  1. Next, follow the setup instructions, starting from [Step 1: Install prerequisites]

If you just want to rebuild the development database, the following is much faster:

$ ./tools/rebuild-dev-database

For more details, see the schema migration documentation.

If you ever want to recreate your development environment again from scratch (e.g., to test a change you’ve made to the provisioning process, or because you think something is broken), you can do so using vagrant destroy and then vagrant up. This will usually be much faster than the original vagrant up since the base image is already cached on your machine (it takes about 5 minutes to run with a fast Internet connection).

Any additional programs (e.g., Zsh, emacs, etc.) or configuration that you may have installed in the development environment will be lost when you recreate it. To address this, you can create a script called tools/custom_provision in your Zulip Git checkout; and place any extra setup commands there. Vagrant will run tools/custom_provision every time you run vagrant provision (or create a Vagrant guest via vagrant up).

If you ever want to recreate your development environment again from scratch (e.g., to test a change you’ve made to the provisioning process, or because you think something is broken), you can do so using vagrant destroy and then vagrant up. This will usually be much faster than the original vagrant up since the base image is already cached on your machine (it takes about 5 minutes to run with a fast Internet connection).

Any additional programs (e.g., Zsh, emacs, etc.) or configuration that you may have installed in the development environment will be lost when you recreate it. To address this, you can create a script called tools/custom_provision in your Zulip Git checkout; and place any extra setup commands there. Vagrant will run tools/custom_provision every time you run vagrant provision (or create a Vagrant guest via vagrant up).

If you ever want to recreate your development environment again from scratch (e.g., to test a change you’ve made to the provisioning process, or because you think something is broken), you can do so using vagrant destroy and then vagrant up. This will usually be much faster than the original vagrant up since the base image is already cached on your machine (it takes about 5 minutes to run with a fast Internet connection).

Any additional programs (e.g., Zsh, emacs, etc.) or configuration that you may have installed in the development environment will be lost when you recreate it. To address this, you can create a script called tools/custom_provision in your Zulip Git checkout; and place any extra setup commands there. Vagrant will run tools/custom_provision every time you run vagrant provision (or create a Vagrant guest via vagrant up).

If you ever want to recreate your development environment again from scratch (e.g., to test a change you’ve made to the provisioning process, or because you think something is broken), you can do so using vagrant destroy and then vagrant up. This will usually be much faster than the original vagrant up since the base image is already cached on your machine (it takes about 5 minutes to run with a fast Internet connection).

Any additional programs (e.g., Zsh, emacs, etc.) or configuration that you may have installed in the development environment will be lost when you recreate it. To address this, you can create a script called tools/custom_provision in your Zulip Git checkout; and place any extra setup commands there. Vagrant will run tools/custom_provision every time you run vagrant provision (or create a Vagrant guest via vagrant up).

Shutting down the development environment for use later

On Windows with WSL 2, you do not need to shut down the environment. Simply close your terminal window(s).

Alternatively, you can use a command to terminate/shutdown your WSL2 environment with PowerShell using:

> wsl --terminate <environment_name>

To shut down but preserve the development environment so you can use it again later use vagrant halt or vagrant suspend.

You can do this from the same Terminal/Git BASH window that is running run-dev by pressing ^C to halt the server and then typing exit. Or you can halt Vagrant from another Terminal/Git BASH window.

From the window where run-dev is running:

2016-05-04 18:33:13,330 INFO     127.0.0.1       GET     200  92ms /register/ (unauth@zulip via ?)
^C
KeyboardInterrupt
(zulip-server) vagrant@vagrant:/srv/zulip$ exit
logout
Connection to 127.0.0.1 closed.
$

Now you can suspend the development environment:

$ vagrant suspend
==> default: Saving VM state and suspending execution...

If vagrant suspend doesn’t work, try vagrant halt:

$ vagrant halt
==> default: Attempting graceful shutdown of VM...

Check out the Vagrant documentation to learn more about suspend and halt.

To shut down but preserve the development environment so you can use it again later use vagrant halt or vagrant suspend.

You can do this from the same Terminal/Git BASH window that is running run-dev by pressing ^C to halt the server and then typing exit. Or you can halt Vagrant from another Terminal/Git BASH window.

From the window where run-dev is running:

2016-05-04 18:33:13,330 INFO     127.0.0.1       GET     200  92ms /register/ (unauth@zulip via ?)
^C
KeyboardInterrupt
(zulip-server) vagrant@vagrant:/srv/zulip$ exit
logout
Connection to 127.0.0.1 closed.
$

Now you can suspend the development environment:

$ vagrant suspend
==> default: Saving VM state and suspending execution...

If vagrant suspend doesn’t work, try vagrant halt:

$ vagrant halt
==> default: Attempting graceful shutdown of VM...

Check out the Vagrant documentation to learn more about suspend and halt.

To shut down but preserve the development environment so you can use it again later use vagrant halt or vagrant suspend.

You can do this from the same Terminal/Git BASH window that is running run-dev by pressing ^C to halt the server and then typing exit. Or you can halt Vagrant from another Terminal/Git BASH window.

From the window where run-dev is running:

2016-05-04 18:33:13,330 INFO     127.0.0.1       GET     200  92ms /register/ (unauth@zulip via ?)
^C
KeyboardInterrupt
(zulip-server) vagrant@vagrant:/srv/zulip$ exit
logout
Connection to 127.0.0.1 closed.
$

Now you can suspend the development environment:

$ vagrant suspend
==> default: Saving VM state and suspending execution...

If vagrant suspend doesn’t work, try vagrant halt:

$ vagrant halt
==> default: Attempting graceful shutdown of VM...

Check out the Vagrant documentation to learn more about suspend and halt.

To shut down but preserve the development environment so you can use it again later use vagrant halt or vagrant suspend.

You can do this from the same Terminal/Git BASH window that is running run-dev by pressing ^C to halt the server and then typing exit. Or you can halt Vagrant from another Terminal/Git BASH window.

From the window where run-dev is running:

2016-05-04 18:33:13,330 INFO     127.0.0.1       GET     200  92ms /register/ (unauth@zulip via ?)
^C
KeyboardInterrupt
(zulip-server) vagrant@vagrant:/srv/zulip$ exit
logout
Connection to 127.0.0.1 closed.
$

Now you can suspend the development environment:

$ vagrant suspend
==> default: Saving VM state and suspending execution...

If vagrant suspend doesn’t work, try vagrant halt:

$ vagrant halt
==> default: Attempting graceful shutdown of VM...

Check out the Vagrant documentation to learn more about suspend and halt.

Resuming the development environment

On Windows with WSL 2, to resume developing you just need to open a new Git BASH window. Then change into your zulip folder and verify the Python environment was properly activated (you should see (zulip-server)). If the (zulip-server) part is missing, run:

$ source .venv/bin/activate

When you’re ready to work on Zulip again, run vagrant up (no need to pass the --provider option required above). You will also need to connect to the virtual machine with vagrant ssh and re-start the Zulip server:

$ vagrant up
$ vagrant ssh

(zulip-server) vagrant@vagrant:/srv/zulip$ ./tools/run-dev

When you’re ready to work on Zulip again, run vagrant up (no need to pass the --provider option required above). You will also need to connect to the virtual machine with vagrant ssh and re-start the Zulip server:

$ vagrant up
$ vagrant ssh

(zulip-server) vagrant@vagrant:/srv/zulip$ ./tools/run-dev

When you’re ready to work on Zulip again, run vagrant up (no need to pass the --provider option required above). You will also need to connect to the virtual machine with vagrant ssh and re-start the Zulip server:

$ vagrant up
$ vagrant ssh

(zulip-server) vagrant@vagrant:/srv/zulip$ ./tools/run-dev

When you’re ready to work on Zulip again, run vagrant up (no need to pass the --provider option required above). You will also need to connect to the virtual machine with vagrant ssh and re-start the Zulip server:

$ vagrant up
$ vagrant ssh

(zulip-server) vagrant@vagrant:/srv/zulip$ ./tools/run-dev

Next steps

Next, read the following to learn more about developing for Zulip:

  • Git & GitHub guide

  • Using the development environment

  • Testing (and Configuring CI to run the full test suite against any branches you push to your fork, which can help you optimize your development workflow).

Troubleshooting and common errors

Below you’ll find a list of common errors and their solutions. Most issues are resolved by just provisioning again by running ./tools/provision (from /srv/zulip) inside the Vagrant guest (or equivalently vagrant provision from outside) or by running ./tools/provision in ~/zulip inside the WSL instance.

If these solutions aren’t working for you or you encounter an issue not documented below, there are a few ways to get further help:

  • Ask in #provision help in the Zulip development community server.

  • File an issue.

When reporting your issue, please include the following information:

  • The host operating system

  • The installation method (e.g., Vagrant or WSL)

  • Whether or not you are using a proxy

  • A copy of Zulip’s vagrant provisioning logs, available in /var/log/provision.log on your virtual machine or ~/zulip/var/log/provision.log on your WSL instance. If you choose to post just the error output, please include the beginning of the error output, not just the last few lines.

The output of tools/diagnose (run inside the Vagrant guest or WSL instance) is also usually helpful.

WSL2 users often encounter issues where services fail to start or remain inactive. Follow the steps below to diagnose and resolve such problems.

1. Check the Status of the Service

To verify if a service is running, use the following command:

$ systemctl status <service_name>

If the service is inactive, you can attempt to start it with:

$ systemctl start <service_name>

2. Diagnose Port Conflicts

Services like postgresql may fail to start due to port conflicts. These conflicts can be caused by:

  • Other services running in Windows.

  • Services running in another WSL2 instance.

Resolving Port Conflicts with Other WSL Instances

To resolve port conflicts with another WSL2 instance, stop the conflicting instance using the following command:

> wsl -t <WSL_Instance_Name>

After stopping the conflicting instance, restart your WSL instance with:

> wsl -d <Your_Zulip_Instance_Name>

Resolving Port Conflicts with Services Running on Windows

To resolve conflicts caused by Windows processes:

  1. Identify the process using the conflicting port by running:

> Get-Process -Id (Get-NetTCPConnection -LocalPort <your_port_number>).OwningProcess
  1. If a process is found, terminate it using:

> taskkill /PID <pid> /F
  1. Restart the Service or Enable Auto-Start

After resolving port conflicts, try restarting the service using:

$ systemctl start <service_name>

To ensure the service always starts on boot, enable it with:

$ systemctl enable <service_name>

Additional Tips

  • Use wsl --list to view all running WSL2 instances and their states.

  • Avoid overlapping port usage between WSL2 instances and Windows processes.

  • Keep a record of services and their associated port numbers to prevent conflicts in the future.

  • Ensure that you use a fresh WSL instance to setup the Zulip development environment to avoid dependency conflicts.

Vagrant guest doesn’t show (zulip-server) at start of prompt

This is caused by provisioning failing to complete successfully. You can see the errors in var/log/provision.log; it should end with something like this:

ESC[94mZulip development environment setup succeeded!ESC[0m

The ESC stuff are the terminal color codes that make it show as a nice blue in the terminal, which unfortunately looks ugly in the logs.

If you encounter an incomplete /var/log/provision.log file, you need to update your environment. Re-provision your Vagrant machine; if the problem persists, please come chat with us (see instructions above) for help.

After you provision successfully, you’ll need to exit your vagrant ssh shell and run vagrant ssh again to get the virtualenv setup properly.

ssl read error

If you receive the following error while running vagrant up:

SSL read: error:00000000:lib(0):func(0):reason(0), errno 104

It means that either your network connection is unstable and/or very slow. To resolve it, run vagrant up until it works (possibly on a better network connection).

ssh connection closed by remote host

On running vagrant ssh, if you see the following error:

ssh_exchange_identification: Connection closed by remote host

It usually means the Vagrant guest is not running, which is usually solved by rebooting the Vagrant guest via vagrant halt; vagrant up. See Vagrant was unable to communicate with the guest machine for more details.

Vagrant was unable to communicate with the guest machine

If you see the following error when you run vagrant up:

Timed out while waiting for the machine to boot. This means that
Vagrant was unable to communicate with the guest machine within
the configured ("config.vm.boot_timeout" value) time period.

If you look above, you should be able to see the error(s) that
Vagrant had when attempting to connect to the machine. These errors
are usually good hints as to what may be wrong.

If you're using a custom box, make sure that networking is properly
working and you're able to connect to the machine. It is a common
problem that networking isn't setup properly in these boxes.
Verify that authentication configurations are also setup properly,
as well.

If the box appears to be booting properly, you may want to increase
the timeout ("config.vm.boot_timeout") value.

This has a range of possible causes, that usually amount to a bug in Virtualbox or Vagrant. If you see this error, you usually can fix it by rebooting the guest via vagrant halt; vagrant up.

Vagrant up fails with subprocess.CalledProcessError

The vagrant up command basically does the following:

  • Downloads an Ubuntu image and starts it using a Vagrant provider.

  • Uses vagrant ssh to connect to that Ubuntu guest, and then runs tools/provision, which has a lot of subcommands that are executed via Python’s subprocess module. These errors mean that one of those subcommands failed.

To debug such errors, you can log in to the Vagrant guest machine by running vagrant ssh, which should present you with a standard shell prompt. You can debug interactively by using, for example, cd zulip && ./tools/provision, and then running the individual subcommands that failed. Once you’ve resolved the problem, you can rerun tools/provision to proceed; the provisioning system is designed to recover well from failures.

The Zulip provisioning system is generally highly reliable; the most common cause of issues here is a poor network connection (or one where you need a proxy to access the Internet and haven’t configured the development environment to use it).

Once you’ve provisioned successfully, you’ll get output like this:

Zulip development environment setup succeeded!
(zulip-server) vagrant@vagrant:/srv/zulip$

If the (zulip-server) part is missing, this is because your installation failed the first time before the Zulip virtualenv was created. You can fix this by just closing the shell and running vagrant ssh again, or using source .venv/bin/activate.

Finally, if you encounter any issues that weren’t caused by your Internet connection, please report them! We try hard to keep Zulip development environment provisioning free of bugs.

pip install fails during vagrant up on Linux

Likely causes are:

  1. Networking issues

  2. Insufficient RAM. Check whether you’ve allotted at least two gigabytes of RAM, which is the minimum Zulip requires. If not, go to your VM settings and increase the RAM, then restart the VM.

Vagrant was unable to mount VirtualBox shared folders

For the following error:

Vagrant was unable to mount VirtualBox shared folders. This is usually
because the filesystem "vboxsf" is not available. This filesystem is
made available via the VirtualBox Guest Additions and kernel
module. Please verify that these guest additions are properly
installed in the guest. This is not a bug in Vagrant and is usually
caused by a faulty Vagrant box. For context, the command attempted
was:

 mount -t vboxsf -o uid=1000,gid=1000 keys /keys

If this error starts happening unexpectedly, then just run:

$ vagrant halt
$ vagrant up

to reboot the guest. After this, you can do vagrant provision and vagrant ssh.

os.symlink error

If you receive the following error while running vagrant up:

==> default: Traceback (most recent call last):
==> default: File "./emoji_dump.py", line 75, in <module>
==> default:
==> default: os.symlink('unicode/{}.png'.format(code_point), 'out/{}.png'.format(name))
==> default: OSError
==> default: :
==> default: [Errno 71] Protocol error

Then Vagrant was not able to create a symbolic link.

First, if you are using Windows, make sure you have run Git BASH (or Cygwin) as an administrator. By default, only administrators can create symbolic links on Windows. Additionally UAC, a Windows feature intended to limit the impact of malware, can prevent even administrator accounts from creating symlinks. Turning off UAC will allow you to create symlinks. You can also try some of the solutions mentioned here.

If you ran Git BASH as administrator but you already had VirtualBox running, you might still get this error because VirtualBox is not running as administrator. In that case: close the Zulip VM with vagrant halt; close any other VirtualBox VMs that may be running; exit VirtualBox; and try again with vagrant up --provision from a Git BASH running as administrator.

Second, VirtualBox does not enable symbolic links by default. Vagrant starting with version 1.6.0 enables symbolic links for VirtualBox shared folder.

You can check to see that this is enabled for your virtual machine with vboxmanage command.

Get the name of your virtual machine by running vboxmanage list vms and then print out the custom settings for this virtual machine with vboxmanage getextradata YOURVMNAME enumerate:

$ vboxmanage list vms
"zulip_default_1462498139595_55484" {5a65199d-8afa-4265-b2f6-6b1f162f157d}

$ vboxmanage getextradata zulip_default_1462498139595_55484 enumerate
Key: VBoxInternal2/SharedFoldersEnableSymlinksCreate/srv_zulip, Value: 1
Key: supported, Value: false

If you see “command not found” when you try to run VBoxManage, you need to add the VirtualBox directory to your path. On Windows this is mostly likely C:\Program Files\Oracle\VirtualBox\.

If vboxmanage enumerate prints nothing, or shows a value of 0 for VBoxInternal2/SharedFoldersEnableSymlinksCreate/srv_zulip, then enable symbolic links by running this command in Terminal/Git BASH/Cygwin:

$ vboxmanage setextradata YOURVMNAME VBoxInternal2/SharedFoldersEnableSymlinksCreate/srv_zulip 1

The virtual machine needs to be shut down when you run this command.

Hyper-V error messages

If you get an error message on Windows about lack of Windows Home support for Hyper-V when running vagrant up, the problem is that Windows is incorrectly attempting to use Hyper-V rather than Virtualbox as the virtualization provider. You can fix this by explicitly passing the virtualbox provider to vagrant up:

$ vagrant up --provide=virtualbox

Connection timeout on vagrant up

If you see the following error after running vagrant up:

default: SSH address: 127.0.0.1:2222
default: SSH username: vagrant
default: SSH auth method: private key
default: Error: Connection timeout. Retrying...
default: Error: Connection timeout. Retrying...
default: Error: Connection timeout. Retrying...

A likely cause is that hardware virtualization is not enabled for your computer. This must be done via your computer’s BIOS settings. Look for a setting called VT-x (Intel) or (AMD-V).

If this is already enabled in your BIOS, double-check that you are running a 64-bit operating system.

For further information about troubleshooting Vagrant timeout errors see this post.

VBoxManage errors related to VT-x or WHvSetupPartition

There was an error while executing `VBoxManage`, a CLI used by Vagrant
for controlling VirtualBox. The command and stderr is shown below.

Command: ["startvm", "8924a681-b4e4-4b7a-96f2-4cb11619f123", "--type", "headless"]

Stderr: VBoxManage.exe: error: (VERR_NEM_MISSING_KERNEL_API).
VBoxManage.exe: error: VT-x is not available (VERR_VMX_NO_VMX)
VBoxManage.exe: error: Details: code E_FAIL (0x80004005), component ConsoleWrap, interface IConsole

or

Stderr: VBoxManage.exe: error: Call to WHvSetupPartition failed: ERROR_SUCCESS (Last=0xc000000d/87) (VERR_NEM_VM_CREATE_FAILED)
VBoxManage.exe: error: Details: code E_FAIL (0x80004005), component ConsoleWrap, interface IConsole

First, ensure that hardware virtualization support (VT-x or AMD-V) is enabled in your BIOS.

If the error persists, you may have run into an incompatibility between VirtualBox and Hyper-V on Windows. To disable Hyper-V, open command prompt as administrator, run bcdedit /set hypervisorlaunchtype off, and reboot. If you need to enable it later, run bcdedit /deletevalue hypervisorlaunchtype, and reboot.

OSError: [Errno 26] Text file busy

default: Traceback (most recent call last):
…
default:   File "/srv/zulip-py3-venv/lib/python3.6/shutil.py", line 426, in _rmtree_safe_fd
default:     os.rmdir(name, dir_fd=topfd)
default: OSError: [Errno 26] Text file busy: 'baremetrics'

This error is caused by a bug in recent versions of the VirtualBox Guest Additions for Linux on Windows hosts. You can check the running version of VirtualBox Guest Additions with this command:

$ vagrant ssh -- 'sudo modinfo -F version vboxsf'

The bug has not been fixed upstream as of this writing, but you may be able to work around it by downgrading VirtualBox Guest Additions to 5.2.44. To do this, create a ~/.zulip-vagrant-config file and add this line:

VBOXADD_VERSION 5.2.44

Then run these commands (yes, reload is needed twice):

$ vagrant plugin install vagrant-vbguest
$ vagrant reload
$ vagrant reload --provision

Vagrant guest doesn’t show (zulip-server) at start of prompt

This is caused by provisioning failing to complete successfully. You can see the errors in var/log/provision.log; it should end with something like this:

ESC[94mZulip development environment setup succeeded!ESC[0m

The ESC stuff are the terminal color codes that make it show as a nice blue in the terminal, which unfortunately looks ugly in the logs.

If you encounter an incomplete /var/log/provision.log file, you need to update your environment. Re-provision your Vagrant machine; if the problem persists, please come chat with us (see instructions above) for help.

After you provision successfully, you’ll need to exit your vagrant ssh shell and run vagrant ssh again to get the virtualenv setup properly.

ssl read error

If you receive the following error while running vagrant up:

SSL read: error:00000000:lib(0):func(0):reason(0), errno 104

It means that either your network connection is unstable and/or very slow. To resolve it, run vagrant up until it works (possibly on a better network connection).

ssh connection closed by remote host

On running vagrant ssh, if you see the following error:

ssh_exchange_identification: Connection closed by remote host

It usually means the Vagrant guest is not running, which is usually solved by rebooting the Vagrant guest via vagrant halt; vagrant up. See Vagrant was unable to communicate with the guest machine for more details.

Vagrant was unable to communicate with the guest machine

If you see the following error when you run vagrant up:

Timed out while waiting for the machine to boot. This means that
Vagrant was unable to communicate with the guest machine within
the configured ("config.vm.boot_timeout" value) time period.

If you look above, you should be able to see the error(s) that
Vagrant had when attempting to connect to the machine. These errors
are usually good hints as to what may be wrong.

If you're using a custom box, make sure that networking is properly
working and you're able to connect to the machine. It is a common
problem that networking isn't setup properly in these boxes.
Verify that authentication configurations are also setup properly,
as well.

If the box appears to be booting properly, you may want to increase
the timeout ("config.vm.boot_timeout") value.

This has a range of possible causes, that usually amount to a bug in Virtualbox or Vagrant. If you see this error, you usually can fix it by rebooting the guest via vagrant halt; vagrant up.

Vagrant up fails with subprocess.CalledProcessError

The vagrant up command basically does the following:

  • Downloads an Ubuntu image and starts it using a Vagrant provider.

  • Uses vagrant ssh to connect to that Ubuntu guest, and then runs tools/provision, which has a lot of subcommands that are executed via Python’s subprocess module. These errors mean that one of those subcommands failed.

To debug such errors, you can log in to the Vagrant guest machine by running vagrant ssh, which should present you with a standard shell prompt. You can debug interactively by using, for example, cd zulip && ./tools/provision, and then running the individual subcommands that failed. Once you’ve resolved the problem, you can rerun tools/provision to proceed; the provisioning system is designed to recover well from failures.

The Zulip provisioning system is generally highly reliable; the most common cause of issues here is a poor network connection (or one where you need a proxy to access the Internet and haven’t configured the development environment to use it).

Once you’ve provisioned successfully, you’ll get output like this:

Zulip development environment setup succeeded!
(zulip-server) vagrant@vagrant:/srv/zulip$

If the (zulip-server) part is missing, this is because your installation failed the first time before the Zulip virtualenv was created. You can fix this by just closing the shell and running vagrant ssh again, or using source .venv/bin/activate.

Finally, if you encounter any issues that weren’t caused by your Internet connection, please report them! We try hard to keep Zulip development environment provisioning free of bugs.

pip install fails during vagrant up on Linux

Likely causes are:

  1. Networking issues

  2. Insufficient RAM. Check whether you’ve allotted at least two gigabytes of RAM, which is the minimum Zulip requires. If not, go to your VM settings and increase the RAM, then restart the VM.

Unmet dependencies error

When running vagrant up or provision, if you see the following error:

==> default: E:unmet dependencies. Try 'apt-get -f install' with no packages (or specify a solution).

It means that your local apt repository has been corrupted, which can usually be resolved by executing the command:

$ apt-get -f install

Vagrant guest doesn’t show (zulip-server) at start of prompt

This is caused by provisioning failing to complete successfully. You can see the errors in var/log/provision.log; it should end with something like this:

ESC[94mZulip development environment setup succeeded!ESC[0m

The ESC stuff are the terminal color codes that make it show as a nice blue in the terminal, which unfortunately looks ugly in the logs.

If you encounter an incomplete /var/log/provision.log file, you need to update your environment. Re-provision your Vagrant machine; if the problem persists, please come chat with us (see instructions above) for help.

After you provision successfully, you’ll need to exit your vagrant ssh shell and run vagrant ssh again to get the virtualenv setup properly.

ssl read error

If you receive the following error while running vagrant up:

SSL read: error:00000000:lib(0):func(0):reason(0), errno 104

It means that either your network connection is unstable and/or very slow. To resolve it, run vagrant up until it works (possibly on a better network connection).

ssh connection closed by remote host

On running vagrant ssh, if you see the following error:

ssh_exchange_identification: Connection closed by remote host

It usually means the Vagrant guest is not running, which is usually solved by rebooting the Vagrant guest via vagrant halt; vagrant up. See Vagrant was unable to communicate with the guest machine for more details.

Vagrant was unable to communicate with the guest machine

If you see the following error when you run vagrant up:

Timed out while waiting for the machine to boot. This means that
Vagrant was unable to communicate with the guest machine within
the configured ("config.vm.boot_timeout" value) time period.

If you look above, you should be able to see the error(s) that
Vagrant had when attempting to connect to the machine. These errors
are usually good hints as to what may be wrong.

If you're using a custom box, make sure that networking is properly
working and you're able to connect to the machine. It is a common
problem that networking isn't setup properly in these boxes.
Verify that authentication configurations are also setup properly,
as well.

If the box appears to be booting properly, you may want to increase
the timeout ("config.vm.boot_timeout") value.

This has a range of possible causes, that usually amount to a bug in Virtualbox or Vagrant. If you see this error, you usually can fix it by rebooting the guest via vagrant halt; vagrant up.

Vagrant up fails with subprocess.CalledProcessError

The vagrant up command basically does the following:

  • Downloads an Ubuntu image and starts it using a Vagrant provider.

  • Uses vagrant ssh to connect to that Ubuntu guest, and then runs tools/provision, which has a lot of subcommands that are executed via Python’s subprocess module. These errors mean that one of those subcommands failed.

To debug such errors, you can log in to the Vagrant guest machine by running vagrant ssh, which should present you with a standard shell prompt. You can debug interactively by using, for example, cd zulip && ./tools/provision, and then running the individual subcommands that failed. Once you’ve resolved the problem, you can rerun tools/provision to proceed; the provisioning system is designed to recover well from failures.

The Zulip provisioning system is generally highly reliable; the most common cause of issues here is a poor network connection (or one where you need a proxy to access the Internet and haven’t configured the development environment to use it).

Once you’ve provisioned successfully, you’ll get output like this:

Zulip development environment setup succeeded!
(zulip-server) vagrant@vagrant:/srv/zulip$

If the (zulip-server) part is missing, this is because your installation failed the first time before the Zulip virtualenv was created. You can fix this by just closing the shell and running vagrant ssh again, or using source .venv/bin/activate.

Finally, if you encounter any issues that weren’t caused by your Internet connection, please report them! We try hard to keep Zulip development environment provisioning free of bugs.

pip install fails during vagrant up on Linux

Likely causes are:

  1. Networking issues

  2. Insufficient RAM. Check whether you’ve allotted at least two gigabytes of RAM, which is the minimum Zulip requires. If not, go to your VM settings and increase the RAM, then restart the VM.

Unmet dependencies error

When running vagrant up or provision, if you see the following error:

==> default: E:unmet dependencies. Try 'apt-get -f install' with no packages (or specify a solution).

It means that your local apt repository has been corrupted, which can usually be resolved by executing the command:

$ apt-get -f install

Vagrant guest doesn’t show (zulip-server) at start of prompt

This is caused by provisioning failing to complete successfully. You can see the errors in var/log/provision.log; it should end with something like this:

ESC[94mZulip development environment setup succeeded!ESC[0m

The ESC stuff are the terminal color codes that make it show as a nice blue in the terminal, which unfortunately looks ugly in the logs.

If you encounter an incomplete /var/log/provision.log file, you need to update your environment. Re-provision your Vagrant machine; if the problem persists, please come chat with us (see instructions above) for help.

After you provision successfully, you’ll need to exit your vagrant ssh shell and run vagrant ssh again to get the virtualenv setup properly.

ssl read error

If you receive the following error while running vagrant up:

SSL read: error:00000000:lib(0):func(0):reason(0), errno 104

It means that either your network connection is unstable and/or very slow. To resolve it, run vagrant up until it works (possibly on a better network connection).

ssh connection closed by remote host

On running vagrant ssh, if you see the following error:

ssh_exchange_identification: Connection closed by remote host

It usually means the Vagrant guest is not running, which is usually solved by rebooting the Vagrant guest via vagrant halt; vagrant up. See Vagrant was unable to communicate with the guest machine for more details.

Vagrant was unable to communicate with the guest machine

If you see the following error when you run vagrant up:

Timed out while waiting for the machine to boot. This means that
Vagrant was unable to communicate with the guest machine within
the configured ("config.vm.boot_timeout" value) time period.

If you look above, you should be able to see the error(s) that
Vagrant had when attempting to connect to the machine. These errors
are usually good hints as to what may be wrong.

If you're using a custom box, make sure that networking is properly
working and you're able to connect to the machine. It is a common
problem that networking isn't setup properly in these boxes.
Verify that authentication configurations are also setup properly,
as well.

If the box appears to be booting properly, you may want to increase
the timeout ("config.vm.boot_timeout") value.

This has a range of possible causes, that usually amount to a bug in Virtualbox or Vagrant. If you see this error, you usually can fix it by rebooting the guest via vagrant halt; vagrant up.

Vagrant up fails with subprocess.CalledProcessError

The vagrant up command basically does the following:

  • Downloads an Ubuntu image and starts it using a Vagrant provider.

  • Uses vagrant ssh to connect to that Ubuntu guest, and then runs tools/provision, which has a lot of subcommands that are executed via Python’s subprocess module. These errors mean that one of those subcommands failed.

To debug such errors, you can log in to the Vagrant guest machine by running vagrant ssh, which should present you with a standard shell prompt. You can debug interactively by using, for example, cd zulip && ./tools/provision, and then running the individual subcommands that failed. Once you’ve resolved the problem, you can rerun tools/provision to proceed; the provisioning system is designed to recover well from failures.

The Zulip provisioning system is generally highly reliable; the most common cause of issues here is a poor network connection (or one where you need a proxy to access the Internet and haven’t configured the development environment to use it).

Once you’ve provisioned successfully, you’ll get output like this:

Zulip development environment setup succeeded!
(zulip-server) vagrant@vagrant:/srv/zulip$

If the (zulip-server) part is missing, this is because your installation failed the first time before the Zulip virtualenv was created. You can fix this by just closing the shell and running vagrant ssh again, or using source .venv/bin/activate.

Finally, if you encounter any issues that weren’t caused by your Internet connection, please report them! We try hard to keep Zulip development environment provisioning free of bugs.

pip install fails during vagrant up on Linux

Likely causes are:

  1. Networking issues

  2. Insufficient RAM. Check whether you’ve allotted at least two gigabytes of RAM, which is the minimum Zulip requires. If not, go to your VM settings and increase the RAM, then restart the VM.

Unmet dependencies error

When running vagrant up or provision, if you see the following error:

==> default: E:unmet dependencies. Try 'apt-get -f install' with no packages (or specify a solution).

It means that your local apt repository has been corrupted, which can usually be resolved by executing the command:

$ apt-get -f install

Specifying an Ubuntu mirror

Bringing up a development environment for the first time involves downloading many packages from the Ubuntu archive. The Ubuntu cloud images use the global mirror http://archive.ubuntu.com/ubuntu/ by default, but you may find that you can speed up the download by using a local mirror closer to your location. To do this, create ~/.zulip-vagrant-config and add a line like this, replacing the URL as appropriate:

UBUNTU_MIRROR http://us.archive.ubuntu.com/ubuntu/

Specifying a proxy

If you need to use a proxy server to access the Internet, you will need to specify the proxy settings before running vagrant up. First, install the Vagrant plugin vagrant-proxyconf:

$ vagrant plugin install vagrant-proxyconf

Then create ~/.zulip-vagrant-config and add the following lines to it (with the appropriate values in it for your proxy):

HTTP_PROXY http://proxy_host:port
HTTPS_PROXY http://proxy_host:port
NO_PROXY localhost,127.0.0.1,.example.com,.zulipdev.com

For proxies that require authentication, the config will be a bit more complex, for example:

HTTP_PROXY http://userName:userPassword@192.168.1.1:8080
HTTPS_PROXY http://userName:userPassword@192.168.1.1:8080
NO_PROXY localhost,127.0.0.1,.example.com,.zulipdev.com

You’ll want to double-check your work for mistakes (a common one is using https:// when your proxy expects http://). Invalid proxy configuration can cause confusing/weird exceptions; if you’re using a proxy and get an error, the first thing you should investigate is whether you entered your proxy configuration correctly.

Now run vagrant up in your terminal to install the development server. If you ran vagrant up before and failed, you’ll need to run vagrant destroy first to clean up the failed installation.

If you no longer want to use proxy with Vagrant, you can remove the HTTP_PROXY and HTTPS_PROXY lines in ~/.zulip-vagrant-config and then do a vagrant reload.

Using a different port for Vagrant

You can also change the port on the host machine that Vagrant uses by adding to your ~/.zulip-vagrant-config file. E.g., if you set:

HOST_PORT 9971

(and vagrant reload to apply the new configuration), then you would visit http://localhost:9971/ to connect to your development server.

If you’d like to be able to connect to your development environment from other machines than the VM host, you can manually set the host IP address in the ~/.zulip-vagrant-config file as well. For example, if you set:

HOST_IP_ADDR 0.0.0.0

(and restart the Vagrant guest with vagrant reload), your host IP would be 0.0.0.0, a special value for the IP address that means any IP address can connect to your development server.

Customizing CPU and RAM allocation

When running Vagrant using a VM-based provider such as VirtualBox or VMware Fusion, CPU and RAM resources must be explicitly allocated to the guest system (with Docker and other container-based Vagrant providers, explicit allocation is unnecessary and the settings described here are ignored).

Our default Vagrant settings allocate 2 CPUs with 2 GiB of memory for the guest, which is sufficient to run everything in the development environment. If your host system has more CPUs, or you have enough RAM that you’d like to allocate more than 2 GiB to the guest, you can improve performance of the Zulip development environment by allocating more resources.

To do so, create a ~/.zulip-vagrant-config file containing the following lines:

GUEST_CPUS <number of cpus>
GUEST_MEMORY_MB <system memory (in MB)>

For example:

GUEST_CPUS 4
GUEST_MEMORY_MB 8192

would result in an allocation of 4 CPUs and 8 GiB of memory to the guest VM.

After changing the configuration, run vagrant reload to reboot the guest VM with your new configuration.

If at any time you wish to revert back to the default settings, simply remove the GUEST_CPUS and GUEST_MEMORY_MB lines from ~/.zulip-vagrant-config.

Previous Next

© Copyright 2012–2015 Dropbox, Inc., 2015–2021 Kandra Labs, Inc., and contributors.

Built with Sphinx using a theme provided by Read the Docs.