<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Citus Data Blog - Articles by Gurkan Indibay</title>
  <author>
    <name>Gurkan Indibay</name>
  </author>
  <subtitle>Scaling data and analytics with Postgres</subtitle>
  <id>https://www.citusdata.com/blog/</id>
  <link href="https://www.citusdata.com/blog/"/>
  <link href="https://www.citusdata.com/blog/feed/gurkan-indibay.xml" rel="self"/>
  <updated>2023-05-09T16:28:00+00:00</updated>
  <entry>
    <title>Evolving django-multitenant to build scalable SaaS apps on Postgres &amp; Citus</title>
    <link rel="alternate" href="https://www.citusdata.com/blog/2023/05/09/evolving-django-multitenant-to-build-scalable-saas-apps-on-postgres-and-citus/"/>
    <id>https://www.citusdata.com/blog/2023/05/09/evolving-django-multitenant-to-build-scalable-saas-apps-on-postgres-and-citus/</id>
    <published>2023-05-09T16:28:00+00:00</published>
    <updated>2023-05-09T16:28:00+00:00</updated>
    <author>Gurkan Indibay</author>
    <content type="html">&lt;p&gt;If you&amp;#39;re building a software application that serves multiple tenants, you may have already encountered the challenges of managing and isolating tenant-specific data. That&amp;#39;s where the &lt;a href="https://github.com/citusdata/django-multitenant"&gt;django-multitenant library&lt;/a&gt; comes in. This library, actively used since 2017 and now downloaded more than 10K times per month, offers a simple and flexible solution for building multi-tenant Django applications.&lt;/p&gt;

&lt;p&gt;In this blog post, we&amp;#39;ll dive deeper into the concept of multi-tenancy and explore how Django-multitenant can help you build scalable, secure, and maintainable multi-tenant applications on top of PostgreSQL and the &lt;a href="https://github.com/citusdata/citus"&gt;Citus database extension&lt;/a&gt;. We&amp;#39;ll also provide a practical example of how to use Django-multitenant in a real-world scenario. So, if you&amp;#39;re looking to simplify your multi-tenant development process, keep reading. &lt;/p&gt;

&lt;p&gt;If you already have a good understanding of multi-tenancy concepts, you can skip this multi-tenancy part and proceed to the sections below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="#automating-django-queries"&gt;Automating Django queries with django-multitenant&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#whats-new-in-django-multitenant"&gt;What&amp;#39;s new in django-multitenant version 3.2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;What is multi-tenancy?&lt;/h2&gt;

&lt;p&gt;Multitenancy is an architecture where a single instance of a software application serves multiple customers (tenants). Each tenant has its own data, configuration, and user interface, but shares the same codebase and infrastructure. It&amp;#39;s beneficial for SaaS applications as it allows for efficient resource usage, cost savings, and easier maintenance through simultaneous updates for all tenants.&lt;/p&gt;

&lt;h3&gt;Multitenancy challenges and approaches&lt;/h3&gt;

&lt;p&gt;When dealing with multitenancy in your application, you must design with a few database issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Tenant Isolation&lt;/strong&gt; : Ensure data and resource separation, route requests correctly, and manage tenant-specific configuration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt; : Design a scalable database architecture, partition data, and manage concurrent access to handle increasing tenant load.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security&lt;/strong&gt; : Implementing additional security measures to protect tenant data, secure data in transit and at rest, and manage access control.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Customization&lt;/strong&gt; : Allow easy customization of the application for each tenant, including configuration, branding, and workflows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt; : Optimize application performance through efficient database schema design, caching, and data access mechanisms.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are three approaches to managing multi-tenancy in applications.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Separate Databases&lt;/strong&gt; - Each tenant has their database.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shared Database, Separate Schemas&lt;/strong&gt;. Tenants have their own schemas but can reside in the same database.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shared Database, Shared Schema&lt;/strong&gt; - All tenants share the same schema and data objects should include tenant_id to denote ownership to a tenant.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first 3 approaches above (separate databases, and separate schemas) are out of scope for this blogpost.&lt;/p&gt;

&lt;p&gt;We will focus on the 3rd approach, the &amp;quot;&lt;strong&gt;Shared Database, Shared Schema&lt;/strong&gt;&amp;quot; approach.&lt;/p&gt;

&lt;h3&gt;Multitenancy with a shared database, shared schema&lt;/h3&gt;

&lt;p&gt;Django-multitenant supports sharing a schema between tenants, which offers advantages such as easier maintenance, reduced complexity, better resource utilization, improved scalability, and enhanced data security. However, this approach has two challenges: scaling Postgres databases for multiple tenants and dealing with complexity in application development.&lt;/p&gt;

&lt;p&gt;If you are currently in the process of developing a multi-tenant application using Django ORM on PostgreSQL using the shared schema approach, there are 2 problems needs to be addressed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Application complexity due to tenant ID addition into application&lt;/li&gt;
&lt;li&gt;Scaling the application while adding more tenants&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The django-multitenant library helps with the first problem above—application complexity— while the &lt;a href="https://www.citusdata.com/download/"&gt;Citus extension to Postgres&lt;/a&gt; can be used to enable distributed scale.&lt;/p&gt;

