Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cryguy/hashboard/llms.txt

Use this file to discover all available pages before exploring further.

A Hashboard backup is always two things: the SQLite database file and the attachments directory. Attachment bytes are stored on disk under ATTACHMENTS_DIR, not inside the database — a snapshot of the database alone restores an instance whose file download links all return 404. A snapshot of the attachments alone is equally incomplete; the database rows that describe those files are missing. Both parts are required, and the order in which you take them matters. The Docker image places both the database and the attachments directory under /data precisely so that backing up one directory captures everything. For pm2 deployments, the locations are set by DATABASE_URL and ATTACHMENTS_DIR in .env.

Database backup

Do not cp a live SQLite database in WAL mode. In WAL (Write-Ahead Log) mode, committed pages can still reside in the -wal sidecar file at the moment you copy the main database file. A plain cp hashboard.db of a running database may produce a stale or torn copy that is inconsistent or unreadable. Use VACUUM INTO for live backups, or stop the process and copy all three files together.

Safe form 1 — live, no downtime

VACUUM INTO writes a single, consistent, fully checkpointed snapshot to a new file without pausing writes to the original:
sqlite3 hashboard.db "VACUUM INTO '/backups/hashboard-$(date +%F).db'"
The output file is a self-contained, WAL-free copy of the database at the moment the command ran. It can be opened, inspected, or restored independently of any sidecar files.

Safe form 2 — offline (process stopped)

If you stop Hashboard before backing up, copy all three files together. Leaving any one of them behind produces an inconsistent backup:
cp hashboard.db hashboard.db-wal hashboard.db-shm /backups/
The -wal and -shm files may be zero-length or absent on a cleanly stopped process — that is fine. Copy them anyway so the backup is complete regardless.

Attachments backup

Attachment files are immutable once written — Hashboard never modifies a file after the initial upload. A plain recursive copy or rsync is always safe and only ever adds entries:
rsync -a data/attachments/ /backups/attachments/
Adjust the source path to match your ATTACHMENTS_DIR setting.

Order matters

Always take the database snapshot first, then back up the attachments. In this order, the worst case is a file on disk with no database row pointing to it — those orphaned files are invisible to users and cause no harm. The reverse order — attachments first, then the database — can leave rows in the database that promise bytes the backup does not contain, which restores to a set of broken download links.
A complete backup procedure in the correct order:
# 1. Database snapshot first (consistent, live, no downtime)
sqlite3 hashboard.db "VACUUM INTO '/backups/hashboard-$(date +%F).db'"

# 2. Attachments second (immutable files, plain rsync is safe)
rsync -a data/attachments/ /backups/attachments/

From a live Docker container

When Hashboard is running under Docker Compose, use docker compose exec to run VACUUM INTO inside the container against the named volume. Both the database and attachments live under /data, so no path translation is needed:
docker compose exec hashboard node -e "new (require('better-sqlite3'))(process.env.DATABASE_URL).exec(\"VACUUM INTO '/data/backup.db'\")"
This creates /data/backup.db on the named volume. To extract it to the host:
docker cp hashboard:/data/backup.db ./backups/hashboard-$(date +%F).db
Then back up the attachments from the volume using a separate container or volume-aware tooling:
docker run --rm \
  -v hashboard-data:/data \
  -v "$(pwd)/backups/attachments":/out \
  alpine sh -c "cp -a /data/attachments/. /out/"

Restore

To restore from a backup, stop Hashboard, replace the database file and attachments directory with the backup copies, then restart. For Docker, copy the files into the named volume before starting the container. Hashboard will apply any pending migrations that postdate the backup on the next boot.
Before relying on a backup, verify it opens cleanly:
sqlite3 /backups/hashboard-2025-01-01.db "PRAGMA integrity_check;"
A healthy database returns a single row: ok. Any other output indicates corruption. VACUUM INTO snapshots are almost always clean because the command only completes after writing a consistent file, but periodic verification is good practice.

Build docs developers (and LLMs) love