<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Martin Janse van Rensburg</title>
    <description>The latest articles on DEV Community by Martin Janse van Rensburg (@vanrensbird).</description>
    <link>https://dev.to/vanrensbird</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3979779%2F753d403e-a5f0-4e23-9f2e-c7974a029526.jpg</url>
      <title>DEV Community: Martin Janse van Rensburg</title>
      <link>https://dev.to/vanrensbird</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vanrensbird"/>
    <language>en</language>
    <item>
      <title>HIPAA compliant databases will bankrupt you as fast as US healthcare</title>
      <dc:creator>Martin Janse van Rensburg</dc:creator>
      <pubDate>Fri, 30 Oct 2026 22:22:14 +0000</pubDate>
      <link>https://dev.to/vanrensbird/hipaa-compliant-databases-will-bankrupt-you-as-fast-as-us-healthcare-2e5h</link>
      <guid>https://dev.to/vanrensbird/hipaa-compliant-databases-will-bankrupt-you-as-fast-as-us-healthcare-2e5h</guid>
      <description>&lt;p&gt;A question keeps coming up in dev forums: "We're a three-person health startup. Do we really need an enterprise contract to get a HIPAA compliant database?" The honest answer in 2026 is no. But the pricing pages don't make that easy to see. Between "contact sales" buttons, add-ons that aren't priced in public, and surcharges that only show up in the docs, figuring out what fully managed HIPAA compliant database hosting really costs takes an afternoon of digging. I did the digging for managed databases, the kind where someone else runs the servers, backups and patching for you. Here are the numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I work at Prisma, which is one of the providers below. I've kept this to public prices and linked every number to the provider's own page so you can check my math.&lt;/p&gt;

&lt;h3&gt;
  
  
  The contrarian take: HIPAA isn't an enterprise feature
&lt;/h3&gt;

&lt;p&gt;HIPAA doesn't certify databases. There's no "HIPAA edition" that magically makes data safe. What HIPAA needs from a database vendor is mostly paperwork plus solid basics: a signed Business Associate Agreement (BAA) and real safeguards around the data.&lt;/p&gt;

&lt;p&gt;So why do so many vendors put it behind a sales call? Some genuinely run a separate, locked-down environment for regulated customers. Others just use HIPAA as a reason to start an enterprise deal. Either way, the result for a small team is the same: you can't get a price without a meeting.&lt;/p&gt;

&lt;p&gt;I think that's the wrong default. A startup should be able to read a pricing page, pick a plan and know what HIPAA will cost per year.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a fully managed HIPAA compliant cloud database costs per year in 2026
&lt;/h3&gt;

