Nightly block-level backup to S3. Zero downtime. MariaDB is in read-only mode, not stopped. Zero config needed, it detects your DB automatically. Restore a complete bootable Pi to new hardware in one command.
curl -sL pi2s3.com/install | bash
boot.pi2s3.com — no SD card, no USB, no media.--post-restore.--parallel.Pick a section — each page covers one part of the story in full detail.
Two shell scripts: one runs nightly on the Pi, one restores everything to new hardware. No agents, no daemons, no cloud accounts beyond AWS.
MariaDB/MySQL: SET GLOBAL read_only=ON for the brief flush window — containers stay up, only writes are blocked. PostgreSQL: a CHECKPOINT, writes never blocked. Detected automatically whether your DB runs in Docker or natively. No DB: Docker is stopped briefly. Either way, crash-recovery makes the restore consistent.
Reads only used blocks from each partition (not empty sectors). 954 GB NVMe at 28% full: partclone reads 267 GB, dd would read 954 GB.
pigz compresses in parallel using all Pi 5 cores. Output streams directly to S3 with no local temp file and no second disk required.
Docker restarts, manifest JSON uploaded, optional push notification via ntfy.sh. SHA256 verification confirms every file in S3.
sfdisk restores the saved GPT layout to the new device. Partitions are recreated exactly: same sizes, same order.
Each partition streams S3 → gunzip → partclone.restore. No local download. Works from any Linux machine attached to the target drive.
Insert storage into the new Pi, power on. Raspberry Pi OS automatically expands the root filesystem on first boot. No extra config.
test-recovery.sh --post-boot checks OS, NVMe, Docker containers, Cloudflare tunnel, cron jobs, MariaDB, and HTTP. PASS/FAIL per check.
dd reads every sector regardless of whether it contains data. partclone reads the filesystem allocation bitmap and skips unallocated blocks. Same result, a fraction of the work.
| dd | partclone | |
|---|---|---|
| What it reads | Every sector (used + empty) | Used blocks only |
| Speed on 954 GB NVMe (28% full) | ~90 min | ~5 min |
| S3 upload size | ~10 GB (compressed zeros) | ~3–5 GB |
| Restore | gunzip | dd | partclone per partition |
| Docker downtime | 60–90 min | 5–15 min |
Because it's a block level image of the full device, there's nothing to configure. Every file, database, container, service, and SSH key is included automatically. Works on both NVMe and SD card with auto detection of boot media.
OS, kernel, packages, and systemd services including cloudflared, custom watchdogs, and any compiled binaries.
All images, volumes, networks, and compose configs. MariaDB data, WordPress uploads, application files: everything in /var/lib/docker.
.env files, config.env, credentials, authorized_keys, cron jobs, and logrotate rules. All restored exactly as is.
config.txt, cmdline.txt, and the full /boot/firmware partition. The restored Pi boots identically to the original.
GPT layout saved separately as a sfdisk dump and applied first on restore. Works across different NVMe sizes.
Custom I/O scheduler settings, udev rules, and performance tuning survive the restore intact.
At 3–5 GB per compressed image using S3 STANDARD_IA. Costs vary by region. af-south-1 (Cape Town) is slightly higher than us-east-1.
| Retention | S3 storage | Monthly cost (STANDARD_IA) |
|---|---|---|
| 7 images | ~25 GB | <$1/month |
| 30 images | ~120 GB | ~$2/month |
| 60 images | ~240 GB | ~$3/month |
S3 lifecycle policy is installed automatically by install.sh --setup. Images beyond MAX_IMAGES (default: 60) are deleted automatically. Switch to GLACIER_IR for long term cold storage at ~80% less cost.