<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>SaaS · Jared Lynskey</title>
    <link>https://jared.lynskey.co.nz/en/tags/saas/</link>
    <description>Short stories of value on software engineering and engineering leadership — performance reviews, hiring and scaling teams, CI/CD, DevOps monitoring and agentic AI. Written from Seoul.</description>
    <generator>Hugo</generator>
    <language>en</language>
    <copyright>© 2026 Jared Lynskey</copyright>
    <lastBuildDate>Mon, 26 Oct 2026 00:00:00 +1200</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/en/tags/saas/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Single-Tenant vs Multi-Tenant SaaS: When to Use Which</title>
      <link>https://jared.lynskey.co.nz/en/posts/2024/2024-04-07-singletenantsingleinstance/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/en/posts/2024/2024-04-07-singletenantsingleinstance/</guid>
      <pubDate>Sat, 06 Apr 2024 00:30:03 +0000</pubDate>
      <dc:creator>Me</dc:creator>
      <category>SaaS</category>
      <category>architecture</category>
      <category>single-tenant</category>
      <category>multi-tenant</category>
      <description>Single-tenant (multi-instance) vs multi-tenant SaaS: what each one means, how they compare on isolation, cost and operations, the options in between, when to use which, how to keep tenants apart in a shared database, and the tooling that makes an instance per customer survivable.</description>
      <content:encoded><![CDATA[<p>Single-tenant SaaS gives every customer their own copy of the application and its database. Multi-tenant SaaS runs every customer on one shared application and database, kept apart by a tenant ID that has to be checked on every query. Single-tenant buys isolation, per-customer control and easier compliance, and costs you a fleet of copies to run. Multi-tenant is cheaper and much simpler to operate, but keeping customers apart becomes your code&rsquo;s job. Most products start multi-tenant and offer a dedicated instance to the few customers who need one.</p>
<p>Every B2B SaaS runs into the tenancy question eventually, usually the day a big prospective customer asks whether their data can live somewhere separate from everyone else&rsquo;s. I&rsquo;ve been near this conversation at more than one company now, and the answer always comes down to two options: give each customer their own instance (single-tenant, multi-instance), or run everyone on one shared instance (multi-tenant, single-instance). Neither is &ldquo;correct&rdquo;. They just fail in different ways.</p>

<h2 class="relative group">Infrastructure Design
    <div id="infrastructure-design" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#infrastructure-design" aria-label="Anchor">#</a>
    </span>
    
</h2>
<figure class="diagram">
  <div role="img" aria-label="The difference between Single-Tenant and Multi-Tenant"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 790" aria-hidden="true" focusable="false">
<defs><marker id="tn-ah" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path class="d-head" d="M0,0 L10,5 L0,10 z"/></marker></defs>
<text class="d-title" x="320" y="164" text-anchor="middle" font-size="42">Multi-instance</text>
<text class="d-sub" x="320" y="204" text-anchor="middle" font-size="24">single-tenant: a copy per customer</text>
<rect class="d-panel" x="12" y="234" width="616" height="304" rx="36"/>
<rect class="d-group" x="25" y="250" width="186" height="272" rx="22"/>
<rect class="d-box" x="33" y="280" width="170" height="86" rx="14"/>
<text class="d-label" x="118.0" y="337.64" text-anchor="middle" font-size="24">Database</text>
<polygon class="d-badge" points="118.0,256.0 143.7,274.7 133.9,304.8 102.1,304.8 92.3,274.7"/>
<g class="d-icon">
<ellipse cx="118" cy="273" rx="10" ry="4"/>
<path d="M108,273 v15 a10,4 0 0 0 20,0 v-15 M108,280 a10,4 0 0 0 20,0"/>
</g>
<rect class="d-box" x="33" y="418" width="170" height="86" rx="14"/>
<text class="d-label" x="118.0" y="475.28" text-anchor="middle" font-size="23">Application</text>
<polygon class="d-badge" points="118.0,394.0 143.7,412.7 133.9,442.8 102.1,442.8 92.3,412.7"/>
<g class="d-icon">
<rect x="110" y="411" width="16" height="16" rx="2"/>
<rect class="d-icon-fill" x="114.5" y="415.5" width="7" height="7"/>
<path stroke-width="1.4" d="M114,411 v-4 M114,427 v4 M110,415 h-4 M126,415 h4 M118,411 v-4 M118,427 v4 M110,419 h-4 M126,419 h4 M122,411 v-4 M122,427 v4 M110,423 h-4 M126,423 h4"/>
</g>
<line class="d-line" x1="118" y1="508" x2="118" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="84" cy="710.88" r="14.44"/><path d="M58.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="152" cy="710.88" r="14.44"/><path d="M126.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="118" cy="704.0" r="19.0"/><path d="M84.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
<rect class="d-group" x="227" y="250" width="186" height="272" rx="22"/>
<rect class="d-box" x="235" y="280" width="170" height="86" rx="14"/>
<text class="d-label" x="320.0" y="337.64" text-anchor="middle" font-size="24">Database</text>
<polygon class="d-badge" points="320.0,256.0 345.7,274.7 335.9,304.8 304.1,304.8 294.3,274.7"/>
<g class="d-icon">
<ellipse cx="320" cy="273" rx="10" ry="4"/>
<path d="M310,273 v15 a10,4 0 0 0 20,0 v-15 M310,280 a10,4 0 0 0 20,0"/>
</g>
<rect class="d-box" x="235" y="418" width="170" height="86" rx="14"/>
<text class="d-label" x="320.0" y="475.28" text-anchor="middle" font-size="23">Application</text>
<polygon class="d-badge" points="320.0,394.0 345.7,412.7 335.9,442.8 304.1,442.8 294.3,412.7"/>
<g class="d-icon">
<rect x="312" y="411" width="16" height="16" rx="2"/>
<rect class="d-icon-fill" x="316.5" y="415.5" width="7" height="7"/>
<path stroke-width="1.4" d="M316,411 v-4 M316,427 v4 M312,415 h-4 M328,415 h4 M320,411 v-4 M320,427 v4 M312,419 h-4 M328,419 h4 M324,411 v-4 M324,427 v4 M312,423 h-4 M328,423 h4"/>
</g>
<line class="d-line" x1="320" y1="508" x2="320" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="286" cy="710.88" r="14.44"/><path d="M260.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="354" cy="710.88" r="14.44"/><path d="M328.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="320" cy="704.0" r="19.0"/><path d="M286.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
<rect class="d-group" x="429" y="250" width="186" height="272" rx="22"/>
<rect class="d-box" x="437" y="280" width="170" height="86" rx="14"/>
<text class="d-label" x="522.0" y="337.64" text-anchor="middle" font-size="24">Database</text>
<polygon class="d-badge" points="522.0,256.0 547.7,274.7 537.9,304.8 506.1,304.8 496.3,274.7"/>
<g class="d-icon">
<ellipse cx="522" cy="273" rx="10" ry="4"/>
<path d="M512,273 v15 a10,4 0 0 0 20,0 v-15 M512,280 a10,4 0 0 0 20,0"/>
</g>
<rect class="d-box" x="437" y="418" width="170" height="86" rx="14"/>
<text class="d-label" x="522.0" y="475.28" text-anchor="middle" font-size="23">Application</text>
<polygon class="d-badge" points="522.0,394.0 547.7,412.7 537.9,442.8 506.1,442.8 496.3,412.7"/>
<g class="d-icon">
<rect x="514" y="411" width="16" height="16" rx="2"/>
<rect class="d-icon-fill" x="518.5" y="415.5" width="7" height="7"/>
<path stroke-width="1.4" d="M518,411 v-4 M518,427 v4 M514,415 h-4 M530,415 h4 M522,411 v-4 M522,427 v4 M514,419 h-4 M530,419 h4 M526,411 v-4 M526,427 v4 M514,423 h-4 M530,423 h4"/>
</g>
<line class="d-line" x1="522" y1="508" x2="522" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="488" cy="710.88" r="14.44"/><path d="M462.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="556" cy="710.88" r="14.44"/><path d="M530.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="522" cy="704.0" r="19.0"/><path d="M488.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
<text class="d-title" x="982" y="50" text-anchor="middle" font-size="42">Multi-tenant</text>
<text class="d-sub" x="982" y="88" text-anchor="middle" font-size="24">one shared instance for everyone</text>
<rect class="d-panel" x="698" y="104" width="568" height="434" rx="44"/>
<rect class="d-box" x="887" y="126" width="190" height="86" rx="14"/>
<text class="d-label" x="982.0" y="183.64" text-anchor="middle" font-size="24">Database</text>
<polygon class="d-badge" points="982.0,102.0 1007.7,120.7 997.9,150.8 966.1,150.8 956.3,120.7"/>
<g class="d-icon">
<ellipse cx="982" cy="119" rx="10" ry="4"/>
<path d="M972,119 v15 a10,4 0 0 0 20,0 v-15 M972,126 a10,4 0 0 0 20,0"/>
</g>
<line class="d-line" x1="982" y1="216" x2="982" y2="246" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<rect class="d-group" x="704" y="240" width="556" height="156" rx="30"/>
<rect class="d-box" x="714" y="282" width="536" height="90" rx="16"/>
<polygon class="d-badge" points="982.0,248.0 1007.7,266.7 997.9,296.8 966.1,296.8 956.3,266.7"/>
<g class="d-icon">
<rect x="974" y="265" width="16" height="16" rx="2"/>
<rect class="d-icon-fill" x="978.5" y="269.5" width="7" height="7"/>
<path stroke-width="1.4" d="M978,265 v-4 M978,281 v4 M974,269 h-4 M990,269 h4 M982,265 v-4 M982,281 v4 M974,273 h-4 M990,273 h4 M986,265 v-4 M986,281 v4 M974,277 h-4 M990,277 h4"/>
</g>
<rect class="d-box" x="722" y="306" width="168" height="56" rx="12"/>
<text class="d-label" x="806.0" y="347.92" text-anchor="middle" font-size="22">Application</text>
<rect class="d-box" x="898" y="306" width="168" height="56" rx="12"/>
<text class="d-label" x="982.0" y="347.92" text-anchor="middle" font-size="22">Application</text>
<rect class="d-box" x="1074" y="306" width="168" height="56" rx="12"/>
<text class="d-label" x="1158.0" y="347.92" text-anchor="middle" font-size="22">Application</text>
<rect class="d-box" x="887" y="440" width="190" height="86" rx="14"/>
<text class="d-label" x="982.0" y="497.28" text-anchor="middle" font-size="23">Load Balancer</text>
<polygon class="d-badge" points="982.0,404.0 1007.7,422.7 997.9,452.8 966.1,452.8 956.3,422.7"/>
<g class="d-icon">
<path d="M982,416 l6,9 h-12 z M982,425 v5 M972,433 h20 M972,433 v3 M982,433 v3 M992,433 v3"/>
<rect class="d-icon-fill" x="969" y="436" width="6" height="5"/>
<rect class="d-icon-fill" x="979" y="436" width="6" height="5"/>
<rect class="d-icon-fill" x="989" y="436" width="6" height="5"/>
</g>
<line class="d-line" x1="806" y1="364" x2="922" y2="438" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<line class="d-line" x1="982" y1="364" x2="982" y2="398" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<line class="d-line" x1="1158" y1="364" x2="1042" y2="438" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<line class="d-line" x1="932" y1="528" x2="780" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="716" cy="710.88" r="14.44"/><path d="M690.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="784" cy="710.88" r="14.44"/><path d="M758.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="750" cy="704.0" r="19.0"/><path d="M716.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
<line class="d-line" x1="982" y1="528" x2="982" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="948" cy="710.88" r="14.44"/><path d="M922.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="1016" cy="710.88" r="14.44"/><path d="M990.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="982" cy="704.0" r="19.0"/><path d="M948.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
<line class="d-line" x1="1032" y1="528" x2="1184" y2="652" marker-start="url(#tn-ah)" marker-end="url(#tn-ah)"/>
<g class="d-person-back"><circle cx="1180" cy="710.88" r="14.44"/><path d="M1154.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person-back"><circle cx="1248" cy="710.88" r="14.44"/><path d="M1222.16,758 v-9.120000000000001 a25.84,21.28 0 0 1 51.68,0 v9.120000000000001 z"/></g>
<g class="d-person"><circle cx="1214" cy="704.0" r="19.0"/><path d="M1180.0,766 v-12.0 a34.0,28.0 0 0 1 68.0,0 v12.0 z"/></g>
</svg>
</div>
</figure>


<h3 class="relative group">Single tenant, multi-instance
    <div id="single-tenant-multi-instance" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#single-tenant-multi-instance" aria-label="Anchor">#</a>
    </span>
    
</h3>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><strong>Single Tenant, Multi-Instance</strong>: In this setup, multiple instances of the infrastructure serve multiple tenants.</span>
</div>

<p>The appeal here is isolation, and nearly every benefit flows from it. Each customer&rsquo;s data sits in its own environment, so security reviews and compliance conversations get a lot easier. A bad deploy or a heavy workload takes down one customer instead of all of them. You can scale, tune, and customise each instance independently, and dedicated resources mean nobody&rsquo;s performance depends on what a neighbouring tenant is doing. Maintenance can be scheduled per customer, which keeps downtime small and targeted, and if you price things sensibly you can match infrastructure cost to what each tenant actually uses.</p>
<p>The price you pay is that you&rsquo;re now running N copies of everything. Resource overhead adds up, configuration drifts between instances, and keeping versions and data in sync across the fleet becomes its own project. Scaling, isolation, and cost management still need real planning. The problems don&rsquo;t disappear; they multiply.</p>

<h3 class="relative group">Multi tenant, single instance
    <div id="multi-tenant-single-instance" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#multi-tenant-single-instance" aria-label="Anchor">#</a>
    </span>
    
</h3>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><strong>Multi Tenant, Single Instance</strong>: A single software application serves multiple tenants.</span>
</div>

<p>This is the default for a reason: one deployment, one database to back up, one version in production. It&rsquo;s efficient with resources, simple to operate, and every customer gets updates and security patches at the same moment.</p>
<p>The hard parts are the flip side of sharing. One tenant&rsquo;s heavy usage can drag down everyone&rsquo;s performance, per-customer customisation is limited, and data isolation now lives in your application code rather than your infrastructure, so a single bug can turn into a cross-tenant data leak. Scalability bottlenecks hit all customers at once, and maintenance needs more care because there&rsquo;s no &ldquo;just this customer&rdquo; blast radius anymore.</p>
<p><a href="https://www.scaleway.com/en/blog/saas-multi-tenant-vs-multi-instance-architectures/"  target="_blank" rel="noreferrer">Learn more about SaaS Solutions - Multi-instance vs. Multi-tenant Architectures</a></p>

<h2 class="relative group">Multi-tenant vs multi-instance
    <div id="multi-tenant-vs-multi-instance" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#multi-tenant-vs-multi-instance" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>You&rsquo;ll often see this described as multi-tenant vs multi-instance instead, and it&rsquo;s the same choice seen from the other side. &ldquo;Tenant&rdquo; is about customers: how many share one copy of the software. &ldquo;Instance&rdquo; is about deployments: how many copies you run. A single-tenant product is a multi-instance deployment, one copy per customer, and a multi-tenant product is usually a single instance that everyone shares. So when someone asks for multi-instance, they&rsquo;re asking for single-tenancy.</p>
<p>In practice it&rsquo;s a range rather than two boxes, and most of the real decisions are about the database. At the most shared end, every customer&rsquo;s rows sit in the same tables with a <code>tenant_id</code> column, and every query filters on it. That&rsquo;s the cheapest to run and the easiest to get wrong. One step along, each customer gets their own schema inside a shared database, which gives you cleaner separation and per-customer backups, at the cost of running every migration once per schema. Further along, the application stays shared but each customer gets their own database: a popular middle ground, with strong data isolation and no need to run a whole stack per customer. At the far end is full single-tenancy, with the application and the database both dedicated. AWS&rsquo;s SaaS guidance calls the two ends silo and pool, and mixes of the two bridge, which is handy vocabulary when you&rsquo;re explaining your setup to a customer&rsquo;s security team.</p>

<h2 class="relative group">Single-tenant vs multi-tenant at a glance
    <div id="single-tenant-vs-multi-tenant-at-a-glance" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#single-tenant-vs-multi-tenant-at-a-glance" aria-label="Anchor">#</a>
    </span>
    
</h2>
<table>
	<thead>
			<tr>
					<th></th>
					<th>Single-tenant (multi-instance)</th>
					<th>Multi-tenant (single instance)</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Data isolation</td>
					<td>Separate infrastructure per customer</td>
					<td>Enforced by your code, or by database policies</td>
			</tr>
			<tr>
					<td>Cost per customer</td>
					<td>High: each customer carries a whole stack</td>
					<td>Low: everyone shares one</td>
			</tr>
			<tr>
					<td>Operations</td>
					<td>A deployment, an upgrade and a dashboard per customer</td>
					<td>One of each</td>
			</tr>
			<tr>
					<td>Noisy neighbours</td>
					<td>None</td>
					<td>One heavy tenant can slow everyone down</td>
			</tr>
			<tr>
					<td>Customisation</td>
					<td>Per customer, even different versions</td>
					<td>Mostly configuration and feature flags</td>
			</tr>
			<tr>
					<td>Releases</td>
					<td>Rolled out customer by customer</td>
					<td>Everyone at once</td>
			</tr>
			<tr>
					<td>A bad deploy hits</td>
					<td>One customer</td>
					<td>Every customer</td>
			</tr>
			<tr>
					<td>Security reviews and data residency</td>
					<td>Easy to answer</td>
					<td>Takes more explaining</td>
			</tr>
	</tbody>
</table>

<h2 class="relative group">When to use which
    <div id="when-to-use-which" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#when-to-use-which" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Go multi-tenant unless something specific pushes you off it. It&rsquo;s the right default when you have lots of customers paying modest amounts, when people sign themselves up, and when your team is small enough that thirty environments would eat it alive.</p>
<p>Single-tenant earns its overhead in a few situations. Customers in health, finance and government often need their data physically separate, or kept in a particular country, and a dedicated instance is the simplest honest answer to that in a security review. A handful of large customers on enterprise contracts can carry the cost of their own stack, and often expect one. And when customers need their own release schedule, a pinned version or deep customisation, separate instances stop being overhead and become part of what you&rsquo;re selling.</p>
<p>Where most teams end up is both: a multi-tenant product for nearly everyone, and a dedicated instance as an enterprise tier for the few customers who need it and will pay for it. If the multi-tenant version carries a tenant ID everywhere from day one, moving one customer out to their own database later is a migration rather than a rewrite.</p>

<h2 class="relative group">Making multi-tenant isolation harder to break
    <div id="making-multi-tenant-isolation-harder-to-break" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#making-multi-tenant-isolation-harder-to-break" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The multi-tenant bug that keeps people up at night is the query that forgets its tenant filter. It doesn&rsquo;t fail. It quietly returns somebody else&rsquo;s data, and code review won&rsquo;t catch every one. If you&rsquo;re on Postgres, row-level security moves the check into the database, so a query that forgets the filter still only sees the current customer&rsquo;s rows:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">ALTER</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">invoices</span><span class="w"> </span><span class="n">ENABLE</span><span class="w"> </span><span class="k">ROW</span><span class="w"> </span><span class="k">LEVEL</span><span class="w"> </span><span class="k">SECURITY</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="n">POLICY</span><span class="w"> </span><span class="n">tenant_isolation</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">invoices</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">USING</span><span class="w"> </span><span class="p">(</span><span class="n">tenant_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">current_setting</span><span class="p">(</span><span class="s1">&#39;app.tenant_id&#39;</span><span class="p">)::</span><span class="n">uuid</span><span class="p">);</span></span></span></code></pre></div></div>
<p>Each request then sets its tenant inside a transaction with <code>SET LOCAL app.tenant_id = '…'</code>, so a pooled connection doesn&rsquo;t carry one customer&rsquo;s ID into the next request, and a request that never sets it gets an error rather than rows. Two catches: connect the app as a role that doesn&rsquo;t own the table, because owners skip these policies unless you also add <code>FORCE ROW LEVEL SECURITY</code>, and never as a superuser, which skips them regardless.</p>

<h2 class="relative group">Where to start for single-tenant?
    <div id="where-to-start-for-single-tenant" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#where-to-start-for-single-tenant" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>If you do go single-tenant, tooling decides whether it&rsquo;s manageable or a slow-motion disaster. You can&rsquo;t hand-manage thirty environments.</p>
<p>The core is containers plus infrastructure as code. Docker packages the app identically for every tenant, Kubernetes handles deploying and scaling all those instances, and Terraform, Pulumi, or AWS CloudFormation let you define a tenant&rsquo;s entire stack in code, so spinning up customer #31 is a pull request rather than a lost weekend. If you&rsquo;re living closer to the VM world, configuration management tools like Ansible, Chef, or Puppet fill the same role. A solid CI/CD pipeline (Jenkins, GitLab CI/CD) matters more than usual here, because every release has to roll out across every instance without babysitting.</p>
<p>Beyond that, it depends on your product&rsquo;s shape. Serverless (AWS Lambda, Azure Functions) can remove the server layer entirely for some workloads. Managed databases like Amazon RDS or Azure SQL Database save you from administering one database per tenant by hand. Monitoring with Prometheus, Grafana, or New Relic stops being optional once you have a fleet, because you want to know which instance is unhappy before the customer tells you. For identity, a service like Auth0 or Okta handles user management and access control without you rebuilding it per tenant. Whatever language you work in, there&rsquo;s probably a SaaS-oriented framework or library that saves you some of this plumbing, and any of the big clouds (AWS, Azure, Google Cloud Platform) will host all of it happily.</p>
<p>Don&rsquo;t buy the whole list on day one. Start with containers and IaC, add monitoring and IAM as tenants accumulate, and let actual pain drive the rest.</p>

<h2 class="relative group">The short version
    <div id="the-short-version" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-short-version" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Single-tenant, or multi-instance, means a separate copy of the application and database for each customer: strong isolation and per-customer control, and a fleet to run. Multi-tenant means one shared copy: cheap and simple, with isolation enforced in code. Most products should start multi-tenant, with a tenant ID on everything and the database enforcing it, and offer dedicated instances to the few customers who need them and will pay for them. If you do run single-tenant, containers and infrastructure as code are what keep it manageable.</p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2024/2024-04-07-singletenantsingleinstance/featured.jpg" medium="image" type="image/jpeg" />
    </item>
  </channel>
</rss>
