<?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: Declan Leroy</title>
    <description>The latest articles on DEV Community by Declan Leroy (@declan_letoy).</description>
    <link>https://dev.to/declan_letoy</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3929175%2F64b71b19-07cc-4261-b6d8-5793f84f22e0.png</url>
      <title>DEV Community: Declan Leroy</title>
      <link>https://dev.to/declan_letoy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/declan_letoy"/>
    <language>en</language>
    <item>
      <title>PostgreSQL backup tool Databasus released backup verification in real database Docker containers</title>
      <dc:creator>Declan Leroy</dc:creator>
      <pubDate>Thu, 21 May 2026 10:42:07 +0000</pubDate>
      <link>https://dev.to/declan_letoy/databasus-released-backup-verification-in-real-database-docker-containers-1p3b</link>
      <guid>https://dev.to/declan_letoy/databasus-released-backup-verification-in-real-database-docker-containers-1p3b</guid>
      <description>&lt;p&gt;Databasus just shipped restore verification. The idea behind it is short. A backup job that ends with "success" only proves the dump command ran. It does not prove you can restore the file it produced. Restore verification closes that gap. It takes your latest backup, restores it into a throwaway database container and checks that the data actually came back.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqi939m0mp7bbqhf291wn.png" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2Fqi939m0mp7bbqhf291wn.png" alt="Backup restore verification" width="799" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Databasus is the most widely used tool for &lt;a href="https://databasus.com" rel="noopener noreferrer"&gt;PostgreSQL backup&lt;/a&gt;, and it already handles logical, physical and point-in-time recovery backups. Restore verification is the piece that now confirms those backups will actually restore. You can read the full feature docs on the &lt;a href="https://databasus.com/restore-verification" rel="noopener noreferrer"&gt;restore verification page&lt;/a&gt;. The rest of this article covers what it does, how the report reads and how to switch it on.&lt;/p&gt;

&lt;h2&gt;
  
  
  A backup that finished is not a backup you can restore
&lt;/h2&gt;

&lt;p&gt;Here is the uncomfortable part of running backups. Most setups check two things and call it done. They check that the backup job did not throw an error, and sometimes they check a file checksum. Both checks are real and worth having. The trouble is that neither one tells you the backup will restore.&lt;/p&gt;

&lt;p&gt;A dump can exit with code 0 and still be missing data. A role without read permission on certain tables can make the dump tool skip those objects quietly. So can a missing extension on the source or a tablespace mismatch. None of that shows up in an exit code, and a checksum will happily confirm that an incomplete dump is a perfectly intact file.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;What it proves&lt;/th&gt;
&lt;th&gt;What it still misses&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Backup job succeeded&lt;/td&gt;
&lt;td&gt;The dump command ran and exited without an error&lt;/td&gt;
&lt;td&gt;Objects skipped because a role lacked read permission&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Archive checksum matches&lt;/td&gt;
&lt;td&gt;The stored file is intact, with no bit rot&lt;/td&gt;
&lt;td&gt;Whether the dump is complete or can be restored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restore verification passed&lt;/td&gt;
&lt;td&gt;The backup restored into a live database and the rows are there&lt;/td&gt;
&lt;td&gt;Nothing meaningful, since it runs the actual restore&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A green backup status and a matching checksum are both fine to have. They just leave one question open, and it is the question that matters most. Restore verification is the check that answers it directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What restore verification does
&lt;/h2&gt;

&lt;p&gt;When a verification runs, it does not parse the backup or simulate a restore. It performs a real one. The sequence is the same every time, and it is easy to follow from start to finish.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pulls the most recent successful backup for the database&lt;/li&gt;
&lt;li&gt;Spins up a fresh database container and restores the archive into it with the engine's native restore tool&lt;/li&gt;
&lt;li&gt;Counts the rows in every table and checks the restored size against the backup&lt;/li&gt;
&lt;li&gt;Destroys the container along with everything inside it&lt;/li&gt;
&lt;li&gt;Sends the result back to Databasus&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each run is self-contained. It never touches your production database, and it leaves nothing behind once the check is done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inside a real database container
&lt;/h3&gt;

&lt;p&gt;The restore does not run against your live server. Databasus starts a separate Docker container for the job, on the host where the verification agent runs. That container runs the matching database engine, so the restore uses the real tooling instead of an approximation of it.&lt;/p&gt;

&lt;p&gt;Running it this way has a couple of practical upsides. The check cannot interfere with production traffic, because it never connects to the production server at all. And since the container is disposable, a verification can restore a full database without you hunting for spare space on a real instance. When the run finishes the container and its data are deleted, so a week of daily checks will not slowly fill a disk.&lt;/p&gt;

