Published 2026-04-22 by TechNet New England
Updating a self-hosted Hudu instance is straightforward, but skipping the backup step or running into permission issues can turn a routine update into a recovery project. This guide covers the full process: what to back up, how to avoid common mistakes, and a reusable script your team can run on a schedule. ## What You Need to Back Up Before any Hudu update, you need to preserve three things: 1. **The PostgreSQL database** (all your company records, assets, passwords, documentation) 2. **The .env file** (your Hudu configuration, secrets, and API keys) 3. **Uploaded files** (logos, attachments, photos stored locally or in object storage) If you lose any of these during a bad update, recovery without a backup ranges from painful to impossible. ## The Common Permission Mistake If you run the backup commands like this: ```bash cd ~/hudu2 sudo docker compose exec -T db pg_dump -U postgres hudu_production > hudu-backup.sql cp .env .env.backup ``` You will likely get: ``` -bash: hudu-backup.sql: Permission denied cp: cannot create regular file '.env.backup': Permission denied ``` The reason: the `>` redirect runs as your current shell user, not as root. Even though `sudo` runs the Docker command, the output file is created by your unprivileged user in a directory they may not have write access to. The fix: write backups to a directory your user can write to (like `/tmp`), or wrap the entire command in `sudo sh -c`. ## Safe Manual Backup Commands Run these before every update: ```bash # Create a backup directory sudo mkdir -p /var/backups/hudu # Go to your Hudu directory cd ~/hudu2 # Back up the database sudo sh -c 'docker compose exec -T db pg_dump -U postgres hudu_production > /var/backups/hudu/hudu-backup-$(date +%F-%H-%M).sql' # Back up the .env file sudo cp .env /var/backups/hudu/.env.backup-$(date +%F-%H-%M) # Back up uploaded files (only if using local storage, not S3) sudo tar -czf /var/backups/hudu/hudu-app-data-$(date +%F-%H-%M).tar.gz /var/lib/docker/volumes/hudu2_app_data/_data # Verify backups exist sudo ls -lh /var/backups/hudu/ ``` If you use S3, Wasabi, or MinIO for file storage instead of local volumes, skip the tar command and back up your bucket separately. ## The Update Process Once backups are confirmed, update Hudu: ```bash cd ~/hudu2 sudo docker compose down sudo docker compose pull sudo docker compose up -d ``` That is the official Hudu update sequence. It stops the containers, pulls the latest images, and starts everything back up. ### Verify the update ```bash sudo docker compose ps ``` All services should show as running: app, worker, db, redis, and your reverse proxy (if managed through the same compose file). ### Check logs if something looks wrong ```bash sudo docker compose logs --tail=200 ``` Look for errors related to migrations, database connections, or missing columns. If you see migration failures after an update, see our companion article on [fixing Hudu 500 errors from failed migrations](/blog/hudu-company-page-500-error-failed-database-migration). ## Reusable Backup Script Save this as `/usr/local/bin/hudu-backup.sh` so your team can run it before any update or on a nightly schedule: ```bash #!/usr/bin/env bash set -Eeuo pipefail # ===== Settings ===== HUDU_DIR="$HOME/hudu2" BACKUP_ROOT="/var/backups/hudu" DATESTAMP="$(date +%F-%H%M%S)" RETENTION_DAYS=14 # Set to "yes" if Hudu uses local file storage # Set to "no" if using S3/Wasabi/MinIO BACKUP_LOCAL_STORAGE="yes" # Local storage path (default Hudu Docker volume) LOCAL_STORAGE_PATH="/var/lib/docker/volumes/hudu2_app_data/_data" # Optional: remote copy target # REMOTE_TARGET="user@backuphost:/srv/backups/hudu/" REMOTE_TARGET="" # ===== Setup ===== RUN_DIR="${BACKUP_ROOT}/${DATESTAMP}" LOGFILE="${BACKUP_ROOT}/backup.log" mkdir -p "${RUN_DIR}" touch "${LOGFILE}" log() { echo "[$(date '+%F %T')] $*" | tee -a "${LOGFILE}" } fail() { log "ERROR: $*" exit 1 } command -v docker >/dev/null 2>&1 || fail "docker not found" cd "${HUDU_DIR}" || fail "Could not cd to ${HUDU_DIR}" if ! sudo docker compose ps >/dev/null 2>&1; then fail "docker compose not responding in ${HUDU_DIR}" fi log "Starting Hudu backup into ${RUN_DIR}" # 1) Back up .env if [[ -f ".env" ]]; then sudo cp -a ".env" "${RUN_DIR}/.env" sudo chmod 600 "${RUN_DIR}/.env" log "Backed up .env" else log "WARNING: .env not found in ${HUDU_DIR}" fi # 2) Back up Postgres database log "Dumping database" if ! sudo sh -c "docker compose exec -T db pg_dump -U postgres hudu_production > ${RUN_DIR}/hudu_production.sql"; then fail "Database dump failed" fi if [[ ! -s "${RUN_DIR}/hudu_production.sql" ]]; then fail "Database dump is empty" fi sudo gzip -f "${RUN_DIR}/hudu_production.sql" log "Database dump complete" # 3) Back up local file storage if [[ "${BACKUP_LOCAL_STORAGE}" == "yes" ]]; then if [[ -d "${LOCAL_STORAGE_PATH}" ]]; then log "Archiving local uploaded files" sudo tar -czf "${RUN_DIR}/hudu_app_data.tar.gz" -C "${LOCAL_STORAGE_PATH}" . log "Local storage archive complete" else log "WARNING: Local storage path not found: ${LOCAL_STORAGE_PATH}" fi else log "Skipping local storage (using object storage)" fi # 4) Generate checksums log "Generating checksums" ( cd "${RUN_DIR}" sudo sh -c 'sha256sum ./* > SHA256SUMS.txt' ) # 5) Optional off-server copy if [[ -n "${REMOTE_TARGET}" ]]; then log "Copying backup off-server" rsync -av "${RUN_DIR}/" "${REMOTE_TARGET}${DATESTAMP}/" fi # 6) Clean up old backups log "Removing backups older than ${RETENTION_DAYS} days" find "${BACKUP_ROOT}" -mindepth 1 -maxdepth 1 -type d -mtime +"${RETENTION_DAYS}" -exec rm -rf {} + log "Backup finished successfully" ``` ### Install the script ```bash sudo nano /usr/local/bin/hudu-backup.sh # Paste the script above sudo chmod 700 /usr/local/bin/hudu-backup.sh sudo mkdir -p /var/backups/hudu ``` ### Run it manually before an update ```bash sudo /usr/local/bin/hudu-backup.sh ``` ### Schedule it to run nightly at 2:15 AM ```bash sudo crontab -e ``` Add this line: ``` 15 2 * * * /usr/local/bin/hudu-backup.sh >/dev/null 2>&1 ``` ## Full Update Checklist Here is the complete sequence for a safe Hudu update: ```bash # 1. Run the backup script sudo /usr/local/bin/hudu-backup.sh # 2. Verify backups exist sudo ls -lh /var/backups/hudu/ # 3. Stop Hudu cd ~/hudu2 sudo docker compose down # 4. Pull latest images sudo docker compose pull # 5. Start Hudu sudo docker compose up -d # 6. Verify all containers are running sudo docker compose ps # 7. Check for errors sudo docker compose logs --tail=200 ``` ## Copy Backups Off the Server A backup that only exists on the same server as Hudu is not a real backup. Copy it somewhere else: ```bash # From your local machine or another server scp user@huduserver:/var/backups/hudu/latest-backup.sql.gz ./ ``` Or set the `REMOTE_TARGET` variable in the script to automatically rsync backups to another machine after each run. ## Docker Compose Version Note If you get errors with `docker compose`, your system might use the older standalone version. Try `docker-compose` (with a hyphen) instead: ```bash sudo docker-compose down sudo docker-compose pull sudo docker-compose up -d ``` ## If the Update Breaks Something Check migration status first: ```bash cd ~/hudu2 sudo docker compose exec app bundle exec rails db:migrate:status ``` If any migration shows `down`, run: ```bash sudo docker compose exec app bundle exec rails db:migrate ``` If a migration fails, check the error message carefully. See our [Hudu 500 error migration fix guide](/blog/hudu-company-page-500-error-failed-database-migration) for a real example of how to diagnose and patch a stuck migration. ## Key Takeaways - Always back up before updating. Database, .env, and uploaded files. - Watch out for permission issues when redirecting output. Use `sudo sh -c` or write to `/tmp`. - The backup script can run on a cron schedule so you always have a recent backup ready. - Copy backups off the server. A backup on the same disk as the data it protects is not safe. - If an update causes 500 errors, check migration status before anything else. --- *Need help managing your self-hosted Hudu instance or other Docker-based IT platforms? [Contact TechNet New England](/contact) for expert support.*