This article covers how to back up and restore a self-hosted Hudu instance. A complete backup has two separate parts: your PostgreSQL database and your uploaded files. Both are covered below, along with how to verify that your backups are usable and how to restore from them.
A complete backup has two parts. The database stores your documentation's structure, records, and text, but it only stores references to your uploaded files, not the files themselves. If you restore the database without also restoring your files, Hudu will load with broken images, missing attachments, and documents that can't be downloaded. Always back up both.
Visit Self-Hosted Setup: File Storage for details on how file storage is configured, and Self-Hosted: Updating your Hudu Version for related maintenance tasks.
Backups of Hudu
Manual backup of Hudu Postgres
-
Log into the server you want to back up and navigate to the ~/hudu2 directory:
cd ~/hudu2
- Make sure the server is up and running.
-
Then run:
sudo docker compose exec -T db pg_dump -U postgres hudu_production > NAME-OF-DUMP.sql
- This file can now be moved onto a new server to be used. Make sure to keep the same
.envvariables.
A manual dump like this captures your database only. It does not include your uploaded files. See Backing up and restoring files below to back those up as well.
Auto-backup script to S3 (Hudu Software - Doubletake)
The first thing to automate is your database. There are multiple ways to automatically back up Postgres databases. One simple way is to add the Doubletake service to the end of your docker-compose.yml. On the schedule you set, it dumps your database, verifies and optionally compresses or encrypts the dump, and uploads it to your S3-compatible bucket. It can also download, verify, and restore that dump when you need it.
postgres3:
image: hudusoftware/doubletake:latest
restart: unless-stopped
links:
- db
environment:
S3_HOST_BASE: 's3.us-west-1.wasabisys.com'
S3_REGION: 's3.us-west-1'
S3_BUCKET: 'bucketname'
S3_FOLDER: 'foldername'
S3_ACCESS_KEY_ID: 'XXXX'
S3_SECRET_ACCESS_KEY: 'XXXXXXXX'
CRON_SCHEDULE: '0 */6 * * *'
DB_NAME: 'hudu_production'
DB_USER: 'postgres'
POSTGRES_PASSWORD: # Optional — DB_PASSWORD is deprecated; only needed if your db isn't using trust-mode auth
DB_HOST: 'db'
DB_BACKUP_VERIFY: '1'
DB_BACKUP_COMPRESS: '0' # Optional, default is 0
DB_BACKUP_COMPRESS_LEVEL: # Optional, default is 4
POSTGRES_EXTRA_OPTS: '--schema=public --blobs'
DISK_SPACE_THRESHOLD: 'XX' # Optional, minimum percentage of free disk space required to run a backupA few of the key options:
-
CRON_SCHEDULE— how often the backup runs. The example above (0 */6 * * *) runs every six hours. -
DB_BACKUP_VERIFY— set to1to enable Doubletake's built-in integrity check (see Verifying the integrity of your backups below). -
DB_BACKUP_COMPRESS— set to1to compress dumps before upload. Compressed dumps are written as.xzfiles. -
DB_BACKUP_ENCRYPT_KEY/DB_BACKUP_ENCRYPT_IV— optional, encrypts dumps before upload. Generate withopenssl rand -hex 32andopenssl rand -hex 16respectively. -
S3_MD5_REQUIRED— set to1if your bucket has file locking or versioning enabled; required in that case. -
AUTO_LIFECYCLE_POLICY_TAGS— set to1to automatically tag backups (monthly/weekly/hourly) for use with your S3 provider's lifecycle policies. -
DISK_SPACE_THRESHOLD— optional. Before each backup, Doubletake checks how much disk space is free, and by default it won't run unless more than 50% of the disk is available. If your database dumps are small compared to your free space, you can lower this requirement by setting it to the percentage that fits your environment.
Doubletake also supports additional advanced options, including custom S3 request headers (S3_REQUEST_HEADERS) and extra pg_dump arguments (PG_DUMP_USER_ARGS). See the full list on the Doubletake page linked below.
To run a backup on demand rather than waiting for the schedule:
sudo docker compose exec -T postgres3 /backup.sh
To view the log from the most recent run (useful for confirming a backup succeeded or troubleshooting a failure), run the following, replacing postgres3 with your container name if you changed it:
sudo docker compose exec -T postgres3 /bin/sh -c "cat /doubletake.log"
Updating Doubletake after a PostgreSQL upgrade
Older versions of Doubletake are not compatible with PostgreSQL 18. After upgrading PostgreSQL, update Doubletake to the latest image, or your scheduled backups will fail.
Make sure the Doubletake image in your docker-compose.yml is set to hudusoftware/doubletake:latest, then run the following from the ~/hudu2 directory to pull the latest image and restart the service:
sudo docker compose pull postgres3 sudo docker compose up -d postgres3
Then run a backup and check the log to confirm it completed successfully:
sudo docker compose exec -T postgres3 /backup.sh sudo docker compose exec -T postgres3 /bin/sh -c "cat /doubletake.log"
Replace postgres3 with your Doubletake service name if you changed it.
Visit the Hudu Software - Doubletake page for the full list of configuration options and manual backup, restore, and verification commands.
Verifying the integrity of your backups
There are two layers of verification, and it's important to understand what each one does and does not prove.
1. Automated file verification (built in to Doubletake)
When DB_BACKUP_VERIFY is set to 1, Doubletake calculates a BLAKE2 hash and byte size for each dump and stores them in a small .meta file next to the dump in your bucket. When you download or restore that dump, Doubletake recalculates the hash and compares it to the .meta file to confirm the dump has not changed or become corrupted since it was written. On a successful restore or download you'll see confirmation in the output, for example:
metadata downloaded to /dump/backup.meta Metadata is the same. Metadata is identical. Download successful.
2. Manual verification (recommended on a schedule)
The only way to be confident a backup is usable is to periodically restore it into a separate, non-production environment and confirm that Hudu comes up and your data is present. We recommend doing this on a recurring basis rather than only when you need it.
Between full test restores, you can run these quick checks on a dump file:
-
Confirm the file exists and isn't empty. A backup that failed part-way can leave a zero-byte or truncated file:
ls -lh NAME-OF-DUMP.sql
-
For an uncompressed plain SQL dump, confirm it completed. A complete
pg_dumpends with a completion marker on the last line. If it's missing, the dump was cut short:tail -n 1 NAME-OF-DUMP.sql # expected: -- PostgreSQL database dump complete
-
For a compressed dump, test the archive. This confirms the compressed file isn't corrupted:
xz -t backup.sql.xz
To run a full test restore, restore the dump into a scratch database or a throwaway Hudu instance using the steps in Restoring a database backup below, then log in and spot-check that your records, passwords, and attachments are present. If the restore completes without errors and the data looks correct, you have a verified, usable backup.
Schedule a recurring test restore, for example monthly. A backup you have never restored is a backup you cannot rely on.
Restoring a database backup
Once you have a database backup, you can restore your Hudu instance from it by following these steps:
- Make sure you have an up-to-date backup of your documentation before you begin.
- Move the
.sqldatabase dump file into the~/hudu2directory. Typically, the easiest way to move files is via SCP or SFTP. - Run
sudo docker compose downto bring your instance down. - Run
sudo docker compose up -d db - Run
sudo docker compose exec db dropdb hudu_production -U postgres - Run
sudo docker compose exec db createdb hudu_production -U postgres - Run
cat NAME-OF-DUMP.sql | sudo docker compose exec -T db psql -d hudu_production -U postgres - Run
sudo docker compose down - Run
sudo docker compose up -dto get your instance back up and running.
If you back up with Doubletake, you can also restore directly from your bucket by running sudo docker compose exec -T postgres3 /bin/bash -c "/restore.sh", which downloads the latest dump, verifies it against its .meta file, and restores it.
Backing up and restoring files
Your uploaded files (photos, attachments, document files, and logos) are stored separately from the database. Where they live depends on your file storage configuration: either on the local Docker volume or in an S3-compatible object storage bucket. Back up whichever one you use, on a schedule, alongside your database backups.
Local storage
When using local storage, files are located here: /var/lib/docker/volumes/hudu2_app_data/_data/. To restore, move this directory from the old server to the new server, keeping the same path.
S3 bucket storage
If your files live in an S3-compatible bucket (for example Wasabi or AWS S3), your backup job needs to copy the contents of that bucket somewhere else, on a schedule. Restoring the database alone will not bring your files back, so this step is required for a complete backup. You can use the AWS CLI, s3cmd, rclone, or a similar tool.
Sync the bucket to your local system with the AWS CLI
aws s3 sync s3://your-bucket-name /home/ubuntu/s3/your-bucket-name/
If your provider is not AWS (for example Wasabi), add the --endpoint-url flag pointing at your provider:
aws s3 sync s3://your-bucket-name /home/ubuntu/s3/your-bucket-name/ --endpoint-url=https://s3.wasabisys.com
Sync the bucket with s3cmd
s3cmd sync s3://your-bucket-name /home/ubuntu/s3/your-bucket-name/
For an offsite copy, you can also sync one bucket to another (ideally with a different provider or region) rather than pulling everything to local disk. Whichever approach you use, automate it with a cron job so file backups run as regularly as your database backups, and keep the copy separate from your primary bucket.
We also recommend using lifecycle rules to move older S3 backups to lower-cost cold storage such as Amazon Glacier.
Keep your backup bucket private. No one should be able to list the contents of a bucket that holds your files. Use IAM users and per-folder access policies, and never make the bucket public.
Moving from object to local or vice versa
Both local and object storage use the same directory structure. When moving to or from local storage, move the files from /var/lib/docker/volumes/hudu2_app_data/_data/.
FAQ
Yes. The database stores your documentation's structure and text and points to your files, but it does not contain the files themselves. If you restore only the database, Hudu will load with broken images and missing attachments. Back up both your database and your file storage.
Doubletake's built-in verification confirms the backup file is intact in storage, but the only way to know a backup is truly usable is to restore it into a separate, non-production environment and confirm Hudu comes up with your data intact. See Verifying the integrity of your backups above. We recommend running a test restore on a recurring schedule.
If you are using Docker Compose V1, you will need to add a dash to docker-compose (for example, docker-compose instead of docker compose).