&lt;p&gt;This is the part the feature name points at. Your backup gets proven against a real engine in a real container, and then that container is thrown away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading a verification report
&lt;/h2&gt;

&lt;p&gt;Every verification leaves a report behind. At a glance you get a status, which is enough to see whether anything needs attention. Open the run itself and you get the detail, including exactly what came back from the archive.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it shows&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Status&lt;/td&gt;
&lt;td&gt;One of Pending, Running, Successful, Failed or Canceled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timeline&lt;/td&gt;
&lt;td&gt;Every stage of the run with its own timestamp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restore exit code&lt;/td&gt;
&lt;td&gt;The exit code returned by the database's native restore tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restored database size&lt;/td&gt;
&lt;td&gt;The size of the restored database, checked against the backup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema and table counts&lt;/td&gt;
&lt;td&gt;How many schemas and tables came back from the archive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-table row counts&lt;/td&gt;
&lt;td&gt;A row count for each table in the restored database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure message&lt;/td&gt;
&lt;td&gt;Shown at the top of a failed run, so the reason is the first thing you see&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The per-table row counts are the part worth watching. If a table you expect to be large comes back empty, the report shows it now, long before the day you actually need that backup to restore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning it on with a verification agent
&lt;/h2&gt;

&lt;p&gt;Restore verification is heavier than a normal backup job. It downloads a full backup, starts a database and runs a restore. Work like that does not belong inside the main Databasus container, so it runs on a small separate worker called the verification agent.&lt;/p&gt;

&lt;p&gt;The agent is a single lightweight Go binary. You create it in the Databasus UI first, which gives you something to register against, then you launch the binary on a host you control. It dials out to Databasus, picks up verification jobs from a queue and reports the results back. You can run it next to Databasus or on a different machine, whichever suits your hardware.&lt;/p&gt;

&lt;p&gt;Once it is running and registered, the agent appears in Databasus and starts taking jobs.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the agent host needs
&lt;/h3&gt;

&lt;p&gt;The agent does not ask for much, but it does have a few hard requirements. Most of them follow from the fact that it runs real databases in Docker. It is worth checking these before you launch it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Outbound HTTPS access to your Databasus URL, since the agent dials out and is never exposed&lt;/li&gt;
&lt;li&gt;Docker installed and running on the host&lt;/li&gt;
&lt;li&gt;Free disk of roughly twice your largest backup, with at least 1 GB of headroom&lt;/li&gt;
&lt;li&gt;At least 1 CPU core and 512 MB of RAM for every verification running at the same time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a host already runs Docker and has some disk to spare, it can almost certainly run an agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting a resource budget
&lt;/h3&gt;

&lt;p&gt;You will not want a verification to consume the whole machine, so the agent runs inside a budget. You pass four flags when you launch it, and Databasus divides that budget across the jobs it sends to the agent.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;What it limits&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--max-cpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CPU cores the agent may use&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--max-ram-mb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;RAM in megabytes the agent may use&lt;/td&gt;
&lt;td&gt;2048&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--max-disk-gb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Disk in gigabytes available for restores&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--max-concurrent-jobs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How many verifications run at the same time&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There is a floor of 1 CPU core and 512 MB of RAM per job. If your budget cannot cover that floor for the concurrency you asked for, the agent does not fail. It simply advertises a lower number of concurrent jobs and works within what it has. Set the budget once at launch and the agent stays inside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schedules, the queue and notifications
&lt;/h2&gt;

&lt;p&gt;You decide how often each database gets verified. There is no single right cadence, so Databasus gives you three ways to trigger a run, and you pick whichever fits the database in front of you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After every successful backup, the moment it finishes&lt;/li&gt;
&lt;li&gt;On an hourly, daily, weekly or monthly cadence at a time you choose&lt;/li&gt;
&lt;li&gt;On a custom UTC cron expression for anything the presets miss, for example &lt;code&gt;0 4 * * 0&lt;/code&gt; for every Sunday at 04:00&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The "after every backup" option raises a fair question. What if backups arrive faster than verifications can finish? Databasus handles that by cancelling any pending verification for a database as soon as a fresher backup shows up. Only the most recent backup waits in line, so checks never pile up and you always verify the newest data.&lt;/p&gt;

&lt;p&gt;You can also run a one-off verification by hand. Each database has a "Restore verifications" tab, and from there you can start a single check without changing the schedule. It is handy for spot-checking one particular backup, or for testing a new agent right after you set it up.&lt;/p&gt;