&lt;blockquote style="font:inherit"&gt;
&lt;p style="color:inherit;margin-bottom:1.5em"&gt;&lt;a href="https://github.com/citusdata/citus"&gt;Citus is&lt;/a&gt; available as open source, and as managed service as part of &lt;a href="https://learn.microsoft.com/azure/cosmos-db/postgresql/"&gt;Azure Cosmos DB for PostgreSQL&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can use the django-multitenant library with Citus seamlessly whether using Citus open source or on the Azure Cosmos DB for PostgreSQL managed service. django-multitenant helps with data isolation in application development. Data isolation requires careful development process, including filtering queries by tenant_id, including tenant column in join clauses, and recording correct tenant data when saving user input which django-multitenant easy for you.&lt;/p&gt;

&lt;h2 id="automating-django-queries"&gt;Automating Django queries with django-multitenant&lt;/h2&gt;

&lt;p&gt;The django-multitenant library extends the Django ORM to automatically and uniformly scope queries by tenant. It adds the &lt;code&gt;tenant_id&lt;/code&gt; in both fetching and manipulating data. It&amp;#39;s a mature library which is downloaded over 10,000 times each month.&lt;/p&gt;

&lt;p&gt;Features of django-multitenant:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You can set the tenant with one method call, and existing application code will access the proper tenant without extensive changes.&lt;/li&gt;
&lt;li&gt;With a tenant set, the library will seamlessly include the tenant ID in join operations, eliminating the need to manually add tenant IDs.&lt;/li&gt;
&lt;li&gt;Supports standard Django and the Django Rest Framework to streamline integration.&lt;/li&gt;
&lt;li&gt;Implements helper classes for Citus, a distributed Postgres extension—and facilitates table distribution during the database migration process.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id="whats-new-in-django-multitenant"&gt;What's new in django-multitenant version 3.2&lt;/h2&gt;

&lt;p&gt;With the start of 2023, the django-multitenant project has been reinvigorated with active development and new releases. The latest release, version 3.2, includes the following critical changes and new features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Added DjangoRestFramework support, enabling you to easily create RESTful APIs that can handle multi-tenant data.&lt;/li&gt;
&lt;li&gt;Improved model migration guidelines, which can help you migrate tenant-specific data seamlessly.&lt;/li&gt;
&lt;li&gt;Support for the latest version of Django (version 4.1), allowing you to take advantage of its latest features and improvements.&lt;/li&gt;
&lt;li&gt;Ability to automatically set the tenant for ManyToMany related models.&lt;/li&gt;
&lt;li&gt;Fixed invalid error messages in case of invalid field names.&lt;/li&gt;
&lt;li&gt;Support for getting models using apps.get_model.&lt;/li&gt;
&lt;li&gt;Removed reserved tenant_id limitation by introducing TenantMeta usage.&lt;/li&gt;
&lt;li&gt;Introduced ReadTheDocs &lt;a href="https://django-multitenant.readthedocs.io/en/stable/"&gt;documentation&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Fixed issue with ManyToMany Non tenant model saves.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Quickstart on how to get started with django-multitenant&lt;/h2&gt;

&lt;p&gt;Let&amp;#39;s learn how to use &lt;code&gt;django-multitenant&lt;/code&gt; in a sample application which includes newly added features introduced with version 3.2. This example provides a web interface for a fictitious project management application. (The example uses the Django Rest Framework for developer efficiency, but the underlying Django-multitenant library works with vanilla Django too.)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;To get started, first install the Python packages:&lt;/p&gt;
    &lt;div class="highlight"&gt;
      &lt;pre class="highlight bash"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;django-multitenant
pip &lt;span class="nb"&gt;install &lt;/span&gt;Django
pip &lt;span class="nb"&gt;install &lt;/span&gt;djangorestframework
&lt;/code&gt;&lt;/pre&gt;
      &lt;button class="copy-button" data-clipboard-action="copy" data-clipboard-text="pip install django-multitenant
pip install Django
pip install djangorestframework
"&gt;Copy&lt;/button&gt;
    &lt;/div&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a TenantMeta class in your models and specify the field which identifies tenants.&lt;/p&gt;
    &lt;div class="highlight"&gt;
      &lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Country&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TenantModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;subdomain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TenantMeta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tenant_field_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'id'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Manager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TenantModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"managers"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TenantMeta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tenant_field_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'account_id'&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;constraints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UniqueConstraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'account_id'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'unique_manager_account'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TenantModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"projects"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;managers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManyToManyField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;through&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ProjectManager"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TenantMeta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tenant_field_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'account_id'&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;constraints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UniqueConstraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'account_id'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'unique_project_account'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProjectManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TenantModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TenantForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"projectmanagers"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;manager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TenantForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TenantMeta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tenant_field_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'account_id'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
      &lt;button class="copy-button" data-clipboard-action="copy" data-clipboard-text="class Country(models.Model):
    name = models.CharField(max_length=255)

