<?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: AK D</title>
    <description>The latest articles on DEV Community by AK D (@aktisdev).</description>
    <link>https://dev.to/aktisdev</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%2F4146689%2F0eed37ca-d004-45de-b27e-cc0e11dce968.png</url>
      <title>DEV Community: AK D</title>
      <link>https://dev.to/aktisdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aktisdev"/>
    <language>en</language>
    <item>
      <title>What a Supabase database backup leaves out</title>
      <dc:creator>AK D</dc:creator>
      <pubDate>Thu, 15 Oct 2026 08:12:27 +0000</pubDate>
      <link>https://dev.to/aktisdev/what-a-supabase-database-backup-leaves-out-j0h</link>
      <guid>https://dev.to/aktisdev/what-a-supabase-database-backup-leaves-out-j0h</guid>
      <description>&lt;p&gt;A Supabase database backup restores your Postgres data. Most projects depend on more than that, and the gaps only show up when you restore into a new project.&lt;/p&gt;

&lt;p&gt;Here is what does not come back from the database backup alone.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Where it lives&lt;/th&gt;
&lt;th&gt;In the database backup?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tables, rows, RLS policies, functions&lt;/td&gt;
&lt;td&gt;Postgres&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth users&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;auth&lt;/code&gt; schema in Postgres&lt;/td&gt;
&lt;td&gt;Yes, if you back up that schema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage file contents&lt;/td&gt;
&lt;td&gt;Object storage&lt;/td&gt;
&lt;td&gt;No, only the metadata rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edge Function code&lt;/td&gt;
&lt;td&gt;Deployed separately&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edge Function secrets&lt;/td&gt;
&lt;td&gt;Project settings&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Passwords of custom database roles&lt;/td&gt;
&lt;td&gt;Postgres roles&lt;/td&gt;
&lt;td&gt;Need to be reset after a restore&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Storage files
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;storage.objects&lt;/code&gt; table tells you which files exist. The files themselves are not in it. After a restore the app can list every file and download none of them. You need a separate copy of the bytes, ideally from the same moment as the database backup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auth users with pg_dump
&lt;/h2&gt;

&lt;p&gt;A common pg_dump example uses &lt;code&gt;--schema=public&lt;/code&gt; to skip the Supabase-managed schemas. That also skips &lt;code&gt;auth&lt;/code&gt;, so the restored project has your data but no user accounts. If you dump manually, include &lt;code&gt;auth&lt;/code&gt; or dump the full database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge Functions
&lt;/h2&gt;

&lt;p&gt;Function code is deployed from your machine or CI, not stored in Postgres. If the code lives in Git, you can redeploy it. If someone deployed from a laptop and never committed, the database backup will not help. Secrets have to be set again by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test it once
&lt;/h2&gt;

&lt;p&gt;The only way to know what your backup covers is to restore it into an empty project and use the app. Log in, open a page with images, call a function. Whatever breaks is what your backup is missing.&lt;/p&gt;

&lt;p&gt;I wrote a longer comparison of the &lt;a href="https://revivedb.dev/supabase-backup" rel="noopener noreferrer"&gt;Supabase backup options and what each one covers&lt;/a&gt;. If you'd rather not script all of this yourself, &lt;a href="https://revivedb.dev" rel="noopener noreferrer"&gt;ReviveDB&lt;/a&gt; backs up the database, Auth, Storage files and Edge Functions together and test-restores every database backup.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>postgres</category>
      <category>database</category>
      <category>backup</category>
    </item>
    <item>
      <title>How to test the database layer of a Supabase backup</title>
      <dc:creator>AK D</dc:creator>
      <pubDate>Thu, 01 Oct 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/aktisdev/how-to-test-the-database-layer-of-a-supabase-backup-2nnf</link>
      <guid>https://dev.to/aktisdev/how-to-test-the-database-layer-of-a-supabase-backup-2nnf</guid>
      <description>&lt;p&gt;To test a Supabase database backup, restore it into a clean, isolated target with errors treated as fatal. Compare schemas, roles, row counts and sequences with an inventory captured at backup time, then run application checks. Record the restore result and duration for each test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a valid dump can still fail you
&lt;/h2&gt;

&lt;p&gt;A dump may be readable and still be operationally useless. Missing roles can break ownership. Absent extensions can stop schema creation. A version mismatch can surface incompatible syntax. A restore may log errors, continue, and leave behind a database that looks populated but is incomplete.&lt;/p&gt;

