2 Backups
Drew edited this page 2026-05-22 18:55:45 -04:00

Backups

What to back up and how. Currently no automation — these are commands I run by hand.

What to back up

Path What's in it
/var/discourse/shared/standalone/ Discourse db + uploads — use Discourse's built-in backup, not raw tar
/opt/forgejo/data/ Forgejo repos + sqlite + LFS — stop the container first
/var/sftp/shared/uploads/ Filedrop + SFTP shared bucket
/home/drew/docker/filebrowser/{srv,database,config} Filebrowser files + user db
/etc/cloudflared/ Tunnel UUID + credentials
/etc/ssh/sshd_config + /etc/ssh/ssh_host_* sshd config + host keys
/var/landing/ The two landing sites + their compose
/opt/filedrop/ Filedrop compose, nginx.conf, hooks

Skipped: Docker images (re-pullable), /opt/filedrop/tusdata/ (in-flight uploads).

Procedures

Discourse — use the built-in backup

# Web UI: Admin → Settings → Backups → Create Backup
# Or CLI:
sudo /var/discourse/launcher enter app
discourse backup
exit

# Backup lands in /var/discourse/shared/standalone/backups/default/<timestamp>.tar.gz
# Copy off-host:
rsync -av /var/discourse/shared/standalone/backups/default/ <remote>:/backups/discourse/

The Discourse backup CLI produces one tarball with everything — db dump + uploads. Don't just rsync the data dir — you'll grab postgres mid-write and end up with a broken backup.

Forgejo — stop, tar, start

cd /opt/forgejo
docker compose stop forgejo
sudo tar czf /backups/forgejo-$(date +%F).tar.gz -C /opt/forgejo data/
docker compose start forgejo

Stop is required because the sqlite db is a single file with WAL — copying it live can produce a corrupted backup. Stop is fast (seconds).

For zero-downtime: use sqlite3 /opt/forgejo/data/gitea/gitea.db ".backup /tmp/gitea.db.bak" then tar both that and the rest of data/. The repos themselves are bare git and are safe to copy live (a half-pushed pack is harmless — git resyncs).

Filedrop / SFTP shared bucket

rsync -av --delete /var/sftp/shared/uploads/ <remote>:/backups/uploads/

Append-only bucket — --delete is safe if the policy is "remote mirror". Drop --delete if you want to retain files that have been removed locally.

Filebrowser

cd /home/drew/docker/filebrowser
docker compose stop filebrowser           # quick — protects sqlite
sudo tar czf /backups/filebrowser-$(date +%F).tar.gz srv/ database/ config/
docker compose start filebrowser

Configs (small, daily)

sudo tar czf /backups/configs-$(date +%F).tar.gz \
    /etc/cloudflared \
    /etc/ssh/sshd_config /etc/ssh/ssh_host_* \
    /var/landing \
    /opt/filedrop/docker-compose.yml /opt/filedrop/nginx /opt/filedrop/hooks \
    /opt/forgejo/docker-compose.yml \
    /home/drew/docker/filebrowser/docker-compose.yml

Tiny tarball (~MB). Run daily. This is what you'd need to rebuild the box alongside the data tarballs above.

Where to put the tarballs

Easiest: rsync them to another machine on the tailnet.

rsync -av /backups/ drew@um890pro:/backups/discourse/

Tailscale handles the encryption, no other setup needed. Right now there is no off-host backup — that's the gap.

Restore

See Recovery.