<?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/">
  <channel>
    <title>Rclone on Code Genos</title>
    <link>https://codegenos.github.io/tags/rclone/</link>
    <description>Recent content in Rclone on Code Genos</description>
    <image>
      <title>Code Genos</title>
      <url>https://codegenos.github.io/%3Clink%20or%20path%20of%20image%20for%20opengraph,%20twitter-cards%3E</url>
      <link>https://codegenos.github.io/%3Clink%20or%20path%20of%20image%20for%20opengraph,%20twitter-cards%3E</link>
    </image>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Wed, 08 Jul 2026 21:40:00 +0300</lastBuildDate>
    <atom:link href="https://codegenos.github.io/tags/rclone/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Automated Docker Database Backups on Raspberry Pi: An Orchestration Guide using Backrest, Restic, and Rclone</title>
      <link>https://codegenos.github.io/posts/automated-docker-database-backups-raspberry-pi-backrest-restic-rclone/</link>
      <pubDate>Wed, 08 Jul 2026 21:40:00 +0300</pubDate>
      <guid>https://codegenos.github.io/posts/automated-docker-database-backups-raspberry-pi-backrest-restic-rclone/</guid>
      <description>A step-by-step guide to automating docker database backups on a self-hosted Raspberry Pi using Backrest for orchestration, Restic for deduplication and encryption, and Rclone to ship snapshots to Google Drive.</description>
      <content:encoded><![CDATA[<p>A Raspberry Pi home lab can run a surprising number of workloads: automation servers, media managers, self-hosted apps, and more. Most of them share one critical dependency — a database; PostgreSQL or MongoDB instances quietly grows months of data, and the failure mode can be: a dead SD card, a failed OS upgrade, or an accidental <code>docker volume rm</code>.</p>
<p>This guide solves the problem that is explained above. We will build a fully automated, encrypted, deduplicated backup pipeline that:</p>
<ol>
<li>Triggers <strong>logical database dumps</strong> (not raw volume snapshots) before each backup run.</li>
<li>Stores those dumps in a <strong>Restic repository</strong> for deduplication and client-side encryption.</li>
<li>Replicates the repository to <strong>Google Drive</strong> via Rclone.</li>
<li>Orchestrates the whole process through <strong>Backrest</strong> — a self-hosted UI wrapper around Restic that runs as a Docker container.</li>
</ol>
<p>The result is a &ldquo;set it and forget it&rdquo; system that you can verify, test restores from, and extend to any additional services in your stack.</p>
<hr>
<h2 id="architecture-overview">Architecture Overview</h2>
<h3 id="the-three-layer-stack">The Three-Layer Stack</h3>
<p><strong>Restic</strong> is the core backup engine. It handles deduplication (so consecutive daily snapshots do not waste space storing identical file chunks), client-side AES-256 encryption, and snapshot management. Every backup produces an immutable, verifiable snapshot.</p>
<p><strong>Rclone</strong> is a Swiss-army-knife command-line cloud transfer tool. It speaks over 40 storage protocols natively, including Google Drive. Restic integrates with Rclone as a backend, which means Restic handles all encryption locally before a single byte is sent to the cloud. Then Rclone send the data to the cloud storage.</p>
<p><strong>Backrest</strong> is a web UI and scheduler built on top of Restic. Instead of managing cron jobs and shell scripts manually, you configure plans, retention policies, and hooks through a dashboard. Critically for this use case, Backrest supports <strong>pre-backup and post-backup hooks</strong> — arbitrary shell commands that run inside the Backrest container (or via <code>docker exec</code> into a sibling container) before Restic starts the snapshot. This is the mechanism we use to generate consistent database dumps.</p>
<h3 id="conceptual-data-flow">Conceptual Data Flow</h3>
<pre tabindex="0"><code>┌─────────────────────────────────────────────────────┐
│  Backrest Scheduler (cron)                          │
│                                                     │
│  1. Pre-Backup Hook (CONDITION_SNAPSHOT_START)      │
│     └─ sh -c &#34;docker exec -e PGPASSWORD=&#39;***&#39;       |
|           -t container-name pg_dump -Fp             |
|           -U user dbname &gt; /userdata/postgres.sql&#34;  │
│     └─ sh -c &#34;docker exec container-name mongodump  |
|           --archive --username user --password ***  |
|           --authenticationDatabase admin            |
|           &gt; /userdata/mongo-backup.archive&#34;         │
│                                                     │
│  2. Restic Snapshot                                 │
│     └─ restic backup /userdata                      │
│        (deduplication + AES-256 encryption)         │
│                                                     │
│  3. Restic → Rclone backend → Google Drive          │
│     └─ rclone:gdrive:restic-backups                 │
└─────────────────────────────────────────────────────┘
</code></pre><p>The backup volume (<code>/userdata</code>) is a temporary staging area mounted into the Backrest container. Hooks write dump files there; Restic snapshots it. Because Restic deduplicates at the chunk level, even large databases result in efficient incremental uploads after the first run.</p>
<hr>
<h2 id="why-logical-dumps-instead-of-volume-snapshots">Why Logical Dumps Instead of Volume Snapshots?</h2>
<p>A <strong>file-level snapshot</strong> of a running database volume is not a consistent backup. PostgreSQL and MongoDB both maintain in-memory state and use write-ahead logs. Copying raw files while the database engine is writing will produce a snapshot that may be internally inconsistent — it can fail to restore, or restore to a corrupted state. You would only discover this on the day you need it most.</p>
<p>A <strong>logical dump</strong> (<code>pg_dump</code>, <code>mongodump</code>) is a backup of the <em>logical content</em> of the database. The database engine serializes its own state into a portable, self-consistent archive. This is the safe path for production databases, and it is what <code>pg_dump</code> and <code>mongodump --archive</code> produce. The resulting files are also database-version-portable, making migrations easier.</p>
<p>Use file-level volume snapshots for stateless application data (configuration files, static assets). Use logical dumps for databases.</p>
<hr>
<h2 id="step-1-rclone-configuration--google-drive-remote">Step 1: Rclone Configuration — Google Drive Remote</h2>
<p>Backrest needs an Rclone configuration to know where to send snapshots. Configure Rclone first so the path is ready when you deploy Backrest.</p>
<h3 id="option-a-oauth-interactive-simpler">Option A: OAuth (Interactive, Simpler)</h3>
<p>On a machine with a browser (your laptop, not the Pi), run:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">rclone config
</span></span></code></pre></div><p>Follow the prompts:</p>
<pre tabindex="0"><code>n) New remote
name&gt; gdrive
Storage&gt; drive          # Google Drive
client_id&gt;              # Leave blank to use Rclone&#39;s shared credentials
client_secret&gt;          # Leave blank
scope&gt; drive.file       # IMPORTANT: least-privilege scope
root_folder_id&gt;         # Leave blank
service_account_file&gt;   # Leave blank for OAuth
Edit advanced config?&gt; n
Use auto config?&gt; y     # Opens browser for OAuth consent
</code></pre><blockquote>
<p><strong>Scope: <code>drive.file</code></strong> — This scope restricts Rclone to only files it creates. It cannot read, modify, or delete any other files in your Drive. Always prefer this over the full <code>drive</code> scope.</p></blockquote>
<p>Copy the resulting <code>rclone.conf</code> to your Pi:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">scp ~/.config/rclone/rclone.conf pi@raspberrypi:~/backrest/rclone/rclone.conf
</span></span></code></pre></div><h3 id="option-b-service-account-headless-recommended-for-a-pi">Option B: Service Account (Headless, Recommended for a Pi)</h3>
<p>For a truly unattended setup, a Google Cloud <strong>Service Account</strong> eliminates the need for OAuth tokens that expire or require re-authorization.</p>
<ol>
<li>Create a project in <a href="https://console.cloud.google.com/">Google Cloud Console</a>.</li>
<li>Enable the <strong>Google Drive API</strong>.</li>
<li>Create a Service Account, download the JSON key file to your Pi.</li>
<li>Share a specific Google Drive folder with the service account&rsquo;s email address (e.g., <code>backup-agent@your-project.iam.gserviceaccount.com</code>).</li>
</ol>
<p>In <code>rclone.conf</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[gdrive]</span>
</span></span><span class="line"><span class="cl"><span class="na">type</span> <span class="o">=</span> <span class="s">drive</span>
</span></span><span class="line"><span class="cl"><span class="na">scope</span> <span class="o">=</span> <span class="s">drive.file</span>
</span></span><span class="line"><span class="cl"><span class="na">service_account_file</span> <span class="o">=</span> <span class="s">/config/rclone/service-account.json</span>
</span></span><span class="line"><span class="cl"><span class="na">root_folder_id</span> <span class="o">=</span> <span class="s">&lt;folder-id-from-drive-url&gt;</span>
</span></span></code></pre></div><p>The Service Account approach is superior for a headless Pi: no browser required, no token refresh failures.</p>
<h3 id="verify-the-remote">Verify the Remote</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">rclone lsd gdrive:
</span></span></code></pre></div><p>This should return an empty listing (or the contents of the shared folder if you created any). A working Rclone remote is the prerequisite for all remaining steps.</p>
<hr>
<h2 id="step-2-backrest-deployment">Step 2: Backrest Deployment</h2>
<p>Backrest runs as a Docker container. The <code>docker-compose.yml</code> below is designed for an ARM64 Raspberry Pi running Docker in rootless or standard mode.</p>
<p>Create the following directory structure on your Pi:</p>
<pre tabindex="0"><code>/root/backrest/
├── docker-compose.yml
├── config/              # Backrest persistent config (repo keys, plans)
├── cache/               # Restic cache (speeds up subsequent operations)
└── rclone/
    └── rclone.conf      # (or service-account.json if using SA)
</code></pre><p><strong><code>docker-compose.yml</code>:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">backrest</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/garethgeorge/backrest:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">backrest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">backrest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./backrest/data:/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./backrest/config:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./backrest/cache:/cache</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./backrest/tmp:/tmp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/root/backrest/rclone:/root/.config/rclone</span><span class="w"> </span><span class="c"># Mount for rclone config (needed when using rclone remotes)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/root/backrest/data:/userdata </span><span class="w"> </span><span class="c"># Mount local paths to backup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/root/backrest/repos:/repos    </span><span class="w"> </span><span class="c"># Mount local repos (optional for remote storage)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># The &#34;God Mode&#34; connection to run commands inside other containers</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/var/run/docker.sock:/var/run/docker.sock</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">BACKREST_DATA=/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">BACKREST_CONFIG=/config/config.json</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">XDG_CACHE_HOME=/cache</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">TMPDIR=/tmp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">TZ=Europe/Istanbul</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;9898:9898&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span></code></pre></div><p><strong>Why mount the Docker socket?</strong></p>
<p>The pre-backup hooks run inside the Backrest container. To trigger <code>pg_dump</code> inside a separate <code>postgres</code> container, the hook must be able to call <code>docker exec</code>. Mounting <code>/var/run/docker.sock</code> grants the Backrest container access to the host Docker daemon.</p>
<blockquote>
<p><strong>Security note:</strong> Mounting the Docker socket is equivalent to granting root access to the host. Ensure the Backrest UI is never exposed to the public internet without authentication. Run it behind a reverse proxy with strong credentials, or bind it exclusively to <code>127.0.0.1</code>.</p></blockquote>
<p>Start the stack:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> /root/backrest
</span></span><span class="line"><span class="cl">docker compose up -d
</span></span><span class="line"><span class="cl">docker compose logs -f backrest
</span></span></code></pre></div><p>Access the Backrest UI at <code>http://raspberrypi:9898</code> to complete initial setup.</p>
<hr>
<h2 id="step-3-configuring-the-restic-repository-in-backrest">Step 3: Configuring the Restic Repository in Backrest</h2>
<p>In the Backrest UI, navigate to <strong>Repositories → Add Repository</strong>.</p>
<table>
  <thead>
      <tr>
          <th>Field</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Repository URI</td>
          <td><code>rclone:gdrive:restic-backups</code></td>
      </tr>
      <tr>
          <td>Password</td>
          <td>A strong, randomly generated password</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p><strong>Critical:</strong> Save the repository password somewhere outside the backup system itself — a password manager, a printed copy in a safe. If you lose this password, the repository contents are unrecoverable. This is the correct trade-off: the encryption means Google (or anyone who accesses your Drive) cannot read your data.</p></blockquote>
<p>Backrest will call <code>restic init</code> on first save, initializing the repository structure in Google Drive.</p>
<p><strong>ARM64 Performance Note:</strong> The initial backup of a large Restic repository over Google Drive can be slow on a Pi&rsquo;s CPU due to encryption overhead. Subsequent incremental backups are fast because deduplication means only changed chunks are uploaded. Plan the first run to occur overnight.</p>
<hr>
<h2 id="step-4-implementing-pre-backup-hooks">Step 4: Implementing Pre-Backup Hooks</h2>
<p>This is the most operationally important part of the guide. Hooks run before Restic starts snapshotting, ensuring the dump files in <code>/userdata</code> are fresh and consistent every time.</p>
<p>In the Backrest UI, navigate to your plan and add <strong>Pre-Backup Hooks</strong>. Choose <code>CONDITION_SNAPSHOT_START</code> from dropdown.</p>
<h3 id="postgresql--pg_dump">PostgreSQL — <code>pg_dump</code></h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sh -c <span class="s2">&#34;docker exec -e PGPASSWORD=&#39;***&#39; -t container-name \
</span></span></span><span class="line"><span class="cl"><span class="s2">      pg_dump -Fp \
</span></span></span><span class="line"><span class="cl"><span class="s2">      -U user \
</span></span></span><span class="line"><span class="cl"><span class="s2">      dbname &gt; /userdata/postgres.sql&#34;</span>     
</span></span></code></pre></div><blockquote>
<p><strong>Tip: Use <code>-Fc</code> for larger databases.</strong> The plain text format (<code>-Fp</code>) is human-readable and portable, but the custom binary format (<code>-Fc</code>) offers two practical benefits for this backup pipeline:</p>
<p><strong>Restore performance</strong> — <code>-Fc</code> dumps support parallel restore via <code>pg_restore -j $(nproc)</code>. On a Pi&rsquo;s limited CPU, this can cut restore time by hours for multi-GB databases.</p>
<p>To switch, replace <code>pg_dump -Fp</code> with <code>pg_dump -Fc</code> and use <code>pg_restore</code> instead of <code>psql</code> during restoration (see the restore section for reference).</p></blockquote>
<h3 id="mongodb--mongodump-with-archive-format">MongoDB — <code>mongodump</code> with Archive Format</h3>
<p>The <code>--archive</code> flag streams the dump to a single file, which is far more efficient to snapshot than a directory of BSON files.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sh -c <span class="s2">&#34;docker exec container-name \
</span></span></span><span class="line"><span class="cl"><span class="s2">      mongodump --archive \
</span></span></span><span class="line"><span class="cl"><span class="s2">      --username user \
</span></span></span><span class="line"><span class="cl"><span class="s2">      --password *** \
</span></span></span><span class="line"><span class="cl"><span class="s2">      --authenticationDatabase admin \
</span></span></span><span class="line"><span class="cl"><span class="s2">      &gt; /userdata/mongo-backup.archive&#34;</span>
</span></span></code></pre></div><h3 id="verifying-hook-execution">Verifying Hook Execution</h3>
<p>After running a plan manually via the UI, check the Backrest task log. A successful hook run will show the echo output from your scripts. If <code>docker exec</code> fails with a permission error, verify that <code>/var/run/docker.sock</code> is mounted and that the Backrest container&rsquo;s user has read/write access to the socket.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># On the Pi host, check socket permissions</span>
</span></span><span class="line"><span class="cl">ls -la /var/run/docker.sock
</span></span><span class="line"><span class="cl"><span class="c1"># Expected: srw-rw---- 1 root docker ...</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Backrest container must run as root or a user in the docker group</span>
</span></span></code></pre></div><hr>
<h2 id="step-5-configuring-retention-policy">Step 5: Configuring Retention Policy</h2>
<p>The <strong>Retention Policy</strong> defines the lifecycle of your snapshots, dictating which backups should be preserved and which should be marked as obsolete.</p>
<p>You can configure this within your <strong>Backup Plan</strong> settings. Depending on your needs, you can choose between:</p>
<ul>
<li><strong>By Count</strong>: Ideal for maintaining a specific number of recent versions.</li>
<li><strong>By Time Period</strong>: Allows for more complex granular retention, such as keeping daily backups for two weeks, weekly backups for two months, and monthly backups for half a year.</li>
</ul>
<hr>
<h2 id="step-6-validating-backups-and-performing-a-restore">Step 6: Validating Backups and Performing a Restore</h2>
<p>A backup you have never tested is not a backup. Build restore verification into your routine.</p>
<h3 id="checking-repository-integrity">Checking Repository Integrity</h3>
<p>Restic&rsquo;s <code>check</code> command verifies the repository&rsquo;s structural integrity and optionally reads a subset of data to confirm no corruption has occurred during upload.</p>
<p>From the Backrest UI, you can trigger a <strong>Check</strong> operation directly. Alternatively, from the command line:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker <span class="nb">exec</span> backrest restic <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  -r rclone:gdrive:restic-backups <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  check --read-data-subset<span class="o">=</span>10%
</span></span></code></pre></div><p><code>--read-data-subset=10%</code> reads a random 10% of pack files each run. Over 10 runs you have statistically verified the entire repository without the cost of downloading everything each time. This is the right cadence for a Pi on a home internet connection.</p>
<h3 id="restoring-a-postgresql-database">Restoring a PostgreSQL Database</h3>
<p><strong>Step 1: List available snapshots</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker <span class="nb">exec</span> backrest restic <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  -r rclone:gdrive:restic-backups <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  snapshots
</span></span></code></pre></div><p><strong>Step 2: Restore the sql dump file from a specific snapshot</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker <span class="nb">exec</span> backrest restic <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  -r rclone:gdrive:restic-backups <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  restore &lt;snapshot-id&gt; <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --target /tmp/restore <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --include /backup/postgres/
</span></span></code></pre></div><p><strong>Step 3: Load the sql dump into PostgreSQL</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Copy the restored sql dump into the running postgres container</span>
</span></span><span class="line"><span class="cl">docker cp /tmp/restore/backup/postgres/mydb_&lt;timestamp&gt;.sql <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  postgres_container:/tmp/mydb_restore.sql
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Restore using pg_restore</span>
</span></span><span class="line"><span class="cl">docker <span class="nb">exec</span> postgres_container <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  psql <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -U postgres <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -d mydb_restored <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -f /tmp/mydb_restore.sql
</span></span></code></pre></div><h3 id="restoring-a-mongodb-database">Restoring a MongoDB Database</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Restore the archive from the snapshot</span>
</span></span><span class="line"><span class="cl">docker <span class="nb">exec</span> backrest restic <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  -r rclone:gdrive:restic-backups <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  restore &lt;snapshot-id&gt; <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --target /tmp/restore <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --include /backup/mongo/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Load the archive into MongoDB</span>
</span></span><span class="line"><span class="cl">docker cp /tmp/restore/backup/mongo/mongo_&lt;timestamp&gt;.archive <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  mongo_container:/tmp/mongo_restore.archive
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">docker <span class="nb">exec</span> mongo_container <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  mongorestore <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --username<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$MONGO_INITDB_ROOT_USERNAME</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --password<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$MONGO_INITDB_ROOT_PASSWORD</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --authenticationDatabase<span class="o">=</span>admin <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --archive<span class="o">=</span>/tmp/mongo_restore.archive <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --drop   <span class="c1"># drops existing collections before restoring — omit if you want a merge</span>
</span></span></code></pre></div><hr>
<h2 id="best-practices-and-considerations">Best Practices and Considerations</h2>
<h3 id="keep-encryption-keys-separate-from-the-backup-destination">Keep Encryption Keys Separate from the Backup Destination</h3>
<p>Your Restic repository password is the only thing protecting your encrypted backups. If it is stored in the same Google Drive account where the repository lives, an attacker who compromises the account has both ciphertext and key. Store the repository password in:</p>
<ul>
<li>A dedicated password manager (Bitwarden, 1Password).</li>
<li>A separate encrypted file stored offline.</li>
<li>A secrets manager if you are integrating this into a larger infrastructure.</li>
</ul>
<p>Never commit the password to a Git repository, even a private one.</p>
<h3 id="use-separate-google-drive-accounts-for-backup">Use Separate Google Drive Accounts for Backup</h3>
<p>The Service Account approach (Option B) naturally enforces isolation: the service account has access only to the specific folder you shared with it. Your primary Google account is not involved in the backup process and cannot be used to access the repository even if compromised.</p>
<h3 id="alerting-on-failure">Alerting on Failure</h3>
<p>Backrest supports post-backup hooks that run conditionally on failure. Configure one to send a notification (via a webhook to a Slack channel, n8n workflow, or ntfy instance) when a backup plan fails:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/sh
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="c1"># Post-backup failure hook</span>
</span></span><span class="line"><span class="cl">curl -s -X POST <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  -H <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  -d <span class="s2">&#34;{\&#34;text\&#34;: \&#34;Backup FAILED on </span><span class="k">$(</span>hostname<span class="k">)</span><span class="s2"> at </span><span class="k">$(</span>date<span class="k">)</span><span class="s2">\&#34;}&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  <span class="s2">&#34;</span><span class="nv">$SLACK_WEBHOOK_URL</span><span class="s2">&#34;</span>
</span></span></code></pre></div><p>Silently failing backups are the most dangerous failure mode. Alerting converts silent failures into actionable notifications.</p>
<hr>
<h2 id="conclusion">Conclusion</h2>
<p>A Raspberry Pi home lab is a genuinely capable platform for running production-like workloads, but it demands the same operational discipline as a cloud environment — arguably more, given the hardware&rsquo;s failure-proneness.</p>
<p>The pipeline described here — Backrest orchestrating pre-backup hooks that generate logical database dumps, Restic snapshotting and encrypting those dumps, and Rclone shipping them to Google Drive — delivers the properties that make a backup system trustworthy:</p>
<ul>
<li><strong>Consistency:</strong> Logical dumps are always internally consistent, regardless of database write activity.</li>
<li><strong>Confidentiality:</strong> Data is encrypted client-side before leaving the Pi. Google cannot read it.</li>
<li><strong>Efficiency:</strong> Restic deduplication means daily backups of a 500 MB database do not consume 500 MB × 365 days of Drive quota.</li>
<li><strong>Verifiability:</strong> <code>restic check</code> and periodic test restores confirm the backup is actually usable.</li>
<li><strong>Auditability:</strong> Backrest&rsquo;s task history gives you a complete log of every backup run, hook output, and error.</li>
</ul>
<p>Once this is running, the operational overhead drops to near zero. Review the Backrest dashboard weekly, run a test restore monthly, and rotate your repository password annually. The rest is automated.</p>
<p>Your databases are safe. You can sleep.</p>
<hr>
<h2 id="references">References</h2>
<ul>
<li>Restic Design Documentation: <a href="https://restic.readthedocs.io/en/latest/100_references.html">https://restic.readthedocs.io/en/latest/100_references.html</a></li>
<li>Rclone: <a href="https://rclone.org/">https://rclone.org/</a></li>
<li>Rclone Google Drive: <a href="https://rclone.org/drive/">https://rclone.org/drive/</a></li>
<li>Backrest Docs: <a href="https://garethgeorge.github.io/backrest/introduction/getting-started">https://garethgeorge.github.io/backrest/introduction/getting-started</a></li>
</ul>
]]></content:encoded>
    </item>
  </channel>
</rss>