&lt;p&gt;PostgreSQL documents that &lt;a href="https://www.postgresql.org/docs/17/app-pgdump.html" rel="noopener noreferrer"&gt;pg_dump creates a consistent snapshot without blocking readers or writers&lt;/a&gt;. It also documents the boundary: pg_dump exports one database and does not include cluster-wide roles and tablespaces. A restore plan must account for everything outside that boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Capture a source inventory
&lt;/h2&gt;

&lt;p&gt;Before restoring, record what “complete” means. At minimum, capture the server version, extensions, schemas, tables, sequences, views, functions, row counts, and ownership expectations. For products built on Postgres, add domain checks such as user counts, tenant counts, or object inventory.&lt;/p&gt;

&lt;p&gt;Take the inventory at the same logical point as the backup. Comparing a restored nightly dump with a live database the next afternoon produces noise, not evidence. Store the source inventory beside the backup artifact and identify both with the same run ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Restore into a clean, isolated target
&lt;/h2&gt;

&lt;p&gt;A clean target exposes hidden dependencies. Restoring repeatedly into a long-lived test database can succeed only because an extension, role, or table survived a previous run. Provision a disposable database for each verification and remove it afterward.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Match the production PostgreSQL major version unless you are deliberately testing an upgrade.&lt;/li&gt;
&lt;li&gt;Block application traffic and outbound integrations from the verification environment.&lt;/li&gt;
&lt;li&gt;Use temporary credentials and never send production emails, webhooks, or jobs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Make restore errors fatal
&lt;/h2&gt;

&lt;p&gt;For a custom-format archive, use pg_restore with an error policy that cannot silently leave a partial result. PostgreSQL’s &lt;a href="https://www.postgresql.org/docs/17/app-pgrestore.html" rel="noopener noreferrer"&gt;--single-transaction option&lt;/a&gt; executes the restore as one transaction and implies exit-on-error: either all commands complete or none are applied.&lt;/p&gt;

&lt;p&gt;If the database is too large for one transaction, fail on the first SQL error, keep the logs and throw the target away after a failure. The exact flag matters less than refusing to call a partial restore successful.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Compare structure and data
&lt;/h2&gt;

&lt;p&gt;Start with inventory because it catches broad omissions quickly. Compare schemas, relations, extensions, and row counts. Then add checks that represent business value. A table count cannot tell you whether the latest subscription exists or whether a restored user can access the correct tenant.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schema objects: names and types, with expected exclusions documented.&lt;/li&gt;
&lt;li&gt;Data: row counts plus targeted invariants and representative records.&lt;/li&gt;
&lt;li&gt;Security: roles, ownership, grants, and row-level-security policies.&lt;/li&gt;
&lt;li&gt;Sequences: next values must not collide with restored rows.&lt;/li&gt;
&lt;li&gt;Large objects and external files: count, size, and checksum where applicable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Run application-level recovery checks
&lt;/h2&gt;

&lt;p&gt;Database equivalence is necessary but not sufficient. Point a safely isolated application build at the restored target and exercise a short recovery script: authenticate, read a core record, perform a transaction, verify permissions, and retrieve a file if the product uses object storage.&lt;/p&gt;

&lt;p&gt;Keep the smoke test small and deterministic. Its purpose is to catch broken contracts between the application and the restored state, not to replace the full test suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Measure and retain evidence
&lt;/h2&gt;

&lt;p&gt;Record artifact identity and checksum, source and target versions, start and finish times, restore logs, inventory differences, smoke-test results, and the final disposition. This turns “we tested it once” into evidence that can be reviewed after a change or incident.&lt;/p&gt;

&lt;p&gt;Track restore duration over time. A backup that restores correctly in six hours still fails a two-hour RTO. Growth in recovery time is an operational signal, just like growth in database size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the target version after a PostgreSQL upgrade
&lt;/h2&gt;

