Skip to content

Storage Strategy

Drive layout, backup topology, and data protection across all hosts.


Drive Inventory

NAS (ASUS P8H77-I — 4 SATA ports)

Device Model Size Mount Role Status

| sda | Lexar NQ100 SSD | 240GB | / (LVM) | OS, Docker data-root (/data/docker) | Healthy | | sdb | WD Purple (WD23PURZ) | 2TB | /mnt/purple | Frigate recordings, Restic repos | 98% full | | sdc | WD Red Plus (WD80EFBX) | 8TB | /mnt/red8 (planned) | Media, downloads, family data, service data | Recovery pending | | sdd | Generic SSD | 512GB | /mnt/backup-ssd (planned) | Critical data backup (family photos/videos) | Unmounted | | sde | Generic Flash Disk | 3.7GB | /boot/efi, /boot | Boot USB (permanent) | Healthy |

Docker VM (VM 101)

Disk Size Role

| virtio0 | 100GB | Debian OS, Docker volumes, container data |

VPS (Vultr)

Disk Size Role

| NVMe | 32GB | OS, Headscale DB, container data |

Spare Drives (at home)

Drive Size Interface Location

| WD Red 3TB (#1) | 3TB | SATA | Sabrent USB dock | | WD Red 3TB (#2) | 3TB | SATA | Spare | | Old drive | 1TB | SATA | Spare | | Old drive | 2TB | SATA | Spare |


Storage Architecture

NAS Filesystem Layout

NAS Drives
├── sda (240GB SSD) ─── LVM root (/)
│   ├── /data/docker/          # Docker data-root (images, volumes)
│   ├── /srv/forgejo/          # Forgejo git data
│   └── /data/coolify/         # Coolify PaaS data
├── sdb (2TB Purple) ─── /mnt/purple
│   ├── /mnt/purple/frigate/          # Frigate NVR recordings
│   ├── /mnt/purple/backup/restic/    # Restic REST server data
│   └── /mnt/purple/red-recovery/     # 8TB recovery data (temporary, 1.5TB)
├── sdc (8TB Red) ─── /mnt/red8 (after recovery)
│   ├── /mnt/red8/media/        # Music, videos, BD rips
│   ├── /mnt/red8/downloads/    # *arr stack downloads
│   ├── /mnt/red8/data/         # Family photos/videos, old backups
│   ├── /mnt/red8/sync/         # Syncthing data
│   └── /mnt/red8/backup/       # Service backups
└── sdd (512GB SSD) ─── /mnt/backup-ssd (after recovery)
    ├── /mnt/backup-ssd/family-videos/   # 45GB (irreplaceable)
    └── /mnt/backup-ssd/family-photos/   # 168GB (irreplaceable)

Services access data through /srv/ symlinks, decoupling mount points from service paths:

/srv/frigate      → /mnt/purple/frigate
/srv/media        → /mnt/red8/media
/srv/downloads    → /mnt/red8/downloads
/srv/data         → /mnt/red8/data
/srv/sync         → /mnt/red8/sync
/srv/backup       → /mnt/red8/backup

NFS Exports (NAS → Docker VM)

NAS Export Docker VM Mount Used By Mode

| /srv/frigate | /mnt/nas/frigate | Frigate NVR | rw | | /srv/media | /mnt/nas/media | Jellyfin, *arr stack | ro | | /srv/downloads | /mnt/nas/downloads | qBittorrent, *arr stack | rw | | /srv/photos | /mnt/nas/photos | Immich (external library) | ro |

Docker VM fstab entries use defaults,_netdev,nofail to handle NAS unavailability gracefully.


Backup Topology

3-2-1 Strategy

[Docker VM]                    [NAS]                        [Google Drive]
 Live data                      Restic REST                  rclone crypt
 (Docker volumes)               (/mnt/purple/backup/restic)  (gdrive-crypt:homelab/)
     │                               │                            │
     │  restic backup (nightly) ─────┘                            │
     │                                                            │
     │                          offsite-sync (4:30 AM) ───────────┘
     └── Copy 1 (production)    Copy 2 (local backup)        Copy 3 (offsite)

Backup Schedule (PYT — America/Asuncion)

Time Service Container Restic Repo What's Backed Up

| Hourly | Headscale | headscale-backup | Local tar.gz (VPS) | SQLite DB, noise key, config | | 2:00 AM | Vaultwarden | vaultwarden-backup | /augusto/vaultwarden | vaultwarden-data volume | | 2:15 AM | Caddy | caddy-backup | /augusto/caddy | TLS certificates, ACME state | | 2:30 AM | Home Assistant | homeassistant-backup | /augusto/homeassistant | homeassistant-config volume | | 2:45 AM | Pi-hole | pihole-backup | /augusto/pihole | DNS config, blocklists, local DNS entries | | 3:00 AM | Paperless-ngx | paperless-backup | /augusto/paperless | Data + media volumes | | 3:15 AM | Immich | immich-backup | /augusto/immich | PostgreSQL pg_dump (metadata only) | | 3:30 AM | Coolify | coolify-backup | /augusto/coolify | PostgreSQL dumps + SSH keys | | 4:30 AM | Offsite | offsite-sync | — | Restic repos + Headscale → Google Drive |

Retention Policy

All Restic sidecars use the same retention:

Period Keep

| Daily | 7 | | Weekly | 4 | | Monthly | 12 |

Automatic pruning runs after every backup. Weekly integrity checks on Sundays.

Restic REST Server

# NAS — /mnt/purple/backup/restic/
restic-rest:
  image: restic/rest-server:0.14.0
  ports: "8000:8000"
  options: --private-repos --prometheus
  auth: htpasswd (forces /username/ prefix per repo)
  limits: 256M RAM, 0.5 CPU

All Docker VM sidecars connect to rest:http://augusto:<pass>@192.168.0.12:8000/augusto/<service>.

Offsite Sync Details

offsite-sync (NAS container, 4:30 AM)
├── 1. rsync Headscale backups from VPS (SSH, last 48h only)
├── 2. rclone sync Restic repos → gdrive-crypt:homelab/restic/
├── 3. rclone sync Headscale staging → gdrive-crypt:homelab/headscale/
├── 4. ntfy notification (success → cronova-info, failure → cronova-critical)
└── Bandwidth limit: 5 MB/s (RCLONE_BWLIMIT)

Encryption:rclone crypt wraps Google Drive with AES-256. Restore requiresthree secrets: rclone password, rclone salt, and RESTIC_PASSWORD. All stored in Vaultwarden.


Data Criticality

Data Size Irreplaceable Copies Location(s)

| Family videos (2010-2013) | 45GB | Yes | 2 (pending 3) | Purple recovery, 8TB (planned), SSD (planned), GDrive | | Family photos (2006-2014) | 168GB | Yes | 2 (pending 3) | Purple recovery, 8TB (planned), SSD (planned), GDrive | | Vaultwarden DB | ~50MB | Yes (passwords) | 3 | Docker VM, Restic, GDrive | | HA config | ~200MB | Hard to recreate | 3 | Docker VM, Restic, GDrive | | Paperless documents | Variable | Yes (scanned docs) | 3 | Docker VM, Restic, GDrive | | Immich metadata | ~500MB | Yes (tags, albums) | 3 | Docker VM, Restic, GDrive | | Forgejo repos | Variable | Yes (git history) | 3 | NAS, local clones, GitHub mirror | | Frigate recordings | ~158GB | No (7-day retention) | 1 | NAS Purple only | | Music collection | 346GB | No (re-downloadable) | 1 | Purple recovery (pending 8TB) | | Old laptop backups | 473GB | Mostly no | 1 | Purple recovery (pending 8TB) |

Protection Gaps

Gap Risk Mitigation

| ~~Forgejo has no offsite mirror~~ | ~~Single point of failure for git~~ | Resolved — GitHub push mirror active (sync on commit + 8h interval) | | Frigate recordings = 1 copy | Loss if Purple fails | Acceptable — recordings are ephemeral | | Family media pending 8TB recovery | Currently only on 98%-full Purple | Execute recovery plan ASAP | | Encryption keys only in Vaultwarden | Lose Vaultwarden = lose offsite | Paper backup recommended |


Capacity Planning

Current Usage

Drive Total Used Free Pressure

| NAS SSD (sda) | 240GB | ~60GB | ~180GB | Low | | NAS Purple (sdb) | 2TB | 1.7TB | 45GB | Critical | | NAS Red (sdc) | 8TB | — | — | Recovery pending | | NAS SSD (sdd) | 512GB | ~0 | 512GB | Unmounted | | Docker VM | 100GB | ~40GB | ~60GB | Low | | VPS | 32GB | ~10GB | ~22GB | Low |

Post-Recovery Projections

After 8TB recovery and Purple cleanup:

Drive Projected Used Projected Free Growth Rate

| Purple (sdb) | ~170GB | ~1.6TB | ~5GB/week (Frigate) | | Red (sdc) | ~1.5TB | ~5.8TB | Slow (media additions) | | Backup SSD (sdd) | ~213GB | ~263GB | Static (critical data only) |

Purple will sustain Frigate for ~6 years at current recording rates after recovery frees 1.5TB.

Growth Triggers

Trigger When Action

| Purple > 80% | ~2028 | Review Frigate retention or add camera exclusions | | Red > 70% (5.6TB) | Years away | Consider SnapRAID parity with spare 3TB drives | | Restic repos > 500GB | Monitor quarterly | Review retention or move to Red | | Docker VM > 80GB | Monitor | Prune images, review volumes |


Recovery Procedures

Quick Reference

Scenario Recovery Source Procedure RTO

| Single service (VW, HA, Paperless) | Restic REST on NAS | restic restore latest | 15 min | | Docker VM failure | Restic REST + compose files | Rebuild VM, restore volumes | 2-4 hours | | NAS failure | Google Drive + Forgejo | Rebuild Debian, restore from offsite | 4-8 hours | | VPS failure | Local Headscale backup | Rebuild Vultr, restore config | 1 hour | | Complete site failure | Google Drive (rclone crypt) | VPS first, then NAS, then Docker VM | 8-12 hours | | Restic password lost | UNRECOVERABLE | AES-256, no backdoor | N/A |

Full procedures: disaster-recovery.md

Backup verification: backup-test-procedure.md

8TB recovery plan: 8tb-recovery-plan-2026-03-12.md


Backup Scripts

All backup scripts live in docker/shared/backup/:

Script Used By Purpose

| restic-backup.sh | VW, HA, Paperless, Coolify sidecars | Generic Restic backup with retention + integrity | | immich-db-backup.sh | Immich sidecar | pg_dump → compress → Restic | | offsite-sync.sh | offsite-sync container | rsync VPS + rclone to Google Drive | | backup-env.sh | Manual | Collect .env files to /opt/homelab/env-backup/ |


Verification Schedule

Frequency Check Method

| Daily | Backup ran successfully | ntfy notifications (automatic) | | Weekly | Restic integrity | restic check in backup script (automatic) | | Monthly (1st Sunday) | Snapshot freshness | restic snapshots — verify recent entries | | Monthly (1st Sunday) | Test restore | Restore VW, HA, Headscale to temp directory | | Quarterly | Full restore drill | Restore service to test VM, verify functionality |


References