&lt;p&gt;Results can go to any notifier already wired to the database, so Slack, Discord, Telegram, email and the rest all work without extra setup. There are two separate toggles, one for verification success and one for verification failure, and they are independent of each other. Most teams switch on the failure one only, since a constant stream of "it worked" messages tends to get ignored after a while.&lt;/p&gt;

&lt;p&gt;Set a trigger once, point it at the notifiers you already use and the database keeps verifying itself from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Restore verification changes what a backup status means. Instead of "the dump finished", you get "the backup restored into a real database, and here are the row counts". That is the difference between hoping a backup works and knowing it does.&lt;/p&gt;

&lt;p&gt;Databasus is a free, open source and self-hosted backup tool, and it has become an industry standard for PostgreSQL backups. It is Apache 2.0 licensed, with more than 600,000 Docker pulls, around 7,000 GitHub stars and over 30 contributors. Restore verification ships in the same open source build as everything else, with no paid tier and no feature gate, and the code is all on &lt;a href="https://github.com/databasus/databasus" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you already run Databasus, set up an agent and turn verification on for your most important database first. If you do not run it yet, this is one of those features worth having in place well before the day you actually need a backup to come back.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>devops</category>
    </item>
    <item>
      <title>Engineering and security approaches used in open-source PostgreSQL backup tool Databasus</title>
      <dc:creator>Declan Leroy</dc:creator>
      <pubDate>Wed, 13 May 2026 11:30:12 +0000</pubDate>
      <link>https://dev.to/declan_letoy/engineering-and-security-approaches-used-in-open-source-postgresql-backup-tool-databasus-c63</link>
      <guid>https://dev.to/declan_letoy/engineering-and-security-approaches-used-in-open-source-postgresql-backup-tool-databasus-c63</guid>
      <description>&lt;h1&gt;
  
  
  Engineering and security approaches used in open-source PostgreSQL backup tool Databasus
&lt;/h1&gt;

&lt;p&gt;A backup tool is a high-value target. It holds database credentials, it holds full restoreable copies of production data and it usually holds the encryption keys that protect the rest. If any of those slip, the blast radius is the entire database. So the engineering bar for a tool like this is not the same as for an internal admin panel that nobody outside the team will ever talk to.&lt;/p&gt;

&lt;p&gt;Databasus is an open-source industry standard for PostgreSQL backup tools. The project has crossed 500,000+ Docker pulls, around 7,000 GitHub stars and roughly 30 contributors at the time of writing, and the security pipeline below is what supports that scale. None of it is exotic. What's worth showing is how the pieces fit together, because for sensitive software no single check is enough on its own.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1txoodtzr47su3tev2ab.png" class="article-body-image-wrapper"&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.amazonaws.com%2Fuploads%2Farticles%2F1txoodtzr47su3tev2ab.png" alt="Open source security" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why one security check is never enough
&lt;/h2&gt;

&lt;p&gt;Every scanner has blind spots. CodeQL catches a class of bugs that secret scanners ignore, and secret scanners catch leaks that semantic analysis would never look for. Container scanners look at compiled layers, dependency scanners look at the supply chain and unit tests look at logic. The layers overlap on purpose so that a false negative in one pass gets caught by the next.&lt;/p&gt;

&lt;p&gt;That's the whole argument: defence in depth means you assume each tool will miss something, and you stack tools whose blind spots don't overlap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static analysis on every pull request
&lt;/h2&gt;

&lt;p&gt;Static analysis is the cheapest place to catch a security bug. It runs before any human reviewer reads the diff, it runs on every PR and it doesn't get tired on the fiftieth review of the week. Databasus runs several independent passes so that a miss in one engine has a good chance of getting caught by another. They look at different things, which is the whole point.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CodeQL — full security-extended query suite over Go, JavaScript / TypeScript and GitHub Actions code. Runs on every PR and on a weekly schedule.&lt;/li&gt;
&lt;li&gt;CodeRabbit — per-PR review pass that flags logic bugs, suspect patterns and style drift before a human reviewer opens the diff.&lt;/li&gt;
&lt;li&gt;gitleaks — secret scanning over the diff. Catches credentials and tokens accidentally committed.&lt;/li&gt;
&lt;li&gt;semgrep — custom security rules over the diff. Cheap to extend when a new pattern needs to be banned project-wide.&lt;/li&gt;
&lt;li&gt;Codex Security from OpenAI — a separate program that runs deeper, periodic audits over the whole codebase. It catches architectural and cross-cutting issues that narrow per-PR scans tend to miss.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Per-PR scans and periodic deep audits answer different questions, so both have to exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependency hygiene with a deliberate cooldown
&lt;/h2&gt;