&lt;p&gt;Supabase’s &lt;a href="https://supabase.com/changelog/postgres-15-19-17-11-breaking-changes" rel="noopener noreferrer"&gt;PostgreSQL 15.19 and 17.11 release notice&lt;/a&gt; lists four conditional changes: two for ltree indexes, one for btree_gist indexes on floating-point columns and one for legacy pgcrypto ciphers. It also limits how custom operators with a selectivity estimator can be recreated. Run its detection queries for the project before upgrading; a passing row-count comparison alone does not rule them out.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom operators with a non-built-in selectivity estimator can fail to recreate during a logical restore on the new minor version because Supabase roles are not superusers. Treat that SQL error as a failed restore. Use the official detection query and remove the estimator or contact Supabase support before relying on the dump.&lt;/li&gt;
&lt;li&gt;Existing ltree indexes and btree_gist indexes on floating-point values containing NaN may return incomplete or wrong results after the upgrade. Use Supabase’s conditions and index queries to identify affected indexes, then reindex them on the upgraded source. A fresh scratch restore builds new indexes and cannot prove that old source indexes were rebuilt.&lt;/li&gt;
&lt;li&gt;If the application used legacy pgcrypto PGP ciphers such as blowfish or cast5, test decryption on the target version with a controlled sample. A successful restore and matching row counts do not prove that the values can still be decrypted. Follow Supabase’s migration instructions for affected ciphertext.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run the next restore drill against the PostgreSQL minor version planned for recovery and keep its log. Separately verify application queries against the upgraded source, because the restored target and the existing source can fail in different ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  How often should you test?
&lt;/h2&gt;

&lt;p&gt;Automate a structural restore test for every backup when the database size and cost make that practical. Run deeper application recovery drills on a slower schedule and after meaningful changes to versions, extensions, backup tooling, or infrastructure. The test frequency should follow the rate at which your recovery assumptions can change.&lt;/p&gt;

&lt;h2&gt;
  
  
  A result you can trust
&lt;/h2&gt;

&lt;p&gt;Use three outcomes: verified, failed, or verified with explicitly accepted differences. Never turn unexplained differences into success just to keep the dashboard green. Expected exclusions should be versioned policy; everything else deserves investigation.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build &lt;a href="https://revivedb.dev" rel="noopener noreferrer"&gt;ReviveDB&lt;/a&gt;, which runs this restore-and-compare check on every Supabase database backup. This post was first published on the &lt;a href="https://revivedb.dev/blog/how-to-test-postgresql-backup" rel="noopener noreferrer"&gt;ReviveDB blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>supabase</category>
      <category>backup</category>
      <category>testing</category>
    </item>
    <item>
      <title>Do Supabase backups include Storage files?</title>
      <dc:creator>AK D</dc:creator>
      <pubDate>Mon, 28 Sep 2026 08:50:56 +0000</pubDate>
      <link>https://dev.to/aktisdev/do-supabase-backups-include-storage-files-29ef</link>
      <guid>https://dev.to/aktisdev/do-supabase-backups-include-storage-files-29ef</guid>
      <description>&lt;p&gt;No. Supabase database backups do not include the actual files in Supabase Storage. They keep the rows that describe those files. That is easy to miss: after a database restore the catalogue can look fine while every download returns an error.&lt;/p&gt;

&lt;h2&gt;
  
  
  What survives a Supabase database restore?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Storage component&lt;/th&gt;
&lt;th&gt;In the database backup?&lt;/th&gt;
&lt;th&gt;Separate recovery required?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bucket and object metadata rows&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Verify that metadata matches the selected recovery point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Actual file bytes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes. Copy and restore every object separately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public/private bucket setting&lt;/td&gt;
&lt;td&gt;Not reliably portable as database-only recovery&lt;/td&gt;
&lt;td&gt;Capture and recreate through the Storage API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File-size and MIME-type restrictions&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Capture and recreate through the Storage API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object checksum evidence&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Create a manifest while copying the bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The practical test is simple: after recovery, can the application download the expected bytes? A restored &lt;code&gt;storage.objects&lt;/code&gt; row proves only that the catalogue entry exists.&lt;/p&gt;

&lt;p&gt;Supabase states this directly in its &lt;a href="https://supabase.com/docs/guides/platform/backups" rel="noopener noreferrer"&gt;database backups documentation&lt;/a&gt;: database backups include Storage metadata but not objects stored through the Storage API. To restore both, keep the PostgreSQL data and object bytes from the same backup window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metadata and files are two different assets
&lt;/h2&gt;

&lt;p&gt;Supabase Storage uses database records to describe buckets and objects. Those records contain information such as the bucket, object path and ownership. The content of a PDF, image or video is stored separately. Restoring only the database can recreate the catalogue without recreating what the catalogue points to.&lt;/p&gt;

