Overview
Flowsave is an open-source CLI for self-hosted n8n instances. It snapshots your workflows, folder structure, and encrypted credentials to local disk, lets you compare and restore any snapshot, migrate a full instance to a new server in one command, and optionally version your backups in a Git repository.
Who it's for: n8n self-hosters — anyone running n8n in Docker, on a VPS, or on bare metal who has ever lost a workflow to a bad upgrade, a deleted container, or an accidental edit.
Design principles
- Local-first. All snapshots live on your machine. No telemetry, no phoning home, no account required.
- Credentials are sacred. Always encrypted at rest (AES-256-GCM), never in logs, never pushed to git.
- One binary, zero dependencies. The npm package is a single self-contained file.
- Honest output. Every command ends with a summary that says exactly what happened — including what didn't happen and why.
Installation
npm install -g flowsave-cli- The npm package is
flowsave-cli; the installed command isflowsave. - Requires Node.js ≥ 18.
- The n8n instance must be reachable over HTTP(S) from the machine running Flowsave.
Docker requirement (credential backup only)
Workflow backup needs only the n8n REST API. Credential backup and restore additionally use docker exec to call the n8n CLI inside the container. On Linux, Docker must be usable without sudo:
sudo usermod -aG docker $USER # then log out and back in
macOS with Docker Desktop requires nothing — it already runs as the current user. Run flowsave doctor to verify the entire setup before taking your first backup.
Quick start
Three commands to go from zero to your first verified snapshot:
flowsave config init # one-time interactive setup flowsave backup # take your first snapshot flowsave list # see all snapshots
Core concepts
Snapshot
A point-in-time copy of an n8n instance: all workflows as individual JSON files, optional folder structure, optional encrypted credentials, and metadata. Snapshots get sequential integer IDs (1, 2, 3, …) and live under the configured backup directory.
Configuration
A single JSON file at ~/.flowsave/config.json, created by the interactive wizard (flowsave config init). All commands read it; commands that require it exit with a pointer to config init if it is missing.
Credential encryption
Credentials are exported from the n8n container, encrypted with a user-supplied passphrase, and stored as a single opaque blob. The passphrase is never stored anywhere. Flowsave cannot recover credentials if it is lost — this is a feature, not a limitation. Workflows are not encrypted and are unaffected by a lost passphrase.
Two credential transport paths
| Path | How it works | Limitation |
|---|---|---|
| Docker | docker exec into the container, runs n8n export/import:credentials. Handles all credential types including OAuth. |
Requires Docker to be running locally and the container to be accessible. |
| API | n8n REST API. No Docker needed; works across machines. | OAuth credentials may fail schema validation — their exported data contains internal token state the API rejects. Failures are listed by name so you can re-add them manually. |
n8n editions
The folder REST API is an n8n Enterprise feature. On Community edition, backups are complete but flat — folder layout is not preserved. Flowsave detects this and says so in every relevant summary.
Command reference
Jump to any command:
flowsave config
Manage the configuration file at ~/.flowsave/config.json. Three subcommands: init, show, and set.
flowsave config init
Interactive setup wizard. Run once after installing.
| Prompt | Default | Validation |
|---|---|---|
| n8n instance URL | http://localhost:5678 | Must parse as a valid URL |
| API key (n8n Settings → API) | — | Required, non-empty; input masked with * |
| Docker container name | blank | Optional — blank disables credential backup |
| Backup directory | ~/.flowsave/backups | — |
| Git remote URL | blank | Optional — blank disables flowsave push |
| Git branch | main | Only asked if a git remote was entered |
init asks "Overwrite?" (default No) before touching anything. It makes no API calls — connectivity is verified by flowsave doctor or the first backup.flowsave config show
Prints the current configuration with the API key masked (first 8 + last 4 characters visible). Also prints the config file path. Exits 1 with a pointer to config init if no config exists.
flowsave config set <key> <value>
Updates a single field without re-running the wizard. Valid keys: instanceUrl, apiKey, containerName, backupDir, gitRemote, gitBranch, dashboardToken. Any other key exits 1 and lists the valid keys.
flowsave config set gitRemote git@github.com:you/n8n-backups.git flowsave config set gitBranch backups flowsave config set containerName n8n
flowsave backup free
Snapshot all workflows and (optionally) encrypted credentials from the configured instance.
No flags. Credential backup is controlled entirely by config and an interactive prompt.
Interactive behavior
Only shown when containerName is configured:
- Prompt: "Set a passphrase to encrypt credentials (leave blank to skip):" — masked input, validated inline against the passphrase policy. Blank skips credentials entirely.
- Prompt: "Confirm passphrase:" — must match exactly; mismatch aborts with exit 1 before anything is written.
Output — Snapshot Summary
- Snapshot ID, instance URL, n8n version (when detectable via Docker), timestamp, workflow count
- Folder structure:
✓ included/✗ not included(Enterprise-only feature) - Credentials:
✓ encrypted & included/— not included - Snapshot size, saved-to path
Exit codes
0 success · 1 passphrase mismatch, connectivity failure, or any backup error.
flowsave restore free
Restore a snapshot to the same instance or to a different one.
flowsave restore 3 # same-instance flowsave restore --snap 3 # identical — flag form flowsave restore 3 --to http://new:5678 --api-key <key> # cross-instance flowsave restore 3 --to http://new:5678 --api-key <key> \ --target-container n8n-new --passphrase <pass> # cross-instance + Docker creds
Flags
| Flag | Description |
|---|---|
[id] positional | Snapshot ID to restore |
--snap <id> | Alternative to the positional argument |
--to <url> | Target instance URL — switches to cross-instance mode |
--api-key <key> | Target API key — required with --to, rejected without it |
--target-container <name> | Local Docker container for cross-instance credential import (handles OAuth and all types) |
--passphrase <key> | Passphrase to decrypt the snapshot's credentials |
Same-instance vs. cross-instance
flowsave diff before migrating again to check what changed.- Same-instance (no
--to): workflows are updated/created by ID; stale credentials absent from the snapshot are automatically deleted after Docker import. - Cross-instance (
--to): always creates new workflows (forceCreate).
Exit codes
0 success · 1 validation or restore error.
flowsave migrate free
Take a fresh backup of the live source instance and restore it to a destination in one command. Always migrates current state — for a historical snapshot use restore --to instead.
flowsave migrate --to http://new:5678 --api-key <key> flowsave migrate --to http://new:5678 --api-key <key> --passphrase <pass> flowsave migrate --to http://new:5678 --api-key <key> \ --target-container n8n-new --passphrase <pass>
Flags
| Flag | Required | Description |
|---|---|---|
--to <url> | ✓ | Destination n8n instance URL |
--api-key <key> | ✓ | Destination n8n API key |
--target-container <name> | — | Local Docker container for credential import (OAuth-safe path) |
--passphrase <key> | — | Passphrase for credential encryption/decryption |
Progress: spinner shows "Step 1/2: Backing up source instance…" then "Step 2/2: Restoring to destination instance…". The migration leaves a normal restorable snapshot behind.
Exit codes
0 success · 1 passphrase mismatch, missing required flags, or any step failing.
flowsave list local
List all local snapshots in a table, newest first. No API calls.
Columns: ID · Timestamp · Size · Instance URL (truncated at 40 chars). Empty state prints: "No snapshots yet. Run 'flowsave backup' to take your first snapshot."
flowsave show <id> local
Full details of one snapshot. No API calls.
Output includes: date, instance, n8n version (when recorded), workflow count, size, credentials line, folder backup flag, credentials table (names + types, no secrets), and a workflows table with name, active status, node count, folder path, and tags.
Exit codes
0 success · 1 invalid or unknown snapshot ID.
flowsave diff <id1> <id2> local
Compare two snapshots. No API calls.
Reports added/removed workflows by name, and for modified workflows shows field-level context: node count changes, activation toggles, and renames. When both snapshots carry credential metadata, also reports added and removed credentials by name and type.
Known limitations
- Credential diff tracks presence only (by ID). Renames are invisible — the data is encrypted; the name is metadata.
- A credential type change appears as remove + add, matching how n8n itself requires delete-and-recreate.
Exit codes
0 success · 1 non-integer IDs or unknown snapshots.
flowsave delete <id> [id…]
Permanently delete one or more snapshots from disk and the local index.
flowsave delete 3 flowsave delete 19 14 13 flowsave delete 19 14 13 --yes
| Flag | Description |
|---|---|
-y, --yes | Skip the confirmation prompt |
Accepts multiple IDs; duplicates are de-duplicated. Unknown IDs are each reported; if some IDs are valid the command continues with those. Without --yes, prints the full list and asks once — default No.
Exit codes
0 success or user-aborted · 1 invalid/missing IDs.
flowsave prune
Remove snapshots whose content is identical to a newer snapshot — keeps the most recent copy of each distinct state.
flowsave prune # preview + confirm flowsave prune --dry-run # preview only, no changes flowsave prune --yes # no confirmation prompt
| Flag | Description |
|---|---|
--dry-run | Show candidates without deleting anything |
-y, --yes | Skip the confirmation prompt |
"Identical" means: zero workflows added, removed, or modified and zero credentials added or removed.
How the algorithm works
Prune walks newest → oldest, comparing each snapshot only to the last distinct reference — not the absolute newest. This preserves the last restore point before every deletion/change event.
#2 9 creds (Airtable present) ← kept — differs from #3 #3 8 creds (Airtable removed) ← kept — differs from #6 #4 9 creds ← pruned (≡ #6) #5 9 creds ← pruned (≡ #6) #6 9 creds (Airtable re-added) ← kept (newest)
Exit codes
0 success, nothing-to-do, or user-aborted · 1 on errors.
flowsave push
Commit the latest snapshot to a git repository and push it to the configured remote.
No flags. Configure the remote first:
flowsave config set gitRemote git@github.com:you/n8n-backups.git flowsave config set gitBranch backups # optional, default: main
Key behaviors
- The remote repo does not need to exist as a branch beforehand — Flowsave initializes a local git repo on first push and creates the remote branch automatically.
- A
.gitignoreis written automatically so encrypted credential blobs (_credentials.enc.json) never enter git — only workflow JSON and snapshot metadata are committed. - Commit message format:
chore: flowsave backup <ISO-timestamp> snapshot-<id> - Always pushes even with no new commit — so changing
gitBranchpropagates to the remote. - Git authentication is yours (SSH keys / credential store) — Flowsave never touches or stores git credentials.
Exit codes
0 success · 1 no gitRemote configured or no snapshots exist.
flowsave doctor
Diagnose the local setup. Runs four checks, each reported as ✓/✗. Never throws.
| # | Check | Pass condition | Failure detail |
|---|---|---|---|
| 1 | Config | ~/.flowsave/config.json exists and validates | Validation message; subsequent checks are skipped |
| 2 | n8n instance | GET /api/v1/workflows?limit=1 succeeds within 5s | Distinguishes 401 (connected but API key invalid) from network errors |
| 3 | Docker (if containerName set) | Daemon reachable without sudo and named container is running | Permission-denied gets the exact fix: sudo usermod -aG docker $USER + relogin |
| 4 | Backup dir | Directory exists/creatable and writable | Path and reason |
Exit codes
0 all checks pass · 1 any check fails (with count).
Configuration reference
File: ~/.flowsave/config.json — created by config init, edited by config set, readable via config show.
| Key | Required | Default | Description |
|---|---|---|---|
instanceUrl | ✓ | http://localhost:5678 | Base URL of the n8n instance |
apiKey | ✓ | — | n8n API key (n8n Settings → API). Masked in all output. |
containerName | — | unset | Docker container name. Enables credential backup/restore via docker exec. Optional — omit for workflow-only backups. |
backupDir | ✓ | ~/.flowsave/backups | Local snapshot storage. ~ is expanded. |
gitRemote | — | unset | Git remote URL for flowsave push. |
gitBranch | — | main | Branch pushed to. |
dashboardToken | — | unset | Reserved for agent/dashboard connectivity (upcoming). |
Security model
Encryption at rest
- Cipher: AES-256-GCM (authenticated encryption — tampering is detected, not just hidden)
- Key derivation: scrypt, cost N=16384, r=8, p=1 — deliberately slow against brute force
- Per-snapshot randomness: fresh 32-byte salt and 12-byte IV on every encryption
- Blob layout:
[salt 32B][iv 12B][authTag 16B][ciphertext]— one self-contained file,_credentials.enc.json
Passphrase policy
Enforced in core — no CLI path can bypass it:
- Minimum 12 characters
- At least one uppercase letter
- At least one lowercase letter
- At least one digit or special character
Validation happens inline in the prompt (immediate feedback) and again inside encrypt().
Passphrase lifecycle
- Entered twice (set + confirm) for
backupandmigrate; mismatch aborts before anything is written - Never stored, never logged, never in error messages, never in any persisted file
- Lost passphrase = unrecoverable credentials for that snapshot, by design. Workflows are unaffected.
What never leaves your machine
- Encrypted credential blobs are excluded from
flowsave pushvia.gitignore - No telemetry of any kind — the only network calls are to your own n8n instances and your own git remote
Storage layout
~/.flowsave/ ├── config.json # configuration (API key in plain text — local file) ├── index.json # snapshot index: id, timestamp, instanceUrl, sizeBytes └── backups/ # = backupDir (configurable) ├── .git/ # created by flowsave push (optional) ├── .gitignore # excludes _credentials.enc.json └── <id>/ # one directory per snapshot ├── meta.json # snapshot metadata (counts, versions, flags) ├── <Workflow Name>.json # one file per workflow (root = no folder) ├── <Folder>/… # subdirectories mirror n8n folders (Enterprise) ├── _credentials.enc.json # encrypted credential blob (optional) └── _credentials.meta.json # credential names+types, NO secrets (optional)
Workflow JSON files are the raw n8n export format — they round-trip losslessly and are readable in any editor or git UI.
Error handling & exit codes
Flowsave never shows a stack trace. Every error message is human-readable with an actionable hint when one exists.
| Situation | Message style | Hint |
|---|---|---|
| Invalid/missing config | The specific validation error | Run flowsave config init |
| Network failure (ECONNREFUSED, ETIMEDOUT, etc.) | Cannot reach n8n instance: <detail> | Run flowsave doctor |
| Wrong passphrase on restore | Decryption failure (GCM auth tag mismatch) | — |
| Anything else | The error's own message | — |
Exit code summary
0— success, nothing-to-do, or user answered "No" to a confirmation1— any validation failure, aborted passphrase confirmation, failed health check, or runtime error
Exit codes are stable and suitable for use in scripts and cron jobs.
Edition compatibility
| Feature | Community (free) | Enterprise |
|---|---|---|
| Workflow backup & restore | ✓ | ✓ |
| Credential backup (source) | ✓ requires Docker on source | ✓ |
| Credential restore — same-instance | ✓ docker exec + auto-prune stale | ✓ |
| Credential restore — cross-instance (API) | ✓ simple creds; OAuth may fail | ✓ |
| Credential restore — cross-instance (Docker) | ✓ with --target-container | ✓ |
| Folder structure in backup | ✗ | ✓ |
| Folder structure on restore | ✗ | ✓ |
The folder REST API is license-gated by n8n. Community backups lose nothing — the layout is simply flat.
Troubleshooting
"Cannot reach n8n instance"
Run flowsave doctor. It checks the URL, API key (distinguishes an invalid key from a network failure), Docker, and backup directory in one run.
"Permission denied" on credential backup (Linux)
Your user isn't in the docker group. Fix:
sudo usermod -aG docker $USER # then log out and back in
Doctor detects and prints exactly this fix.
OAuth credentials failed during cross-instance restore/migrate
Expected behavior on the API path — their exported data carries internal token state the n8n API rejects. Either re-add them manually in the target UI, or re-run with --target-container <name> if the destination container is locally accessible (the Docker path has no schema restrictions).
I forgot the passphrase for snapshot N
Credentials in that snapshot are unrecoverable — nothing is stored that could recover them, by design. Workflows are unaffected. Take a new backup with a new passphrase.
Re-running migrate/cross-instance restore duplicated my workflows
Documented behavior: cross-instance operations always create, never update. Compare first with flowsave diff, then clean up duplicates on the target manually.
flowsave push says nothing changed but I switched branches
It still pushes. The remote branch is created or updated even without a new commit.
Snapshot says "names not available — older snapshot"
Credential name metadata (_credentials.meta.json) was introduced after that snapshot was taken. The credentials themselves are intact and fully restorable.
Why doesn't my snapshot show the n8n version?
Version detection needs docker exec (the public REST API doesn't expose it). Configure containerName to record it.
Glossary
| Term | Definition |
|---|---|
| Snapshot | One point-in-time backup with a sequential integer ID. |
| Same-instance restore | Restore to the configured instance; updates workflows by ID. |
| Cross-instance restore | Restore to another instance (--to); always creates new workflows. |
| Docker path | Credential transport via docker exec + n8n CLI; handles all credential types. |
| API path | Credential transport via n8n REST API; no Docker needed, OAuth-limited. |
| Prune | Removal of consecutive content-identical snapshots. |
| Retention warning | Post-backup/migrate notice that the snapshot's credentials are locked to the entered passphrase. |