Uptime Kuma moved from esh-docker-vm to ana-docker on 2026-09-22. The pre-backup hook's fallback lookup, `docker ps | grep -E "uptime.kuma"`, then matched nothing and exited 1. Under set -euo pipefail that aborted the hook, and resticprofile treats a failed run-before as fatal, so no snapshot was taken from 2026-09-22 01:00 until this fix (backup-freshness: 54h stale). The block is removed rather than guarded because there is nothing on this host left for it to back up. The header now records the invariant the "blocks only WARN" promise depends on: every optional-service lookup must sit inside an `if` test or end in `|| true`. The remaining blocks were checked and all do. Deployed with playbooks/esh-docker-vm-restic-drop-kuma-block.yaml (the pre-fix hook is kept in /var/lib/restic/repair-20260923/). The live hook hash matches the canonical copy (43e6bea8b8569602). The manual backup saved snapshot 6ec9f74f, and backup-freshness now reports all backups fresh.
6.1 KiB
restic / esh-docker-vm
Primary ESH home-lab Docker host. VM on esh-pve. Covered by vzdump at
the hypervisor (3/3 on esh-pve after VM 108 retirement); this adds
file-level restic with app-consistent DB dumps.
Cross-site writes to rest-server-ana at 10.250.50.70:8000/esh-docker-vm/.
What's backed up
| Path | Purpose |
|---|---|
/opt/docker |
Compose + bind-mounted conf (~12 MB) |
/etc |
Host config |
/root |
Root's scripts / history / keys |
/home |
User home dirs |
/var/lib/docker/volumes |
HA / paperless / pgadmin / CWA / dockge / agents |
/var/lib/restic/stage |
DB dumps from pre-backup.sh |
Critically excluded
- NFS mounts from
10.0.50.50—/mnt/{backup,books,compose,documents}. Debian NAS protects these at its own layer. /mnt/backup/docker/esh-vm-docker/*— offen-sidecar tarballs on NFS; once restic proves itself for a week, retire those sidecars.- Docker internals (
backingFsBlockDev,metadata.db). - Logs, pids, user caches.
Pre-backup hooks
Unique to this host: most containers don't bundle sqlite3, so
pre-backup.sh runs sqlite3 and pg_dump from the host against the
volume bind-mount paths. Simpler than building custom images for HA
and pgadmin. (uptime-kuma moved to ana-docker on 2026-09-22 and its block
was removed; see the header of pre-backup.sh for why a stale block took
the whole backup down.)
| Service | DB | Approach |
|---|---|---|
| paperless-ngx | external Postgres 10.0.50.60 / paperless-ng |
host pg_dump |
| home-assistant | /var/lib/docker/.../homeassistant_v2.db |
host sqlite3 |
| pgadmin | /var/lib/docker/.../pgadmin4.db |
host sqlite3 |
| calibre-web-automated | /config/app.db inside container |
in-container sqlite3 (it has the binary) |
Host prerequisites
Needs sqlite3 and postgresql-client installed on esh-docker-vm:
ssh -t esh-docker-vm 'sudo apt update && sudo apt install -y sqlite3 postgresql-client restic'
(restic too — not installed on this host yet.)
Deploy (one-time setup)
1. Install restic + db-client tooling
ssh -t esh-docker-vm 'sudo apt update && sudo apt install -y restic sqlite3 postgresql-client'
2. Add esh-docker-vm entry on ana-docker rest-server
ssh ana-docker '
docker run --rm httpd:2.4-alpine htpasswd -nbB esh-docker-vm "<NEW-PW>" \
>> /mnt/backup/restic/repo/ana/.htpasswd
'
# no sudo — /mnt/backup is NFS with root_squash; dir is lkraven-owned
3. Install restic creds on esh-docker-vm
ssh -t esh-docker-vm 'sudo install -d -o root -g root -m 0700 /etc/restic /var/lib/restic'
# REST URL
ssh -t esh-docker-vm 'sudo bash -c "cat > /etc/restic/restic.env && chmod 600 /etc/restic/restic.env"'
# paste: RESTIC_REPOSITORY=rest:http://esh-docker-vm:<HTPASSWD-PW>@10.250.50.70:8000/esh-docker-vm/
# Enter, Ctrl-D
# Repo passphrase
ssh -t esh-docker-vm 'sudo bash -c "cat > /etc/restic/password && chmod 600 /etc/restic/password"'
# paste: <REPO-PASSPHRASE>
# Enter, Ctrl-D
# Install DB creds (from dbcreds.env.example — edit in a temp file first)
cp configs/restic/esh-docker-vm/dbcreds.env.example /tmp/dbcreds.env
${EDITOR:-vi} /tmp/dbcreds.env # set PAPERLESS_PGPASS to real value
scp /tmp/dbcreds.env esh-docker-vm:/tmp/
ssh -t esh-docker-vm '
sudo install -o root -g root -m 0600 /tmp/dbcreds.env /etc/restic/dbcreds.env &&
rm /tmp/dbcreds.env
'
shred -u /tmp/dbcreds.env 2>/dev/null || rm -f /tmp/dbcreds.env
4. Init the repo
ssh -t esh-docker-vm '
sudo bash -c "
set -a; . /etc/restic/restic.env; set +a
restic init
"
'
# Type passphrase twice at the prompt — same as installed in /etc/restic/password.
5. Deploy pre-backup.sh
scp configs/restic/esh-docker-vm/pre-backup.sh esh-docker-vm:/tmp/pre-backup.sh
ssh -t esh-docker-vm '
sudo install -o root -g root -m 0700 /tmp/pre-backup.sh /etc/restic/pre-backup.sh &&
rm /tmp/pre-backup.sh
'
6. Test the hook by itself (without running the full backup)
ssh -t esh-docker-vm 'sudo /etc/restic/pre-backup.sh'
ssh -t esh-docker-vm 'sudo ls -lh /var/lib/restic/stage/'
Expected files:
paperless.pg_dump— should be 1–50 MB depending on doc counthome-assistant.sqlite3— ~50 MB (matches live DB size)calibre-web-automated.app.db— ~250 KBpgadmin4.db— ~200 KB
Any WARN: lines in the hook output indicate a block that was skipped —
read them, debug one at a time.
7. Install resticprofile
ssh -t esh-docker-vm '
curl -sfL https://raw.githubusercontent.com/creativeprojects/resticprofile/master/install.sh \
| sudo sh -s -- -b /usr/local/bin &&
/usr/local/bin/resticprofile version
'
8. Deploy profile
scp configs/restic/esh-docker-vm/profiles.yaml esh-docker-vm:/tmp/profiles.yaml
ssh -t esh-docker-vm '
sudo install -o root -g root -m 0644 /tmp/profiles.yaml /etc/restic/profiles.yaml &&
rm /tmp/profiles.yaml &&
sudo resticprofile --config /etc/restic/profiles.yaml show
'
9. Enable timers + first backup
ssh -t esh-docker-vm '
sudo resticprofile --config /etc/restic/profiles.yaml schedule --all &&
sudo resticprofile --config /etc/restic/profiles.yaml backup --verbose
'
Expected first snapshot: ~1–2 GB (HA DB is the dominant file, plus
some Docker volumes). If you see much more, inspect /home and /var/lib/docker/volumes
for surprise content.
Restoring
ssh -t esh-docker-vm '
sudo bash -c "
set -a; . /etc/restic/restic.env; set +a
RESTIC_PASSWORD_FILE=/etc/restic/password \
restic restore --target /tmp/restore latest --path /opt/docker
"
'
Follow-ups after restic is proven (separate session)
- Rotate paperless-ngx Postgres password. Currently
paperless-ng— trivially weak. Update the DB, the compose, anddbcreds.env. - Retire offen/docker-volume-backup sidecars on paperless-ngx + pgadmin
stacks after ~1 week of clean restic runs. Delete the compose's
sidecar services + the tarballs under
/mnt/backup/docker/esh-vm-docker/.
Recreating the repo
Same pattern as ana-ml2 — see configs/restic/ana-ml2/README.md. Repo
path on ana-docker is /mnt/backup/restic/repo/ana/esh-docker-vm/.