&lt;p&gt;Two rules for this list: the database is fully managed, and the BAA comes with the plan. Providers that review each BAA request and approve it case by case are left out, because you can't budget for something you might not get. Sorted cheapest first. Annual cost is monthly times 12. "Estimate" means the bill depends on usage, and I priced the smallest always-on setup.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Cheapest HIPAA option&lt;/th&gt;
&lt;th&gt;Per month&lt;/th&gt;
&lt;th&gt;Per year&lt;/th&gt;
&lt;th&gt;What's included&lt;/th&gt;
&lt;th&gt;The fine print&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://neon.com/pricing" rel="noopener noreferrer"&gt;Neon&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Scale plan&lt;/td&gt;
&lt;td&gt;~$44&lt;/td&gt;
&lt;td&gt;~$528&lt;/td&gt;
&lt;td&gt;About 0.25 CU (1 CU is about 4 GB RAM) billed hourly, 10 GB at $0.35/GB, instant restore up to 30 days (billed), HA not stated, SOC 2 report and ISO 27001&lt;/td&gt;
&lt;td&gt;Estimate. &lt;a href="https://neon.com/docs/security/hipaa" rel="noopener noreferrer"&gt;HIPAA docs&lt;/a&gt; say a 15% surcharge is coming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.mongodb.com/pricing" rel="noopener noreferrer"&gt;MongoDB Atlas&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;M10 cluster&lt;/td&gt;
&lt;td&gt;~$58&lt;/td&gt;
&lt;td&gt;~$701&lt;/td&gt;
&lt;td&gt;2 shared vCPUs, 2 GB RAM, 10 GB storage, replica set, backups billed separately, SOC 2 and ISO not stated on pricing page&lt;/td&gt;
&lt;td&gt;MongoDB &lt;a href="https://www.mongodb.com/products/platform/trust/hipaa" rel="noopener noreferrer"&gt;suggests M30&lt;/a&gt; (~$394/mo) for sensitive data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.prisma.io/pricing" rel="noopener noreferrer"&gt;Prisma Postgres&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Business plan&lt;/td&gt;
&lt;td&gt;$129&lt;/td&gt;
&lt;td&gt;$1,548&lt;/td&gt;
&lt;td&gt;50M operations, 100 GB storage, daily backups kept 30 days, Premium support, SOC 2 Type II, ISO 27001, BAA included&lt;/td&gt;
&lt;td&gt;Flat price with usage included. No HIPAA fee. HIPAA isn't on lower plans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.heroku.com/pricing" rel="noopener noreferrer"&gt;Heroku Postgres&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Shield-0&lt;/td&gt;
&lt;td&gt;$350+&lt;/td&gt;
&lt;td&gt;$4,200+&lt;/td&gt;
&lt;td&gt;4 GB RAM, 68 GB storage, rollback up to 7 days, high availability (Premium tier feature), dedicated environment, HIPAA and PCI&lt;/td&gt;
&lt;td&gt;Also needs a Shield Private Space through Heroku Enterprise, price not listed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://render.com/pricing" rel="noopener noreferrer"&gt;Render&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Scale workspace + smallest Postgres&lt;/td&gt;
&lt;td&gt;~$506&lt;/td&gt;
&lt;td&gt;~$6,074&lt;/td&gt;
&lt;td&gt;0.1 CPU, 256 MB RAM, 1 GB SSD ($0.30/GB more), logical backups and 7 day PITR, HA not in this estimate, workspace audit logs, SOC 2 and ISO not stated on pricing page&lt;/td&gt;
&lt;td&gt;$499/mo workspace, plus 20% on compute for HIPAA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.cockroachlabs.com/pricing/" rel="noopener noreferrer"&gt;CockroachDB&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Mission Critical&lt;/td&gt;
&lt;td&gt;~$710&lt;/td&gt;
&lt;td&gt;~$8,515&lt;/td&gt;
&lt;td&gt;3 dedicated nodes x 2 vCPU, storage billed extra, hourly backups kept 30 days (billed), multi node by design, SOC 2 and ISO not stated on pricing page&lt;/td&gt;
&lt;td&gt;Estimate for 3 nodes x 2 vCPU, before storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://supabase.com/pricing" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Team + HIPAA add-on&lt;/td&gt;
&lt;td&gt;~$1,054&lt;/td&gt;
&lt;td&gt;~$12,648&lt;/td&gt;
&lt;td&gt;Small shared compute with 2 GB RAM, 8 GB disk, daily backups kept 14 days plus 7 day PITR, HA not stated, SOC 2 and ISO 27001&lt;/td&gt;
&lt;td&gt;$599 Team plan, plus a HIPAA add-on that isn't priced publicly (reported at ~$350/mo), plus $100/mo backups that &lt;a href="https://supabase.com/docs/guides/security/hipaa-compliance" rel="noopener noreferrer"&gt;HIPAA projects need&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;No public HIPAA price at all:&lt;/strong&gt; &lt;a href="https://docs.crunchybridge.com/concepts/security" rel="noopener noreferrer"&gt;Crunchy Bridge&lt;/a&gt; (you contact them for a BAA), &lt;a href="https://railway.com/pricing" rel="noopener noreferrer"&gt;Railway&lt;/a&gt; (Enterprise only), &lt;a href="https://aiven.io/docs/platform/concepts/enhanced-compliance-env" rel="noopener noreferrer"&gt;Aiven&lt;/a&gt; (a separate compliance environment with a commitment deal), &lt;a href="https://www.tigerdata.com/pricing" rel="noopener noreferrer"&gt;Tiger Data&lt;/a&gt; (Enterprise only) and &lt;a href="https://xata.io/pricing.md" rel="noopener noreferrer"&gt;Xata&lt;/a&gt; (bring your own cloud, with a yearly minimum).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No BAA for managed databases:&lt;/strong&gt; &lt;a href="https://www.digitalocean.com/trust/hipaa-at-do" rel="noopener noreferrer"&gt;DigitalOcean&lt;/a&gt; will sign a BAA, but Managed Databases aren't on its covered list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading the fine print
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Hidden add-ons.&lt;/strong&gt; Supabase's headline is $599 a month, but HIPAA sits in an add-on with no public price, and the backups it needs cost extra. That's how a $7k plan turns into roughly $12.6k. Render's $499 workspace also adds 20% to compute once HIPAA is switched on. Heroku lists the Shield database price but not the private space it needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why there's no AWS, Google Cloud or Azure.&lt;/strong&gt; All three sign a BAA at no extra cost, but on their own they're building blocks you configure and run yourself: networking, IAM, logging, backup policies and sizing are your job. This post compares fully managed databases, so I left them out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Announced price changes.&lt;/strong&gt; Neon includes HIPAA on its Scale plan today, but its docs say a 15% surcharge will be added, with notice. Budget for it.&lt;/p&gt;