&lt;p&gt;The dependency surface is its own threat model. Most projects that got compromised in the last two years were not compromised through their own code. They pulled in a transitively dependent package that got hijacked, and the malicious version shipped to production within hours of being published. Databasus treats this as a separate problem and uses Dependabot together with the Dependency Review Action, plus a deliberate cooldown so that newly published versions have time to be inspected by the wider community before the project adopts them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ecosystem&lt;/th&gt;
&lt;th&gt;Tracked by&lt;/th&gt;
&lt;th&gt;Cooldown&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Go modules (backend, agent)&lt;/td&gt;
&lt;td&gt;Dependabot&lt;/td&gt;
&lt;td&gt;3 days patch / 7 days minor / 30 days major&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;npm (frontend)&lt;/td&gt;
&lt;td&gt;Dependabot&lt;/td&gt;
&lt;td&gt;3 days patch / 7 days minor / 30 days major&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker base images&lt;/td&gt;
&lt;td&gt;Dependabot&lt;/td&gt;
&lt;td&gt;7 days patch / 7 days minor / 30 days major&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Actions&lt;/td&gt;
&lt;td&gt;Dependabot&lt;/td&gt;
&lt;td&gt;7 days minor / 30 days major&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On top of the cooldown, the Dependency Review Action blocks any pull request that introduces a HIGH or CRITICAL CVE before it can be merged. So a fresh CVE in a transitive dependency doesn't have a path into the codebase even if a contributor opens a PR that pulls it in by accident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Containers and the CI supply chain
&lt;/h2&gt;

&lt;p&gt;The container image and the CI workflows are part of the attack surface even though they're not application code. A poisoned base image, a misconfigured Dockerfile or a malicious GitHub Action all give an attacker code execution inside the build, with the same access as the build itself. The Databasus pipeline locks these down with the same seriousness as the application code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trivy scans the built container image on every build for vulnerable layers and known CVEs in installed packages.&lt;/li&gt;
&lt;li&gt;A separate Trivy pass scans the Dockerfile itself for misconfigurations before the image is even built.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;.trivyignore&lt;/code&gt; file is explicit and documented. DS-0002 (the "container runs as root" rule) is suppressed because the entrypoint legitimately starts as root to handle PUID / PGID remap and volume chown for NAS deployments, then drops to the unprivileged &lt;code&gt;databasus&lt;/code&gt; user via &lt;code&gt;gosu&lt;/code&gt; before running the app.&lt;/li&gt;
&lt;li&gt;All third-party GitHub Actions are pinned to full commit SHAs with a &lt;code&gt;# vX.Y.Z&lt;/code&gt; tag comment. For example: &lt;code&gt;actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5  # v4.3.1&lt;/code&gt;. Floating tags like &lt;code&gt;@v4&lt;/code&gt; or &lt;code&gt;@main&lt;/code&gt; are forbidden.&lt;/li&gt;
&lt;li&gt;Workflows default to top-level &lt;code&gt;permissions: contents: read&lt;/code&gt;. Any job that needs more is elevated explicitly and only for that job.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2025 saw several successful attacks against floating Action tags, which is why pinning is a hard requirement in the project rather than a style preference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests that prove a backup can actually be restored
&lt;/h2&gt;

&lt;p&gt;For a backup tool, "the backup completed successfully" and "the backup is actually restoreable" are two different statements. Plenty of historical incidents come down to teams that watched green checkmarks for years and then found out the dumps were unreadable on the day they needed them. So Databasus tests the recovery path directly. The &lt;code&gt;e2e-agent-backup-restore&lt;/code&gt; job runs a full backup-then-restore cycle on every pull request, against real PostgreSQL containers, on a matrix of every supported major version: 15, 16, 17 and 18.&lt;/p&gt;

&lt;p&gt;The same approach covers MySQL, MariaDB and MongoDB on their own matrices, against real engine containers rather than mocks. A release ships only if every supported engine version on the matrix can restore a backup cleanly. If a refactor breaks the restore path for PostgreSQL 15 only, the release is blocked even though the other three versions still work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Runtime hardening in the application itself
&lt;/h2&gt;

&lt;p&gt;CI catches what's wrong with the code before it ships. Runtime hardening is what the code does once it's running, when there's actual data on the line. Databasus encrypts backup contents with AES-256-GCM at rest, which means a stolen backup blob is useless without the key. This is what makes it safe to push encrypted backups to S3, Google Drive or any other shared storage without trusting the storage provider with anything readable.&lt;/p&gt;

