Operations
Backups
Section titled “Backups”Everything lives in one volume: app.db and photos/. Settings → Backup downloads all
of it as one .tar.gz, laid out exactly like the data directory.
Scheduled backups
Section titled “Scheduled backups”Set BACKUP_TOKEN (openssl rand -hex 32) and a cron job can pull the same archive:
curl -sf -H "Authorization: Bearer $BACKUP_TOKEN" \ https://example.com/api/backup -o "mash-$(date +%F).tar.gz"The token is valid for this endpoint only — it cannot log in to the admin. A wrong or
stale token answers 401, so curl -f in a cron job fails loudly instead of silently
archiving a login page.
Restoring
Section titled “Restoring”A backup restores by unpacking it over the data volume while Mash is stopped:
docker compose stop mashtar xzf mash-backup-….tar.gz -C /path/to/volumedocker compose start mashmanifest.json inside the archive records when the backup was taken and what it contains;
restore ignores it.
Manual volume backup
Section titled “Manual volume backup”You can also back up the volume directly instead of going through the app:
# With the container stopped (or quiesced), copy the data directory:docker compose stopcp -a ./data ./mash-backup-$(date +%F)docker compose startFor hot backups, prefer SQLite’s own backup over copying a live app.db (the container
image doesn’t ship the sqlite3 CLI, so run it from the host against the mounted volume):
sqlite3 ./data/app.db ".backup ./mash-backup-$(date +%F).db"then copy photos/ alongside it.
Upgrades
Section titled “Upgrades”docker compose pull && docker compose up -dMigrations run automatically at boot. Before applying any, Mash snapshots the database to
app.pre-<n>.db next to app.db (one snapshot is kept — the state just before the most
recent upgrade), so a bad upgrade can be rolled back by stopping the container, renaming the
snapshot over app.db, and starting the previous image. Still take a full backup first if
you’re jumping many versions — the snapshot covers the database, not photos/.
To see what you’re running, the foot of Settings shows the version and the commit the build was made from, linked to the commit on GitHub.
Reverse proxy
Section titled “Reverse proxy”Mash sits happily behind Caddy, nginx, or Traefik terminating TLS. The one rule:
PUBLIC_BASE_URL must be the public URL (e.g. https://sauce.example.com), not the
internal address. It’s baked into your QR codes and used for CSRF origin checks, so a
mismatch breaks form submissions.
Rotating credentials
Section titled “Rotating credentials”ADMIN_PASSWORD is reapplied from the environment on every start: change it in .env,
docker compose up -d, done. The same goes for ADMIN_EMAIL. Changing SESSION_SECRET
signs everyone out; changing VOTE_SALT resets vote dedup identity (existing counts stay).
Forgot the admin password?
Section titled “Forgot the admin password?”If you’ve configured email (SMTP_URL + MAIL_FROM), the sign-in page shows a Forgot your
password? link to /reset. Enter the admin email and Mash sends a reset link, good for 30
minutes, to set a new one. The page confirms “if that address is the admin’s, a link is on its
way” whether or not it matched — a stranger can’t use it to discover the admin address.
With no email configured the link doesn’t appear; recover instead by setting ADMIN_PASSWORD
in the environment and restarting, as above.
One catch, and it follows from that same rule: because ADMIN_PASSWORD is reapplied from the
environment on every start, a password set through the reset flow lasts only until the next
restart reapplies the env value. To make it permanent, update ADMIN_PASSWORD in .env to
match.