&lt;p&gt;None of this makes those providers bad choices. It just means the cheapest number in a table and your real annual bill aren't the same thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which one fits
&lt;/h3&gt;

&lt;p&gt;There's no single winner. It depends on how you want to pay and what you already run.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You want the lowest bill at a tiny size:&lt;/strong&gt; Neon or MongoDB Atlas, as long as you budget for Neon's coming surcharge and MongoDB's advice to size up for sensitive data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want a flat yearly number with usage included:&lt;/strong&gt; Prisma Postgres. Details are on its &lt;a href="https://www.prisma.io/postgres/hipaa" rel="noopener noreferrer"&gt;HIPAA compliant database page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You're already on Heroku or Render:&lt;/strong&gt; staying put saves a migration, but HIPAA there means moving up to their higher tiers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need Postgres features Supabase adds on top (auth, storage, realtime):&lt;/strong&gt; Supabase, at the highest price on this list.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What HIPAA actually needs from your database
&lt;/h3&gt;

&lt;p&gt;Whichever managed provider you pick, check for these five things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A signed BAA&lt;/strong&gt; before any patient data touches the database. No BAA, no PHI. Simple as that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encryption&lt;/strong&gt; at rest and in transit. Check that TLS is enforced for connections, not just offered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access controls.&lt;/strong&gt; Limit who can see the workspace, the database and the connection strings. Rotate credentials when people leave.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit trails.&lt;/strong&gt; You need to be able to show who did what. Some of this comes from your provider, and some from your own app logs (keep PHI out of those logs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backups you've tested.&lt;/strong&gt; Know how long backups are kept and do a practice restore before you need one.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Remember that HIPAA is shared responsibility. The provider secures the platform. You secure your app, your team's access and your own policies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting a BAA signed
&lt;/h3&gt;

&lt;p&gt;The steps are similar everywhere:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick a plan where HIPAA is included, and check what the BAA covers. Some only cover the database, not other products from the same vendor.&lt;/li&gt;
&lt;li&gt;Read the provider's standard BAA and share it with whoever handles legal on your team.&lt;/li&gt;
&lt;li&gt;Request the BAA (usually a form or an email to a compliance address) and sign it.&lt;/li&gt;
&lt;li&gt;Only then put patient data in the database.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  How I calculated this
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fully managed databases only.&lt;/strong&gt; Raw cloud services you run yourself are left out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BAA included with the plan.&lt;/strong&gt; Providers that review and approve BAA requests case by case are left out.&lt;/li&gt;
&lt;li&gt;Prices were taken from each provider's public pricing and HIPAA pages in &lt;strong&gt;October 2026&lt;/strong&gt;, US regions, list prices, before tax.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Annual = monthly x 12.&lt;/strong&gt; No discounts, reserved instances or committed-use deals.&lt;/li&gt;
&lt;li&gt;For usage-billed services I priced the &lt;strong&gt;smallest always-on setup&lt;/strong&gt; that can be covered by a BAA, with minimal storage. Your bill will depend on traffic and data size.&lt;/li&gt;
&lt;li&gt;The Supabase HIPAA add-on price isn't published. The ~$350/mo figure is a reported quote, not an official price.&lt;/li&gt;
&lt;li&gt;Prices change. If you spot something out of date, tell me in the comments and I'll update the table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HIPAA shouldn't be a reason to sign an enterprise contract before you've found product-market fit. Read the fine print, budget for the real setup, and pick the managed provider whose price you can actually see.&lt;/p&gt;

</description>
      <category>hipaa</category>
      <category>database</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Great article by Shubra. I hope everyone gives the new Prisma a try. It's not an ORM anymore.</title>
      <dc:creator>Martin Janse van Rensburg</dc:creator>
      <pubDate>Thu, 29 Oct 2026 10:28:20 +0000</pubDate>
      <link>https://dev.to/vanrensbird/great-article-by-shubra-i-hope-everyone-gives-the-new-prisma-a-try-its-not-an-orm-anymore-1m0n</link>
      <guid>https://dev.to/vanrensbird/great-article-by-shubra-i-hope-everyone-gives-the-new-prisma-a-try-its-not-an-orm-anymore-1m0n</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/shubhradev/hono-on-prisma-postgres-and-compute-what-worked-and-what-broke-5dd3" class="crayons-story__hidden-navigation-link"&gt;Hono on Prisma Postgres and Compute, What Worked and What Broke&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/shubhradev/hono-on-prisma-postgres-and-compute-what-worked-and-what-broke-5dd3" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;AI-generated code uncovers a nasty connection bug&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/shubhradev" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3462235%2Fa195c0cb-1004-4a1a-93f4-ecb8593c6884.jpg" alt="shubhradev profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/shubhradev" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Shubhra Pokhariya
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Shubhra Pokhariya
                
                
              
              &lt;div id="story-author-preview-content-4934185" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/shubhradev" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3462235%2Fa195c0cb-1004-4a1a-93f4-ecb8593c6884.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Shubhra Pokhariya&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/shubhradev/hono-on-prisma-postgres-and-compute-what-worked-and-what-broke-5dd3" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 29&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/shubhradev/hono-on-prisma-postgres-and-compute-what-worked-and-what-broke-5dd3" id="article-link-4934185"&gt;
          Hono on Prisma Postgres and Compute, What Worked and What Broke
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/prisma"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;prisma&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/postgres"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;postgres&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/backend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;backend&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/shubhradev/hono-on-prisma-postgres-and-compute-what-worked-and-what-broke-5dd3" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;32&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/shubhradev/hono-on-prisma-postgres-and-compute-what-worked-and-what-broke-5dd3#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              12&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            11 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>backend</category>
      <category>webdev</category>
      <category>postgres</category>
      <category>database</category>
    </item>
    <item>
      <title>Zero-downtime Postgres migration with logical replication: the checklist we actually used</title>
      <dc:creator>Martin Janse van Rensburg</dc:creator>
      <pubDate>Mon, 26 Oct 2026 23:14:05 +0000</pubDate>
      <link>https://dev.to/vanrensbird/zero-downtime-postgres-migration-with-logical-replication-the-checklist-we-actually-used-3k4k</link>
      <guid>https://dev.to/vanrensbird/zero-downtime-postgres-migration-with-logical-replication-the-checklist-we-actually-used-3k4k</guid>
      <description>&lt;p&gt;Most advice about moving a production database boils down to "schedule a maintenance window and run &lt;code&gt;pg_dump&lt;/code&gt;". That works until customers use your product around the clock and six services write to the same database.&lt;/p&gt;

&lt;p&gt;That was our situation. The database behind our own Console (every workspace, project and API key) lived on Amazon RDS, and we wanted it on &lt;a href="https://www.prisma.io/postgres" rel="noopener noreferrer"&gt;Prisma Postgres&lt;/a&gt;, the managed Postgres we run for our users. We moved 83 tables and 74 GB without pausing writes, and we could have switched back to RDS for a full week afterwards without losing a row.&lt;/p&gt;

&lt;p&gt;This is the checklist we'd hand a friend about to do the same thing, plus the SQL that mattered and the one thing that bit us.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why logical replication and not dump/restore
&lt;/h2&gt;

&lt;p&gt;A dump and restore is a snapshot. Anything written after the snapshot is lost unless you stop writes until the restore finishes. On 74 GB that's not a short pause.&lt;/p&gt;

&lt;p&gt;Logical replication is built into Postgres and works differently. The old database &lt;em&gt;publishes&lt;/em&gt; row changes, the new one &lt;em&gt;subscribes&lt;/em&gt;. The subscriber copies each table once, then keeps applying inserts, updates and deletes as they happen. Your app keeps writing to the old database the whole time, and you switch over when the new one has caught up.&lt;/p&gt;

&lt;p&gt;It has rules, though, and every gotcha we hit was one of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pre-flight checklist
&lt;/h2&gt;

&lt;p&gt;We went through each of these before creating anything. Skip one and the migration stalls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Logical decoding is on at the source.&lt;/strong&gt; Postgres needs &lt;code&gt;wal_level = logical&lt;/code&gt; to publish row changes. On RDS you get there with the &lt;code&gt;rds.logical_replication&lt;/code&gt; parameter. Ours was already set, but check it, because changing it needs a reboot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Every table has a primary key.&lt;/strong&gt; The subscriber uses it to find the row an update or delete applies to. This query should return nothing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_namespace&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relnamespace&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relkind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_index&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indrelid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indisprimary&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. The target already has the schema.&lt;/strong&gt; Replication copies rows, not tables. We let our normal CI pipeline run all 204 migrations against the new database, so both sides had identical schemas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The target tables are empty, with one exception you need to plan for.&lt;/strong&gt; Because CI had already migrated the new database, its migration history table was full. Copying the old one on top would fail on duplicate keys. So we published every table &lt;em&gt;by name&lt;/em&gt; and left that one out. If you use any migration tool that tracks history in a table, you'll have the same issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. The new database can reach the old one.&lt;/strong&gt; The subscriber opens the connection, not the other way round. Our RDS security group blocked it. We added the inbound rule in a separate security group so we could delete it cleanly afterwards, then tested the path with &lt;code&gt;dblink&lt;/code&gt; before going further.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. The target stays awake.&lt;/strong&gt; Ours pauses with no client connections, and a replication stream doesn't count, so a tiny service pinged it every few seconds during the copy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. You know where the bytes are.&lt;/strong&gt; Two tables held about three quarters of the data. The biggest was a log of webhook deliveries with no retention policy. A 30-day retention job &lt;em&gt;before&lt;/em&gt; the copy would have saved a lot of waiting. We added it afterwards, naturally.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SQL that did the work
&lt;/h2&gt;

&lt;p&gt;Here's the condensed version. Hostnames and passwords are placeholders.&lt;/p&gt;

&lt;p&gt;On the source, create a dedicated replication login and a publication that lists tables explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;migration_repl&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;LOGIN&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;password&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="n"&gt;rds_replication&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;migration_repl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;USAGE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;migration_repl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;migration_repl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;string_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quote_ident&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_tables&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;schemaname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;tablename&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'_prisma_migrations'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="s1"&gt;'CREATE PUBLICATION app_migration FOR TABLE '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the target, subscribe. This creates the slot on the source and starts the initial copy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;SUBSCRIPTION&lt;/span&gt; &lt;span class="n"&gt;app_migration&lt;/span&gt;
  &lt;span class="k"&gt;CONNECTION&lt;/span&gt; &lt;span class="s1"&gt;'host=&amp;lt;source&amp;gt; dbname=app user=migration_repl
              password=&amp;lt;password&amp;gt; sslmode=require'&lt;/span&gt;
  &lt;span class="n"&gt;PUBLICATION&lt;/span&gt; &lt;span class="n"&gt;app_migration&lt;/span&gt;
  &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;create_slot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then watch three things: per-table state on the target, error counters on the target, and how much WAL the slot is holding on the source.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- target: r = ready&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;srsubstate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_subscription_rel&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;apply_error_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sync_error_count&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_subscription_stats&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- source&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;slot_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;pg_size_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pg_wal_lsn_diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pg_current_wal_lsn&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;restart_lsn&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_replication_slots&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All 82 tables reached ready the same day, with both error counters at zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying without fooling yourself
&lt;/h2&gt;

&lt;p&gt;Run &lt;code&gt;ANALYZE&lt;/code&gt; on the target first. The initial copy doesn't leave planner statistics behind, and your first real queries will plan badly without them.&lt;/p&gt;

&lt;p&gt;Then we used two checks. First, one query (through &lt;code&gt;dblink&lt;/code&gt;) counted rows in every table on both sides and returned &lt;strong&gt;only the tables that differ&lt;/strong&gt;. An empty result is the signal you want, and it's much easier to trust than scrolling through 82 matching numbers.&lt;/p&gt;

&lt;p&gt;Second, an md5 over full rows for a few tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'|'&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"Organization"&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hashes matched on slow-moving tables and differed on two tables touched on every login, simply because the two queries ran seconds apart. Expect that, and don't panic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cutover: one merge, every connection
&lt;/h2&gt;

&lt;p&gt;Our rule was that every service, and every connection path inside each service, switches in the same deploy. If one path moves and another doesn't, a service can write a row to one database and then fail to read it from the other.&lt;/p&gt;

&lt;p&gt;We made that boring on purpose:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Days before, every service learned to read its connection string from a &lt;em&gt;new&lt;/em&gt; secret name, falling back to the old one. No behavior change.&lt;/li&gt;
&lt;li&gt;Minutes before, we created the new secrets. Running services don't pick them up until redeployed, so still no change.&lt;/li&gt;
&lt;li&gt;We merged one pull request, and the deploy rolled every service onto the new database.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a few minutes during the deploy, some services wrote to the old database and some to the new one. Forward replication kept carrying old-side writes across. A key collision would show up as an apply error (it stayed at zero). Two sides updating the same row would &lt;em&gt;not&lt;/em&gt; show up anywhere, which is why we cared so much about keeping that window short. The last write to RDS landed seven minutes after the merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rollback we kept for a week
&lt;/h2&gt;

&lt;p&gt;This is the part we'd recommend most. After cutover, we turned replication around so the old database followed the new one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Minutes before the merge&lt;/strong&gt;, create a logical replication slot on the new database. It retains every change from that moment, so the rollback covers the cutover itself. Don't create it days early: an unread slot was holding roughly a gigabyte of WAL per hour for us.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Once the old side is quiet&lt;/strong&gt; (we sampled its write counters for sixty seconds), drop the forward subscription.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscribe the old database to the new one&lt;/strong&gt; from that slot with &lt;code&gt;copy_data = false&lt;/code&gt;, since it already has every row, and &lt;code&gt;origin = none&lt;/code&gt;, so only the application's own writes flow back instead of echoing the replicated ones.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Going back would have meant reverting one commit. After a week of daily row count checks, we dropped the reverse subscription (outside a transaction block, since a slot was attached), snapshotted RDS and shut it down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing that bit us
&lt;/h2&gt;

&lt;p&gt;Logical replication does not replicate schema changes. No &lt;code&gt;ALTER TABLE&lt;/code&gt;, no DDL at all. While replication runs in either direction, every migration has to run on both databases.&lt;/p&gt;

&lt;p&gt;We knew that. We had one CI migration job per database. But one of our cutover changes accidentally pointed the &lt;em&gt;old&lt;/em&gt; database's job at the new one. Fifty-two minutes after cutover, a migration added three columns on the new side only, and the reverse stream stopped at the first row that used them. We applied the same SQL to RDS by hand about fifteen minutes later, and the stream caught up with nothing lost.&lt;/p&gt;

&lt;p&gt;The lesson is boring and real: before changing a connection setting, list everything that uses it, including CI jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  If we did it again
&lt;/h2&gt;

&lt;p&gt;We'd keep: tables published by name, a &lt;code&gt;dblink&lt;/code&gt; connectivity test before subscribing, mismatch-only verification queries, one deploy for every connection path, and a reverse replication rollback.&lt;/p&gt;

&lt;p&gt;We'd change: audit who uses each connection string first, and add retention to noisy tables before copying.&lt;/p&gt;

&lt;p&gt;We also paired with an AI coding agent: it drafted every query and read the results, and a human ran every statement that touched production. That split worked well.&lt;/p&gt;

&lt;p&gt;We wrote up the full story, with every SQL file and a step-by-step timeline, &lt;a href="https://www.prisma.io/blog/moving-prismas-control-plane-to-prisma-postgres" rel="noopener noreferrer"&gt;here on our blog&lt;/a&gt;. And if you want somewhere to practice this on a real database, &lt;a href="https://www.prisma.io/postgres" rel="noopener noreferrer"&gt;managed Postgres hosting from Prisma&lt;/a&gt; has a free plan with no card. It covers Prisma Postgres with 200,000 operations a month and 2 GB of storage, plus Prisma Compute with 1M requests, 360 GB-hours of memory, 4 active vCPU-hours and 10 GB of outbound bandwidth a month.&lt;/p&gt;

&lt;p&gt;Questions about any step? Ask in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>postgressql</category>
    </item>
  </channel>
</rss>