&lt;p&gt;A row count is consequently not a file-integrity check. Ten thousand restored object rows can coexist with zero recoverable files. Verification must fetch the bytes and compare them with evidence captured when the backup was made.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a useful Storage backup records
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The bucket and full object path, preserving case and special characters.&lt;/li&gt;
&lt;li&gt;The object size and content type.&lt;/li&gt;
&lt;li&gt;A cryptographic checksum, such as SHA-256, calculated from the downloaded bytes.&lt;/li&gt;
&lt;li&gt;The backup timestamp and the database snapshot it belongs to.&lt;/li&gt;
&lt;li&gt;Any version or generation identifier used by the destination object store.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The timestamp relationship matters. If database metadata is captured at 02:00 but file copying continues until 05:00, files may be created, replaced or deleted between those moments. Your recovery report should expose that consistency window instead of presenting both artifacts as one perfectly synchronized snapshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ways to copy Supabase Storage objects
&lt;/h2&gt;

&lt;p&gt;Supabase documents several download paths, including its client libraries, the CLI and the S3-compatible endpoint. The &lt;a href="https://supabase.com/docs/guides/storage/management/download-objects" rel="noopener noreferrer"&gt;official object download guide&lt;/a&gt; is the best starting point because the appropriate method depends on volume and automation requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Small projects
&lt;/h3&gt;

&lt;p&gt;For a small inventory, list objects, download each object and write a manifest containing size and checksum. Retry individual failures, but make the final job fail if any object remains missing. A partially successful copy should never receive a green status.&lt;/p&gt;

&lt;h3&gt;
  
  
  Larger projects
&lt;/h3&gt;

&lt;p&gt;Use bounded concurrency, resumable transfers where available and an object store in a separate account or provider. Keep deletion rights separate from normal application credentials. A backup that can be deleted by the same compromised credential as production is not an independent recovery layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to test a Storage restore
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Restore the matching database backup into an isolated Supabase or PostgreSQL recovery environment.&lt;/li&gt;
&lt;li&gt;Recreate the required buckets and access policies through a documented process.&lt;/li&gt;
&lt;li&gt;Upload the backed-up objects to the paths referenced by the restored metadata.&lt;/li&gt;
&lt;li&gt;Compare expected and restored object counts, sizes and checksums.&lt;/li&gt;
&lt;li&gt;Use the application to download representative private and public objects.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Include edge cases in the sample: a large object, a path containing spaces, a private object, an object owned by a user and a recently replaced file. These checks catch problems that a single public image cannot.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when a Supabase project is deleted?
&lt;/h2&gt;

&lt;p&gt;Supabase describes project deletion as irreversible and says it removes the database, Storage objects, backups, functions and configuration. See the &lt;a href="https://supabase.com/docs/guides/platform/delete-project" rel="noopener noreferrer"&gt;project deletion documentation&lt;/a&gt;. If accidental or malicious project deletion is within your threat model, copies must live outside that project lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical answer
&lt;/h2&gt;

&lt;p&gt;Treat the Postgres dump, Storage-object copy and configuration inventory as separate artifacts joined by one recovery run. Back up each one, retain them independently and prove they work together. That is the difference between restoring a list of filenames and restoring a working product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supabase Storage backup questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does PITR back up Supabase Storage objects?
&lt;/h3&gt;

&lt;p&gt;No. PITR protects PostgreSQL state, including Storage metadata, but it does not version or replay the separate object bytes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can you use &lt;code&gt;storage.objects&lt;/code&gt; as a file backup?
&lt;/h3&gt;

&lt;p&gt;No. Those rows are an index of objects, not the object content. Restoring or editing them directly cannot recreate a missing PDF, image or upload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can you restore Storage to a new Supabase project?
&lt;/h3&gt;

&lt;p&gt;Yes, if you retained an independent object copy and its manifest. Recreate compatible buckets first, upload objects to the original paths, then compare counts, sizes and hashes before application cutover.&lt;/p&gt;

&lt;h3&gt;
  
  
  What should a Storage backup manifest contain?
&lt;/h3&gt;

&lt;p&gt;At minimum: bucket, complete object path, byte size, content type, checksum, capture time and the database recovery point it belongs to.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build &lt;a href="https://revivedb.dev" rel="noopener noreferrer"&gt;ReviveDB&lt;/a&gt;, which backs up Supabase projects outside Supabase, including Storage files. This post was first published on the &lt;a href="https://revivedb.dev/blog/do-supabase-backups-include-storage" rel="noopener noreferrer"&gt;ReviveDB blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>postgres</category>
      <category>backup</category>
      <category>database</category>
    </item>
  </channel>
</rss>