class Account(TenantModel):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    name = models.CharField(max_length=255)
    domain = models.CharField(max_length=255)
    subdomain = models.CharField(max_length=255)
    country = models.ForeignKey(Country, on_delete=models.CASCADE)

    class TenantMeta:
        tenant_field_name = &amp;#39;id&amp;#39;

class Manager(TenantModel):
    name = models.CharField(max_length=255)
    account = models.ForeignKey(
        Account, on_delete=models.CASCADE, related_name=&amp;quot;managers&amp;quot;
    )
    class TenantMeta:
        tenant_field_name = &amp;#39;account_id&amp;#39;
    class Meta:
        constraints = [
            models.UniqueConstraint(fields=[&amp;#39;id&amp;#39;, &amp;#39;account_id&amp;#39;], name=&amp;#39;unique_manager_account&amp;#39;)
        ]

class Project(TenantModel):
    name = models.CharField(max_length=255)
    account = models.ForeignKey(
        Account, related_name=&amp;quot;projects&amp;quot;, on_delete=models.CASCADE
    )
    managers = models.ManyToManyField(Manager, through=&amp;quot;ProjectManager&amp;quot;)
    class TenantMeta:
        tenant_field_name = &amp;#39;account_id&amp;#39;

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=[&amp;#39;id&amp;#39;, &amp;#39;account_id&amp;#39;], name=&amp;#39;unique_project_account&amp;#39;)
        ]

class ProjectManager(TenantModel):
    project = TenantForeignKey(
        Project, on_delete=models.CASCADE, related_name=&amp;quot;projectmanagers&amp;quot;
    )
    manager = TenantForeignKey(Manager, on_delete=models.CASCADE)
    account = models.ForeignKey(Account, on_delete=models.CASCADE)

    class TenantMeta:
        tenant_field_name = &amp;#39;account_id&amp;#39;
"&gt;Copy&lt;/button&gt;
    &lt;/div&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Distribute tables by their tenant columns. You need to remove constraints before distribution, distribute tables via tenant_migrations.Distribute(), and reinstate the constraints.&lt;/p&gt;
    &lt;div class="highlight"&gt;
      &lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django_multitenant.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tenant_migrations&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_operations&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"ALTER TABLE tests_country DROP CONSTRAINT tests_country_pkey CASCADE;"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"ALTER TABLE tests_manager DROP CONSTRAINT tests_manager_pkey CASCADE;"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"ALTER TABLE tests_project DROP CONSTRAINT tests_project_pkey CASCADE;"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"ALTER TABLE tests_projectmanager DROP CONSTRAINT tests_projectmanager_pkey CASCADE;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;tenant_migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Distribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Country"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;tenant_migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Distribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Account"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;tenant_migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Distribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Manager"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;tenant_migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Distribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Project"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;tenant_migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Distribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ProjectManager"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"ALTER TABLE tests_country ADD CONSTRAINT tests_country_pkey PRIMARY KEY (id);"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"ALTER TABLE tests_project ADD CONSTRAINT tests_project_pkey PRIMARY KEY (account_id, id);"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"ALTER TABLE tests_manager ADD CONSTRAINT tests_manager_pkey PRIMARY KEY (account_id, id);"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"ALTER TABLE tests_projectmanager ADD CONSTRAINT tests_projectmanager_pkey PRIMARY KEY (account_id, id);"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
      &lt;button class="copy-button" data-clipboard-action="copy" data-clipboard-text="from django_multitenant.db import migrations as tenant_migrations

def get_operations():
    operations = []
    operations += [
        migrations.RunSQL(
            &amp;quot;ALTER TABLE tests_country DROP CONSTRAINT tests_country_pkey CASCADE;&amp;quot;
        ),
        migrations.RunSQL(
            &amp;quot;ALTER TABLE tests_manager DROP CONSTRAINT tests_manager_pkey CASCADE;&amp;quot;
        ),
        migrations.RunSQL(
            &amp;quot;ALTER TABLE tests_project DROP CONSTRAINT tests_project_pkey CASCADE;&amp;quot;
        ),
        migrations.RunSQL(
            &amp;quot;ALTER TABLE tests_projectmanager DROP CONSTRAINT tests_projectmanager_pkey CASCADE;&amp;quot;
        )
    ]

    operations += [
        tenant_migrations.Distribute(&amp;quot;Country&amp;quot;, reference=True),
        tenant_migrations.Distribute(&amp;quot;Account&amp;quot;),
        tenant_migrations.Distribute(&amp;quot;Manager&amp;quot;),
        tenant_migrations.Distribute(&amp;quot;Project&amp;quot;),
        tenant_migrations.Distribute(&amp;quot;ProjectManager&amp;quot;),
    ]

    operations += [
        migrations.RunSQL(
            &amp;quot;ALTER TABLE tests_country ADD CONSTRAINT tests_country_pkey PRIMARY KEY (id);&amp;quot;
        ),
        migrations.RunSQL(
            &amp;quot;ALTER TABLE tests_project ADD CONSTRAINT tests_project_pkey PRIMARY KEY (account_id, id);&amp;quot;
        ),
        migrations.RunSQL(
            &amp;quot;ALTER TABLE tests_manager ADD CONSTRAINT tests_manager_pkey PRIMARY KEY (account_id, id);&amp;quot;
        ),
        migrations.RunSQL(
            &amp;quot;ALTER TABLE tests_projectmanager ADD CONSTRAINT tests_projectmanager_pkey PRIMARY KEY (account_id, id);&amp;quot;
        ),
    ]
"&gt;Copy&lt;/button&gt;
    &lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That&amp;#39;s all the setup needed for models and migrations. Once the initial setup is done, you must call &amp;quot;set_current_tenant&amp;quot; somewhere in the code.&lt;/p&gt;

&lt;p&gt;In the case of Django Rest Framework, our library interoperates to ensure effective calling of &amp;quot;set_current_tenant&amp;quot; and prevent any possibility of inter-tenant data leakage.&lt;/p&gt;

&lt;p&gt;To complete our REST application, let&amp;#39;s complete the integration steps:&lt;/p&gt;

&lt;div id="continue-list"&gt;&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Define a &amp;quot;get_tenant&amp;quot; method in a suitable location in the code. In this example, we use the views file&lt;/p&gt;
    &lt;div class="highlight"&gt;
      &lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django_multitenant&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tenant_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tenant_func&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
      &lt;button class="copy-button" data-clipboard-action="copy" data-clipboard-text="from django_multitenant import views
def tenant_func(request):
    return Account.objects.filter(user=request.user).first()

views.get_tenant = tenant_func
"&gt;Copy&lt;/button&gt;
    &lt;/div&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Construct the viewsets for the multi-tenant models using &amp;quot;TenantModelViewSet.&lt;/p&gt;
    &lt;div class="highlight"&gt;
      &lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# views.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django_multitenant.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TenantModelViewSet&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AccountViewSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TenantModelViewSet&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    API endpoint that allows groups to be viewed or edited.
    """&lt;/span&gt;

    &lt;span class="n"&gt;model_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;
    &lt;span class="n"&gt;serializer_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AccountSerializer&lt;/span&gt;
    &lt;span class="n"&gt;permission_classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsAuthenticated&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
      &lt;button class="copy-button" data-clipboard-action="copy" data-clipboard-text="# views.py
from django_multitenant.views import TenantModelViewSet

class AccountViewSet(TenantModelViewSet):
    &amp;quot;&amp;quot;&amp;quot;
    API endpoint that allows groups to be viewed or edited.
    &amp;quot;&amp;quot;&amp;quot;

    model_class = Account
    serializer_class = AccountSerializer
    permission_classes = [permissions.IsAuthenticated]
"&gt;Copy&lt;/button&gt;
    &lt;/div&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add MultitenantMiddleware into the Middleware list in your settings file&lt;/p&gt;
    &lt;div class="highlight"&gt;
      &lt;pre class="highlight bash"&gt;&lt;code&gt;&lt;span class="c"&gt;# settings.py&lt;/span&gt;

MIDDLEWARE &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
    ..
    &lt;span class="s2"&gt;"django_multitenant.middlewares.MultitenantMiddleware"&lt;/span&gt;,
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
      &lt;button class="copy-button" data-clipboard-action="copy" data-clipboard-text="# settings.py

MIDDLEWARE = [
    ..
    &amp;quot;django_multitenant.middlewares.MultitenantMiddleware&amp;quot;,
]
"&gt;Copy&lt;/button&gt;
    &lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thats it. Now you have a running multitenant application. Here&amp;#39;s the link to the fully functional sample application using django-multitenant. You can download the application and test all its features:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/gurkanindibay/django-mt-examples"&gt;https://github.com/gurkanindibay/django-mt-examples&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For comprehensive guidance on how to use &lt;a href="https://github.com/citusdata/django-multitenant"&gt;django-multitenant&lt;/a&gt;, you can refer to its documentation available at the following link: &lt;a href="https://django-multitenant.readthedocs.io/en/stable/"&gt;https://django-multitenant.readthedocs.io/en/stable/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It&amp;#39;s important to note that this library is responsible for handling tenancy functions in the ORM. However, you will need to set the tenant by calling &amp;quot;set_current_tenant&amp;quot; throughout your application controllers. If you fail to call it correctly, you may end up getting all tenant results in queries, and tenants won&amp;#39;t be properly set in your insert/update operations.&lt;/p&gt;

&lt;p&gt;For more information on Multi-tenancy and Django-Multitenant, this &lt;a href="https://youtu.be/RKSwjaZKXL0"&gt;PyCon Canada presentation&lt;/a&gt; about &amp;quot;Scaling multi-tenant apps using the Django ORM and Postgres&amp;quot; gives a good overview.&lt;/p&gt;

&lt;h2&gt;django-multitenant can simplify your multi-tenant SaaS app&lt;/h2&gt;

&lt;p&gt;In conclusion, building multi-tenant applications is a challenging task that requires careful planning and implementation. With the Django-multitenant library, you can simplify your multi-tenant development process by providing a flexible and scalable solution to manage and isolate tenant-specific data.&lt;/p&gt;

&lt;p&gt;Finally, we provided a practical example of how to use Django-multitenant in a real-world scenario, demonstrating its ease of use and flexibility.&lt;/p&gt;

&lt;p&gt;Overall, with its active development and support, django-multitenant is a valuable tool if you are looking to build scalable, secure, and maintainable multi-tenant applications on Postgres (and with Citus).&lt;/p&gt;

&lt;h3&gt;Further reading&lt;/h3&gt;

&lt;p&gt;Multi-tenancy is a very broad concept. If you need more information, please refer to the below resources. If you want to learn more about django-Multitenant , you can refer to the links below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://django-multitenant.readthedocs.io/en/stable/"&gt;Docs for django-multitenant&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/citusdata/django-multitenant"&gt;GitHub repo for django-multitenant&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/citusdata/citus"&gt;GitHub repo for Citus database aka distributed PostgreSQL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;script&gt;document.querySelector("#continue-list + ol").setAttribute("start", "4");&lt;/script&gt;
&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href='https://www.citusdata.com/blog/2023/05/09/evolving-django-multitenant-to-build-scalable-saas-apps-on-postgres-and-citus/'&gt;citusdata.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Tips for installing Citus and Postgres packages</title>
    <link rel="alternate" href="https://www.citusdata.com/blog/2022/01/22/tips-for-installing-citus-and-postgres-packages/"/>
    <id>https://www.citusdata.com/blog/2022/01/22/tips-for-installing-citus-and-postgres-packages/</id>
    <published>2022-01-22T17:19:00+00:00</published>
    <updated>2022-01-22T17:19:00+00:00</updated>
    <author>Gurkan Indibay</author>
    <content type="html">&lt;p&gt;Citus is a great extension for scaling out Postgres databases horizontally. You can use Citus either on the &lt;a href="https://learn.microsoft.com/azure/cosmos-db/postgresql/introduction"&gt;cloud on Azure&lt;/a&gt; or you can download Citus open source and install it wherever. In this blog post, we will focus on Citus open source packaging and installation.&lt;/p&gt;

&lt;p&gt;When you go to the Citus &lt;a href="/download/"&gt;download page&lt;/a&gt; to download the Citus packages&amp;mdash;or you visit the &lt;a href="https://docs.citusdata.com/"&gt;Citus open source docs&lt;/a&gt;&amp;mdash;many of you jump straight to the install instructions and the particular OS you&amp;rsquo;re looking for. That way, you can get straight to sharding Postgres with &lt;a href="https://github.com/citusdata/citus"&gt;Citus&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But what if you want to see which operating systems the Citus packages support? Or what if you want to install Citus with an older version of Postgres?&lt;/p&gt;

&lt;p&gt;This post will answer these types of nitty-gritty questions about Citus packages and their usages. Specifically, this post will cover these questions: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="#section1"&gt;Do you have to install Postgres separately before installing Citus?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#section2"&gt;Where can you download Citus packages, from which package repos?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#section3"&gt;How can you download older versions of Citus (or Citus for older versions of Postgres)?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#section4"&gt;Which OS versions does Citus support?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#section5"&gt;What are the docker images used for?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#section6"&gt;Is there a matrix of all the operating systems supported by Citus?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#section7"&gt;Where can packaging-related requests be reported?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="section1"&gt;Do you have to install Postgres separately before installing Citus?&lt;/h2&gt;

&lt;p&gt;&lt;a href="/getting-started/"&gt;Citus&lt;/a&gt; is a Postgres extension which executes side by side with Postgres. You can get detailed information about Postgres extensions on the &lt;a href="https://wiki.postgresql.org/wiki/Extensions"&gt;PostgreSQL wiki&lt;/a&gt; and this Citus blog post about &lt;a href="/blog/2017/10/25/what-it-means-to-be-a-postgresql-extension/"&gt;what it means to be a Postgres extension&lt;/a&gt;. Additionally, you can get lots more information about Citus in the &lt;a href="https://docs.citusdata.com/"&gt;Citus docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you install Citus, you execute two commands as shown on the Citus &lt;a href="/download/"&gt;Downloads page&lt;/a&gt;. For example, the commands below are the current installation commands for Debian-based systems. At the time of publication of this blog post, these commands will install Postgres 14 and Citus 10.2. We&amp;rsquo;re using the Debian-based install command as an example... Since both &lt;code&gt;deb&lt;/code&gt; and &lt;code&gt;rpm&lt;/code&gt; package installations use two commands to do the install, it is enough to illustrate this point with just the &lt;code&gt;deb&lt;/code&gt; packages example:&lt;/p&gt;
    &lt;div class="highlight"&gt;
      &lt;pre class="highlight bash"&gt;&lt;code&gt;curl https://install.citusdata.com/community/deb.sh | &lt;span class="nb"&gt;sudo &lt;/span&gt;bash
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nb"&gt;install &lt;/span&gt;postgresql-14-citus-10.2
&lt;/code&gt;&lt;/pre&gt;
      &lt;button class="copy-button" data-clipboard-action="copy" data-clipboard-text="curl https://install.citusdata.com/community/deb.sh | sudo bash
sudo apt-get -y install postgresql-14-citus-10.2
"&gt;Copy&lt;/button&gt;
    &lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The first command above&lt;/strong&gt; (that starts with &lt;code&gt;curl&lt;/code&gt;) is the package repository installation script. This script installs &lt;em&gt;both&lt;/em&gt; the&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Postgres repository, and&lt;/li&gt;
&lt;li&gt;Citus PackageCloud package installation repository&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before installing these two repos, the script performs some validations such as operating system and release version checks to be able to validate installation environment.&lt;/p&gt;

&lt;p&gt;The second command:&lt;/p&gt;
    &lt;div class="highlight"&gt;
      &lt;pre class="highlight bash"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nb"&gt;install &lt;/span&gt;postgresql-14-citus-10.2
&lt;/code&gt;&lt;/pre&gt;
      &lt;button class="copy-button" data-clipboard-action="copy" data-clipboard-text="sudo apt-get -y install postgresql-14-citus-10.2
"&gt;Copy&lt;/button&gt;
    &lt;/div&gt;

&lt;p&gt;is a package installation script which installs the versions specified in the command (i.e. Postgres 14 and Citus 10.2 in the example above.)&lt;/p&gt;

&lt;p&gt;So, if you install using commands given in the Citus &lt;a href="/download/"&gt;Download page&lt;/a&gt;, the Postgres server and required packages for Citus will both be installed automatically (even though the Citus and Postgres are separate packages that is installed in your system.)&lt;/p&gt;

&lt;h2 id="section2"&gt;Where can you download Citus packages, from which package repos?&lt;/h2&gt;

&lt;p&gt;Citus open source packages can be downloaded from 4 different package repositories.&lt;/p&gt;

&lt;div class="packages-table"&gt;&lt;/div&gt;

&lt;table&gt;&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Package Repository&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Package Type&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PackageCloud&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deb&lt;br&gt;Rpm&lt;/td&gt;
&lt;td&gt;Main package repository for Citus. All packages released up to now are stored here. You can search for and download any packages using PackageCloud&amp;rsquo;s easy-to-use interface.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PGDG&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rpm&lt;/td&gt;
&lt;td&gt;Package repository for Postgres and its extensions, graciously supported by the Postgres community&amp;rsquo;s packaging team. Keep in mind that sometimes the versions available on PGDG will lag the latest release slightly. Also, PGDG does not support multiple versions. Therefore, you can install only the latest version of Citus on PGDG.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PGXN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Zip-based packages&lt;/td&gt;
&lt;td&gt;Postgres Extension Network is a central repository for all Postgres extensions. It has its own client and if you have a server which has PGXN client installed on it and want to manage all the Postgres extensions in one place, you can use this option.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docker Hub&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Docker images&lt;/td&gt;
&lt;td&gt;Citus Docker images include Postgres and Citus together and are pre-configured which makes them good alternatives for you to try out Citus locally.Note that the docker images for Citus are only for development purposes&amp;mdash;and are not optimized for production usage. Therefore, if you need to use Citus for test and production servers, Linux packages from one of the other repositories are the best option.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;h2 id="section3"&gt;How can you download older versions of Citus (or Citus for older versions of Postgres)?&lt;/h2&gt;

&lt;p&gt;The main package repository for Citus open source is &lt;a href="https://packagecloud.io/citusdata/community"&gt;PackageCloud&lt;/a&gt;. All the Citus versions are stored in there and are ready for deployment.&lt;/p&gt;

&lt;p&gt;If you want to install an older version of Citus (or install Citus with an older version of Postgres), first you need to install the package repository with the command below:&lt;/p&gt;

&lt;table class="script-table"&gt;&lt;tr&gt;&lt;td&gt;Deb&lt;/td&gt;&lt;td&gt;&lt;code&gt;curl https://install.citusdata.com/community/deb.sh | sudo bash&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Rpm&lt;/td&gt;&lt;td&gt;&lt;code&gt;curl https://install.citusdata.com/community/rpm.sh | sudo bash&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;When downloading previous versions, you need to change the version in the second installation command.&lt;/p&gt;

&lt;p&gt;Let&amp;rsquo;s evaluate the second install command for both &lt;code&gt;.deb&lt;/code&gt; and &lt;code&gt;rpm&lt;/code&gt; based package installation scripts:&lt;/p&gt;

&lt;table class="script-table"&gt;&lt;tr&gt;&lt;td&gt;Deb&lt;/td&gt;&lt;td&gt;&lt;code&gt;sudo apt-get -y install postgresql-&lt;strong&gt;14&lt;/strong&gt;-citus-&lt;strong&gt;10.2&lt;/strong&gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Rpm&lt;/td&gt;&lt;td&gt;&lt;code&gt;sudo yum install -y citus&lt;strong&gt;102_14&lt;/strong&gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;The file name pattern for &lt;code&gt;rpm&lt;/code&gt; and &lt;code&gt;deb&lt;/code&gt; packages is different. Here&amp;rsquo;s a comparison for the &lt;code&gt;package_name&lt;/code&gt; structure for each:&lt;/p&gt;

&lt;table class="script-table"&gt;&lt;tr&gt;&lt;td&gt;Deb&lt;/td&gt;&lt;td&gt;&lt;code&gt;sudo apt-get -y install postgresql-&amp;lt;postgres_version&amp;gt;-citus-&amp;lt;citus_major_version&amp;gt;.&amp;lt;citus_minor_version&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Rpm&lt;/td&gt;&lt;td&gt;&lt;code&gt;sudo yum install -y citus &amp;lt;citus_major_version&amp;gt;&amp;lt;citus_minor_version&amp;gt;_&amp;lt;postgres_version&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;If you want to use an older version of Postgres (or of Citus), you can do a parameter replace and switch out the Citus major minor versions and/or the Postgres versions in the package installation commands that you see on the &lt;a href="/download/"&gt;Citus download&lt;/a&gt; page. Assuming you get the syntax correct, and if Citus has a package for that specific version, then your customized package download will start and will use the different/older versions that you want.&lt;/p&gt;

&lt;p&gt;It is easy to download with parameter replacement if you are sure about what version you want.&lt;/p&gt;

&lt;h3&gt;Browsing and searching for release versions on PackageCloud&lt;/h3&gt;

&lt;p&gt;If you are not sure about which version of Postgres (and/or Citus) you&amp;rsquo;d like to install, you may want to search for the desired package versions instead. In that case you can use PackageCloud&amp;rsquo;s interface to browse and search for the version you want&amp;mdash;and then download the packages.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the Citus Community on &lt;a href="https://packagecloud.io/citusdata/community"&gt;PackageCloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Enter the version you want to download in the search box&lt;/li&gt;
&lt;/ol&gt;

&lt;figure&gt;
&lt;picture&gt;
&lt;source srcset="https://cdn.citusdata.com/images/blog/Gurkan-Blog-Figure1.webp" type="image/webp"&gt;
&lt;img src="https://cdn.citusdata.com/images/blog/Gurkan-Blog-Figure1.jpg" alt="Figure 1" width="850" height="" loading="lazy" /&gt;
&lt;/picture&gt;
&lt;figcaption&gt;&lt;strong&gt;Figure 1:&lt;/strong&gt; Screenshot of the main search page on PackageCloud for the Citus open source packages . On this page, you can search for the Citus version you want to find all the packages related to this version.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;ol start="3"&gt;&lt;li&gt;Click on the package name you want to install.&lt;/li&gt;&lt;/ol&gt;

&lt;figure&gt;
&lt;picture&gt;
&lt;source srcset="https://cdn.citusdata.com/images/blog/Gurkan-Blog-Figure2.webp" type="image/webp"&gt;
&lt;img src="https://cdn.citusdata.com/images/blog/Gurkan-Blog-Figure2.jpg" alt="Figure 2" width="850" height="" loading="lazy" /&gt;
&lt;/picture&gt;
&lt;figcaption&gt;&lt;strong&gt;Figure 2:&lt;/strong&gt; Screenshot of the PackageCloud search page after searching for Citus version 10.0.4. Package count and all related packages related to the version is listed on the page. You can click and access the package detail page by clicking the package link you want.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;ol start="4"&gt;&lt;li&gt;Copy and execute the command in the install section&lt;/li&gt;&lt;/ol&gt;

&lt;figure&gt;
&lt;picture&gt;
&lt;source srcset="https://cdn.citusdata.com/images/blog/Gurkan-Blog-Figure3.webp" type="image/webp"&gt;
&lt;img src="https://cdn.citusdata.com/images/blog/Gurkan-Blog-Figure3.jpg" alt="Figure 3" width="850" height="" loading="lazy" /&gt;
&lt;/picture&gt;
&lt;figcaption&gt;&lt;strong&gt;Figure 3:&lt;/strong&gt; Screenshot of the detail page on PackageCloud for the package selected from the package search page. You can see the full description, checksums, upload date, and installation instructions on this page.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="section4"&gt;Which OS versions does Citus support?&lt;/h2&gt;

&lt;p&gt;Our Citus open source packages mainly support these stable releases of operating systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debian&lt;/li&gt;
&lt;li&gt;Ubuntu&lt;/li&gt;
&lt;li&gt;Red Hat&lt;/li&gt;
&lt;li&gt;CentOS&lt;/li&gt;
&lt;li&gt;Fedora&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a new stable release is added for one of the operating systems above and includes Postgres support, we add support for this release. On occasion, some of you have asked us to add support for non-stable releases. However, we don&amp;rsquo;t support non-stable releases until they become stable.&lt;/p&gt;

&lt;p&gt;When do we decide to stop support of new Citus releases for an operating system? We take 2 criteria into account:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add/drop support decision of Operating System maintainers for OS releases.&lt;/li&gt;
&lt;li&gt;Add/drop support decision of Postgres Maintainers for OS releases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me clarify the cases with some real time examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Debian 8 Jessie&lt;/strong&gt;: Debian dropped support for Debian 8 (code name jessie) as June 2020. Citus dropped support for this release after 9.5 release but kept the packages released before for Debian 8.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ubuntu Xenial&lt;/strong&gt;: Postgres team dropped support for Ubuntu Xenial as of September 2021 so that we dropped support for Ubuntu/Xenial after 10.1.1 release as of September 2021 as well and kept the packages released before for Ubuntu/Xenial&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="section5"&gt;What are the docker images used for?&lt;/h2&gt;

&lt;p&gt;The docker images for Citus include both Postgres and Citus together and are pre-configured, which makes them good alternatives for you to experiment with Citus locally.&lt;/p&gt;

&lt;p&gt;As you might expect, the Citus docker images are only meant for development purposes (and to give you a quick way to try out Citus and get started)&amp;mdash;and they are not optimized for production usage.&lt;/p&gt;

&lt;p&gt;Therefore, if you need to use Citus just for development, docker images are the best option since installation effort is near zero. If you need to use Citus for test and production servers, Linux packages are the best option.&lt;/p&gt;

&lt;h2 id="section6"&gt;Is there a matrix of all the operating systems supported by Citus?&lt;/h2&gt;

&lt;p&gt;The support matrix for the currently supported OS releases (at the time of publication of this blog post) that maps the OS release versions to the Citus versions is as follows:&lt;/p&gt;

&lt;div class="releases-table"&gt;&lt;/div&gt;

&lt;table&gt;&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OS/Release&lt;/th&gt;
&lt;th&gt;Still Supported&lt;/th&gt;
&lt;th&gt;Starting with OS Release #&lt;/th&gt;
&lt;th&gt;Ending with OS Release # **&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enterprise Linux/CentOS 8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;9.0.0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enterprise Linux/ CentOS 7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;5.0.0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enterprise Linux/ CentOS 6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;5.0.0&lt;/td&gt;
&lt;td&gt;9.4.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Oracle Linux 8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;10.0.6&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Oracle Linux 7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;5.0.0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Oracle Linux 6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;5.0.0&lt;/td&gt;
&lt;td&gt;9.4.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Debian Bullseye&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;10.0.6&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Debian Buster&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;9.0.0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Debian Stretch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;7.0.2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Debian Jessie&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;5.0.0&lt;/td&gt;
&lt;td&gt;8.3.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Debian Wheezy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;5.0.0&lt;/td&gt;
&lt;td&gt;8.0.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ubuntu Bionic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;7.2.2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ubuntu Focal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;9.3.3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ubuntu Xenial&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;7.0.2&lt;/td&gt;
&lt;td&gt;10.0.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ubuntu Precise&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;5.0.0&lt;/td&gt;
&lt;td&gt;6.2.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ubuntu Trusty&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;5.0.0&lt;/td&gt;
&lt;td&gt;8.3.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ubuntu Wily&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;5.0.0&lt;/td&gt;
&lt;td&gt;5.2.1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;&lt;small style="display:block"&gt;&lt;strong&gt;** As of date of blog post publication.&lt;/strong&gt; Blank field = still supported for new releases when this blog post was published.&lt;/small&gt;&lt;/p&gt;

&lt;h2 id="section7"&gt;Where can packaging-related requests be reported?&lt;/h2&gt;

&lt;p&gt;If you have any requests or questions about Citus open source packaging, you can report it to us by opening an issue in our &lt;a href="https://github.com/citusdata/packaging/issues"&gt;Citus packaging repo&lt;/a&gt; on GitHub.&lt;/p&gt;

&lt;p&gt;Oh, and if you&amp;rsquo;re looking for more information about the Citus extension, here are 4 good places to get started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="/getting-started/"&gt;Getting started page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/citusdata/citus#readme"&gt;Citus README on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/download/"&gt;Download page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.citusdata.com/"&gt;Citus docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;style&gt;.script-table td{border-top:1px solid #ddd;}.script-table td:first-child{width:4em}.releases-table + table th:not(:first-child),.releases-table + table td:not(:first-child){text-align:center; width:22%}figure img{border:1px solid #e1e1e1}.packages-table + table th:last-child, .packages-table + table td:last-child{width:60%}&lt;/style&gt;
&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href='https://www.citusdata.com/blog/2022/01/22/tips-for-installing-citus-and-postgres-packages/'&gt;citusdata.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</content>
  </entry>
</feed>
