Backup and restore
How to back up an AlianHub instance, restore it, and prove that the backups work. Everything lives in two places: MongoDB (every workspace's data) and, with STORAGE_TYPE=server, the storage/ folder of uploaded files. A backup must cover both.
Take a backup in the app
- Open Settings › Instance › Backups.
- Tick Include uploaded files to add
storage/(only withSTORAGE_TYPE=server; files in a bucket are not included). - Start the backup. The archive appears in the list when it is done.
- Download it, or copy it off the
backups/volume. Keep at least one copy off the host.
The archive is a .tar.gz named alianhub-<version>-<YYYYMMDD>-<HHMMSS>.tar.gz, written to BACKUP_DIR (backups/ in the app folder; the alianhub_backups volume, /app/backups, in Docker). It holds manifest.json first (date, version, workspaces, whether files are included), then every collection of every database as JSON lines (global/users.jsonl, <companyId>/tasks.jsonl, ...) in MongoDB's extended JSON, so ids and dates survive the round trip.
Archives are not encrypted
A backup holds everything: password hashes, encrypted secrets, every task and file. Keep backups/ on an encrypted volume and encrypt any copy that leaves the host, for example with gpg --symmetric. See Security.
Schedule backups
The app does not take backups on a timer. Use cron (or a systemd timer) with INSTANCE_ADMIN_KEY, which lets a script call the Instance API without a browser session:
Set a long random
INSTANCE_ADMIN_KEYin.envand restart.Add a cron job on the host:
bash# /etc/cron.d/alianhub-backup — every night at 02:00 0 2 * * * root curl -s -X POST -H "adminkey: <your INSTANCE_ADMIN_KEY>" -H 'content-type: application/json' -d '{}' http://localhost:4000/api/v2/instance/backupsCopy new archives off the host with your usual tool (
rclone,rsync,aws s3 cp) and delete old ones, from the Backups page or withDELETE /api/v2/instance/backups/<name>. The app keeps every archive until you delete it.
To include uploaded files, send -d '{"includeFiles":true}' instead of -d '{}'.
Restore
- Open Settings › Instance › Backups and choose an archive. The list shows the archives in
BACKUP_DIR; to restore one taken elsewhere, copy it there first (in Docker:docker cp alianhub-….tar.gz <project>-app-1:/app/backups/). - Read its manifest (date, version, workspaces) and type its name to confirm.
A restore then:
- takes a safety backup of the current state (
pre-restore-*); - switches the API to maintenance: every call except
/healthand the Instance console answers 503, and open tabs show a banner and recover on their own; - drops every collection of each database named in the archive, including collections created after the backup, refills the ones the archive holds, and restores the files if they were included;
- reruns migrations, clears caches and reconnects to the database.
Rules:
- Restore an archive taken by the same or an older version, never a newer one.
- A restore replaces each database the archive names. Workspaces created after the backup, which the archive does not name, are left as they are.
- Encryption keys are not in the archive. Restore with the same
JWT_SECRET(andSECRETS_KEY,TWO_FACTOR_ENC_KEY,AUDIT_CHAIN_KEYif you use them), or stored secrets will not decrypt.
The restore drill
A backup you have never restored is a guess. Restore one on a throwaway instance regularly:
Install a second copy. On the same Docker host, give it its own folder,
.envand project name:bashmkdir ~/alianhub-drill && cd ~/alianhub-drill cp ~/alianhub/docker-compose.yml . echo "JWT_SECRET=<the same JWT_SECRET as production>" > .env APP_PORT=4001 docker compose -p drill up -dCopy the archive into the drill:
docker cp alianhub-….tar.gz drill-app-1:/app/backups/. Openhttp://<host>:4001, complete setup with a throwaway owner, and restore the archive from Settings › Instance › Backups.Log in with a real account and open a few projects, tasks and attachments.
Remove the drill:
docker compose -p drill down -v.
Never run the drill with docker-compose.legacy.yml loaded: that file fixes the volume names, so the drill would attach to the live data and restoring there would overwrite it.
Other ways to back up
mongodump. MongoDB's own tool backs up the same data:
mongodump --uri "mongodb://localhost:27017" --gzip --archive=alianhub-$(date +%F).archive.gz
mongorestore --uri "mongodb://localhost:27017" --gzip --archive=alianhub-2026-11-02.archive.gz --dropIn Docker, run it inside the database container: docker compose exec -T mongo mongodump --gzip --archive > alianhub-$(date +%F).archive.gz. With STORAGE_TYPE=server, copy storage/ as well.
Volume snapshots. Docker users can snapshot the volumes (<project>_alianhub_mongo_data, <project>_alianhub_storage, <project>_alianhub_backups). Stop the stack first, or the database files may be caught mid-write.
Files in a bucket. With S3-compatible storage the files are not in any of these backups. Turn on versioning or replication on the bucket, or copy it on a schedule.