&lt;p&gt;Secrets follow the same rule. Database passwords and storage credentials are encrypted in the project's own database and are never logged. Redaction happens in the logger layer, not at call sites, because call sites forget and the logger doesn't. The default database user for backup work is read-only, so even a compromised Databasus instance has a hard time mutating the source database. And the encrypted blobs on storage can be decrypted and restored without Databasus itself if you keep the secret key, which means there's no vendor lock-in even to an open-source tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full picture in one place
&lt;/h2&gt;

&lt;p&gt;The previous sections describe each layer in isolation. The table below puts them next to each other so it's easier to see what each defence is responsible for and when it runs. Reading it in one go makes the overlap visible, which is the part that does the actual work.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Defence&lt;/th&gt;
&lt;th&gt;What it catches&lt;/th&gt;
&lt;th&gt;When it runs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CodeQL&lt;/td&gt;
&lt;td&gt;Code-level security issues in Go, JS / TS, Actions&lt;/td&gt;
&lt;td&gt;Every PR plus weekly schedule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CodeRabbit&lt;/td&gt;
&lt;td&gt;Review-time issues, style, logic bugs&lt;/td&gt;
&lt;td&gt;Every PR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gitleaks&lt;/td&gt;
&lt;td&gt;Leaked credentials in diffs&lt;/td&gt;
&lt;td&gt;Every PR (via CodeRabbit)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;semgrep&lt;/td&gt;
&lt;td&gt;Custom security rule violations&lt;/td&gt;
&lt;td&gt;Every PR (via CodeRabbit)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex Security&lt;/td&gt;
&lt;td&gt;Cross-cutting, architectural issues&lt;/td&gt;
&lt;td&gt;Periodic deep audits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependabot&lt;/td&gt;
&lt;td&gt;New CVEs in dependencies&lt;/td&gt;
&lt;td&gt;On advisory publication (with cooldown)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency Review Action&lt;/td&gt;
&lt;td&gt;HIGH / CRITICAL CVEs introduced in a PR&lt;/td&gt;
&lt;td&gt;Every PR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trivy (image)&lt;/td&gt;
&lt;td&gt;Vulnerable layers in the built image&lt;/td&gt;
&lt;td&gt;Every image build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trivy (Dockerfile)&lt;/td&gt;
&lt;td&gt;Dockerfile misconfigurations&lt;/td&gt;
&lt;td&gt;Every PR touching the Dockerfile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backup-restore e2e&lt;/td&gt;
&lt;td&gt;Backups that can't actually be restored&lt;/td&gt;
&lt;td&gt;Every PR, all supported engine versions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lint, type-check and tests&lt;/td&gt;
&lt;td&gt;Regressions, type errors, style drift&lt;/td&gt;
&lt;td&gt;Every PR&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No single row would be enough on its own. The overlap between rows is what makes a missed bug recoverable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vulnerability disclosure
&lt;/h2&gt;

&lt;p&gt;Even with all of the above, something will eventually slip. So the disclosure path matters as much as the prevention path. Databasus uses a &lt;code&gt;SECURITY.md&lt;/code&gt; file with GitHub Security Advisories as the primary channel, an acknowledgement window of 48 to 72 hours and a severity-dependent fix timeline. Security reports sit at the top of the work queue and pre-empt feature work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take from this if you build something sensitive
&lt;/h2&gt;

&lt;p&gt;The point of writing all this down is not to advertise a feature list. It's that the same playbook applies to any project that handles credentials, secrets or production data, regardless of language or stack. None of these techniques are specific to PostgreSQL or to Go. The takeaways below are the ones that would help most.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Layer overlapping scanners so that one false negative doesn't reach production. CodeQL plus secret scanning plus dependency scanning plus container scanning is the cheapest layered setup available today.&lt;/li&gt;
&lt;li&gt;Treat the dependency surface as a separate threat model. Add a cooldown on new versions, and block HIGH or CRITICAL CVEs at PR time so that they cannot reach the main branch by mistake.&lt;/li&gt;
&lt;li&gt;Pin every third-party GitHub Action to a full commit SHA with a comment showing the human-readable version. Floating tags were exploited at scale in 2025.&lt;/li&gt;
&lt;li&gt;For anything stateful, test the recovery path against real engines, not mocked ones. Mocks confirm your assumptions about the engine, which is exactly what fails in a real incident.&lt;/li&gt;
&lt;li&gt;Redact at the logger, not at call sites. Call sites get refactored and someone always forgets the one place. The logger is one piece of code that gets updated once.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A backup tool is the kind of software where the engineering bar shows up directly in user trust. That's the same engineering bar that makes Databasus a credible choice for &lt;a href="https://databasus.com" rel="noopener noreferrer"&gt;PostgreSQL backup&lt;/a&gt; at the scale it operates today.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>security</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
