NodaroAccount
Nodaro Docs
DocumentationNode ReferenceModelsTemplatesAI Agents (MCP)DevelopersSelf-hostingResearchCompare

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.

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 archiveWhat it is
db.dumpPostgres, in pg_dump's custom format: workflows, users, jobs and media records
minio-data.tarYour generated media: images, videos and audio
encryption-keyThe instance key that encrypts provider keys and social sign-in tokens. A database restored without it holds rows nobody can read.
envYour .env: provider keys and secrets
manifest.jsonThe 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:

tools/community-backup.sh
# -> ./backups/nodaro-backup-<date>-v<version>.tar.gz
OptionWhat it does
tools/community-backup.sh /path/to/backupsWrites the archive to another directory
COMPOSE_FILE=my-compose.yml tools/community-backup.shUses 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:

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:

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

Restore a backup

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 for the tags.

Back up a managed deployment

Outside the compose stack, four things hold state:

WhatHow to protect it
Supabase Postgres: workflows, profiles, jobs and media recordsUse 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 audioEnable bucket versioning and a long lifecycle rule so deleted files can be recovered. Add cross-region replication for disaster recovery if you need it.
RedisNothing 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_KEYKeep 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.

Restore into a new Supabase project

A restore into a new Supabase project does not keep the database privileges. A function meant for the server alone can come back callable by the browser roles, anon and authenticated. A table or a column that the migrations closed can come back readable or writable. After any restore, list the SECURITY DEFINER functions that those roles can run:

SELECT p.oid::regprocedure AS function,
       has_function_privilege('anon', p.oid, 'EXECUTE') AS anon,
       has_function_privilege('authenticated', p.oid, 'EXECUTE') AS authenticated
  FROM pg_proc p
 WHERE p.pronamespace = 'public'::regnamespace
   AND p.prosecdef
   AND p.prorettype NOT IN ('trigger'::regtype, 'event_trigger'::regtype)
   AND (has_function_privilege('anon', p.oid, 'EXECUTE')
        OR has_function_privilege('authenticated', p.oid, 'EXECUTE'))
 ORDER BY 1;

The result must hold only the functions listed in browser_callable in the repository's test file supabase/tests/server-only-function-privileges.behavior.sql. Close every other function, and name PUBLIC as well, because anon and authenticated inherit its default EXECUTE:

REVOKE EXECUTE ON FUNCTION public.<name>(<args>) FROM PUBLIC, anon, authenticated;
GRANT EXECUTE ON FUNCTION public.<name>(<args>) TO service_role;

Migration 598 in supabase/migrations/ closes the server-only functions and restores the table and column privileges that the migrations up to it set. It is safe to run again. To check a restored database against the expected privileges, run the test file with psql. It rolls itself back and stops at the first thing that is open:

psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f supabase/tests/server-only-function-privileges.behavior.sql

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.

Last updated on

On this page