# Backups and restore

> Back up a self-hosted Nodaro with one command, restore it with another and downgrade safely. Learn what the archive holds and how to protect its key.

Source: https://nodaro.ai/docs/self-hosting/backups

A self-hosted Nodaro is **backed up** with one command and **restored** with another. The backup archive holds everything the stack cannot regenerate: the database, your media, the instance encryption key and your `.env`. The restore is also the only way to downgrade, because the database migrations only move forward. Returning to an earlier version means restoring the backup you took before the update.

## What a backup contains

`tools/community-backup.sh` writes one `tar.gz` archive:

| In the archive | What it is |
| --- | --- |
| `db.dump` | Postgres, in pg_dump's custom format: workflows, users, jobs and media records |
| `minio-data.tar` | Your generated media: images, videos and audio |
| `encryption-key` | The instance key that encrypts provider keys and social sign-in tokens. **A database restored without it holds rows nobody can read.** |
| `env` | Your `.env`: provider keys and secrets |
| `manifest.json` | The app version and the time of the backup, for the restore script |

Redis is left out on purpose: it holds only short-lived job state.

**The archive is a credential:** 
It contains your `.env` and the encryption key. The script restricts the file to its owner (`chmod 600`). Keep it that way, and store the archive like a password. On Windows, `chmod` has no real effect: keep the file out of synced folders and shared drives, and restrict it with NTFS permissions if other people use the computer.

## Take a backup

From your install directory, where `docker-compose.community.yml` is, with the stack **running**:

```bash
tools/community-backup.sh
# -> ./backups/nodaro-backup-<date>-v<version>.tar.gz
```

| Option | What it does |
| --- | --- |
| `tools/community-backup.sh /path/to/backups` | Writes the archive to another directory |
| `COMPOSE_FILE=my-compose.yml tools/community-backup.sh` | Uses a compose file with another name |

The script prints what went into the archive. If the encryption key is missing, it says so loudly and exits with an error, so a scheduled job notices. That archive cannot restore your provider keys.

The database dump is always consistent. Media written while the backup runs may miss the archive. For a guaranteed-consistent media snapshot, stop the app first:

```bash
docker compose -f docker-compose.community.yml stop nodaro
```

**On Windows**, run the scripts from **Git Bash**, the shell that Git for Windows installs. PowerShell and the bash of WSL break the container paths.

### When to back up

Before every **major** version update, where the first number changes, and on whatever schedule your data deserves. A cron line works as it is:

```bash
0 3 * * * cd /path/to/install && tools/community-backup.sh >> backup.log 2>&1
```

## Restore a backup

```bash
tools/community-restore.sh backups/nodaro-backup-<date>-v<version>.tar.gz
```

The restore replaces the database and the media with the archive's contents, so it asks you to type `RESTORE` before it touches anything. Then it:

1. Stops the app and the database clients, `auth` and `rest`, because their open connections would block the restore.
2. Restores Postgres and checks that it works, instead of trusting exit codes. Some harmless messages from the Supabase image are expected and printed.
3. Restores the media and checks that the buckets are back and MinIO is healthy. Then it restores the encryption key and checks its size on the volume. Then it restores your `.env`, after saving any existing `.env` aside. A failed check stops the script **before** the app starts: starting without the right key would create a new one and make every restored provider key unreadable for good.
4. Starts everything again and waits for the app's own health check. The script reports success only once the app is up, and exits with an error otherwise.

When it finishes, open `http://localhost:3000/setup`. Every card should be green.

## Downgrade a version

There is no migration rollback. To return to an earlier version:

1. Restore the backup taken **before** the update, as above.
2. Pin the older image, for example `ghcr.io/nodaroai/nodaro-community:v1.25.1`, in `NODARO_IMAGE` or in `docker-compose.community.yml`. Exact `vX.Y.Z` tags never move.
3. Run `docker compose -f docker-compose.community.yml up -d nodaro`.

See [Updating](https://nodaro.ai/docs/self-hosting/updating) for the tags.

## Back up a managed deployment

Outside the compose stack, four things hold state:

| What | How to protect it |
| --- | --- |
| **Supabase Postgres**: workflows, profiles, jobs and media records | Use Supabase's point-in-time recovery, on paid plans, or run `pg_dump` regularly. This is the backup that matters most. |
| **The storage bucket**: generated images, videos and audio | Enable bucket versioning and a long lifecycle rule so deleted files can be recovered. Add cross-region replication for disaster recovery if you need it. |
| **Redis** | Nothing to back up. It holds only short-lived job state. If you lose it, runs in progress fail, and everything else recovers from Postgres on the next start. |
| **The instance encryption key**, `NODARO_ENCRYPTION_KEY` | Keep it with the Postgres backups. Without it, the restored rows of provider keys, social sign-in tokens and Webhook Output credentials cannot be read. The tiles then show `missing` and the keys must be entered again; nothing else breaks. |

If Postgres is down during a migration or a recovery, the API restarts in a loop until it can reach the database. Once Postgres is back, restart the Nodaro container.

## Troubleshooting

- **`the db service is not running`.** Start the stack first: `docker compose -f docker-compose.community.yml up -d`.
- **Provider keys show `missing` after a restore.** The database was restored without its matching `encryption-key`, or with a different one. Restore from an archive that has the key, or enter the keys again on `/setup`.
- **Restore verification failed.** The script refuses to leave a half-restored database silently. Run the restore again. If it fails repeatedly, the archive may be truncated: compare its size with the original.
- **`could not read the encryption key` during a backup on Windows.** You are not in Git Bash, or the app container is not running. Run the backup again from Git Bash with the stack up. The archive the script warned about is not a usable backup.
- **`media restore verification FAILED` or `encryption key verification FAILED`.** The script stopped on purpose and did not start the app. Check the volume it names, fix the cause, usually a stopped container or a full disk, and run the restore again. It is safe to repeat.

## Frequently asked questions

### How do I back up a self-hosted Nodaro?

From the install directory, with the stack running, run tools/community-backup.sh. It writes one archive to ./backups with the database, the media, the instance encryption key and your .env.

### How do I restore a backup?

Run tools/community-restore.sh with the archive's path. The restore is destructive, so it asks you to type RESTORE first. It verifies the database, the media and the encryption key before it starts the app again.

### Why do my provider keys show missing after a restore?

The database was restored without its matching encryption key. Restore from an archive that contains the key, or enter the keys again on /setup. Nothing else breaks.

### Do I need to back up Redis?

No. Redis holds only short-lived job state. If you lose it, runs in progress fail, and everything else recovers from the database on the next start.

### Can I run the backup scripts on Windows?

Yes, from Git Bash, the shell that Git for Windows installs. PowerShell and the bash of WSL break the container paths, and the encryption key is then left out of the archive.
