Deterministic, idempotent TypeScript CLI to plan and apply n8n workflow deployments from DEV -> PROD.
This project is licensed under Business Source License 1.1. Professional
services and internal business use are allowed, including paid n8n consulting
and workflow delivery, but creating or monetizing a product, hosted service,
SaaS, white-label, OEM, or commercial bundle based substantially on this
project is not allowed. See LICENSE.
This is the fastest way to get ndeploy running for any user on macOS, Linux, or Ubuntu servers.
- Node.js
>= 18 - npm
- API access to DEV and PROD n8n instances
Check your Node version:
node -vClone the repository, install dependencies, build the CLI, and link it globally:
git clone https://github.com/nachokhan/n8n-ndeploy.git ndeploy
cd n8n-ndeploy
npm install
npm run build
chmod +x dist/index.js
sudo npm link
ndeploy --helpIf ndeploy --help prints the command list, the CLI is ready to use.
If the project is already installed on a server, update it like this:
cd /your/ndeploy/folder
git pull
npm run build
chmod +x dist/index.js
sudo npm link
ndeploy --helpThis is enough after pulling new changes, even on Ubuntu, as long as the server is running Node.js 18 or newer.
Create .env (or copy from .env.example):
N8N_DEV_URL=http://localhost:5678
N8N_DEV_API_KEY=dev_api_key
N8N_PROD_URL=http://localhost:5679
N8N_PROD_API_KEY=prod_api_key
# Optional fallback for credentials fill:
# N8N webhook endpoint that returns credential data by requested ids
N8N_DEV_CREDENTIAL_EXPORT_URL=
# Bearer token for that endpoint
N8N_DEV_CREDENTIAL_EXPORT_TOKEN=
# Optional fallback when fetching target credential snapshots
N8N_PROD_CREDENTIAL_EXPORT_URL=
N8N_PROD_CREDENTIAL_EXPORT_TOKEN=Run:
ndeploy --helpThen you can start with:
ndeploy init <workflow_id_dev> [project_root]If this app was useful to you, you can buy me a flat white or expresso as a thank you.
- English (official):
README.md - Spanish (secondary):
README.es.md - German:
README.de.md
- Recursively discovers workflow dependencies:
- sub-workflows
- credentials
- data tables
- Generates a reproducible deployment plan JSON.
- Applies the plan in PROD with
DEV_ID -> PROD_IDmapping. - Patches internal workflow references without global string replacements.
- Auto-publishes sub-workflows when needed.
- Never auto-publishes the root workflow (manual human action only).
ndeploy init <workflow_id_dev> [project_root]Creates the project directory from the DEV workflow name and initializes project.json.
Optional project_root lets you choose where that folder is created (default: current directory).
Use --force to re-initialize metadata if the target project already exists.
ndeploy plan <project>Uses the root workflow configured in <project>/project.json.
Creates:
<project>/plan.json<project>/reports/plan_summary.json
If plan.json already exists, it's backed up as plan_backup_<timestamp>.json.
ndeploy init stores root workflow information in project.json:
plan.root_workflow_id_devplan.root_workflow_nameplan.updated_at
ndeploy apply <project>Executes the plan in PROD (credentials, data tables, workflows). Writes:
<project>/reports/deploy_result.json<project>/reports/deploy_summary.json
If deployment fails mid-run, partial result files are still written.
Force workflow updates even when PROD already matches:
ndeploy apply <project> --force-updatendeploy publish <workflow_id_prod>Manual publish command for root workflow (or any workflow) in PROD.
ndeploy info <project>Shows project status in JSON:
project.jsonmetadataplan.json/reports/plan_summary.json/credentials_manifest.jsonpresence and key metadatareports/deploy_result.json/reports/deploy_summary.jsonpresence and key counters
Optional:
ndeploy info <project> --output <file_path>ndeploy remove --workflows <ids|all> --credentials <ids|all> --data-tables <ids|all>Removes selected resources from PROD.
- IDs use CSV format:
id1,id2,id3 - Alias:
--datatables(same as--data-tables) - Shortcut for everything:
--all --archived-workflowslimits workflow deletion to archived workflows only- Confirmation behavior:
- with
--yes: executes immediately - without
--yes: asks to typeyesinteractively in console
- with
Examples:
ndeploy remove --workflows 12,18 --yes
ndeploy remove --workflows all --archived-workflows --yes
ndeploy remove --credentials all --data-tables all
ndeploy remove --all --yesndeploy orphans <project> --side <source|target>Lists entities not referenced by any non-archived workflow and prints pretty JSON.
--sideis required:source-> usesN8N_DEV_*target-> usesN8N_PROD_*
- Entity filters:
--workflows--credentials--data-tables(alias:--datatables)--all
- If no entity filter is provided, it defaults to
--all. - Default output file (if
--outputis omitted):<project>/reports/orphans_<side>.json
Examples:
ndeploy orphans <project> --side target
ndeploy orphans <project> --side source --credentials
ndeploy orphans <project> --side target --workflows --datatablesndeploy dangling-refs <project> --side <source|target>Lists workflows that reference entities which no longer exist.
--sideis required:source-> usesN8N_DEV_*target-> usesN8N_PROD_*
- Reference filters:
--workflows--credentials--data-tables(alias:--datatables)--all
- If no reference filter is provided, it defaults to
--all. - Alias command:
ndeploy dangling - Default output file (if
--outputis omitted):<project>/reports/dangling_<side>.json
Examples:
ndeploy dangling-refs <project> --side target
ndeploy dangling <project> --side source --credentials
ndeploy dangling-refs <project> --side target --workflows --datatablesndeploy credentials fetch <project>Fetches full source/target snapshots for the current project dependency graph and writes:
<project>/credentials_source.json<project>/credentials_target.json
Optional:
ndeploy credentials fetch <project> --side source
ndeploy credentials fetch <project> --side target
ndeploy credentials fetch <project> --side bothndeploy credentials merge-missing <project>Creates or updates <project>/credentials_manifest.json by adding only credentials that are still missing from the editable manifest.
- Existing manifest entries are never overwritten.
--side source: seed missing entries fromcredentials_source.json.--side target: seed missing entries fromcredentials_target.json.--side both(default): use target first, then source as fallback.
ndeploy credentials compare <project>Compares credentials_source.json and credentials_target.json and reports:
identicaldifferentmissing_in_sourcemissing_in_targettype_mismatch
Optional:
ndeploy credentials compare <project> --format table
ndeploy credentials compare <project> --strictndeploy credentials validate <project>Validates required fields in one credential artifact at a time.
The default side is manifest.
Optional:
ndeploy credentials validate <project> --side source
ndeploy credentials validate <project> --side target
ndeploy credentials validate <project> --side manifest
ndeploy credentials validate <project> --side all --strict
ndeploy credentials validate <project> --output <file_path>ndeploy init <workflow_id_dev> [project_root]ndeploy plan <project>- Review
reports/plan_summary.json(andplan.jsonif needed). - Fetch snapshots:
ndeploy credentials fetch <project> - Compare source and target:
ndeploy credentials compare <project> - Merge missing entries into the manifest:
ndeploy credentials merge-missing <project> - Review/adjust
credentials_manifest.jsonfor PROD values. - Validate the manifest:
ndeploy credentials validate <project> --side manifest --strict ndeploy apply <project>- Review
reports/deploy_summary.json(andreports/deploy_result.jsonif needed). - Human/manual publish of root workflow:
ndeploy publish <root_workflow_id_prod>
- Idempotency:
- Resources are matched in PROD by name whenever possible.
- Credentials:
credentials_source.jsonandcredentials_target.jsonare fetched snapshots.credentials_manifest.jsonis the editable deploy manifest.ndeploy credentials merge-missingonly adds missing entries and never overwrites manual edits.ndeploy credentials compareis informational and does not modify files.
- Data tables:
- Created/mapped by name.
- Schema mismatch adds warnings in the plan.
- Workflows:
- Write payloads are sanitized to comply with n8n public API schema.
- Before execution, DEV freshness is validated for all workflow actions (
payload.checksum). - Workflow actions include informative
observabilityfields in the plan:prod_comparison_at_plan:equal|different|unknown|not_applicablecomparison_reason: reason for the observed result at plan generation time.
- Plan observability is a point-in-time snapshot;
applyremains the source of truth for finalUPDATEvsSKIP. - Equivalence comparison ignores non-functional metadata (for example
node.position,node.id,credentials.*.name,staticData) to reduce false positives. UPDATEactions are skipped when normalized PROD content is already equivalent.--force-updatedisables skip logic and always executes workflow updates.- ID patching targets:
node.credentials.*.idparameters.workflowIdparameters.dataTableId/parameters.tableIdsettings.errorWorkflow
- Publishing policy:
- Sub-workflows can be auto-published during
apply. - Root workflow is never auto-published.
- Sub-workflows can be auto-published during
Detailed step logging is included:
- Plan:
[PLAN][..] - Deploy:
[DEPLOY][VAL][..]and[DEPLOY][RUN][..] - API client:
[N8N_CLIENT]
This makes failures traceable by phase, entity, and API response context.
npm run dev -- --help
npm run typecheck
npm run buildsrc/
cli/ # create/plan/apply/publish/info/remove/orphans/dangling commands
services/ # API, planning, deploy, transforms
types/ # Zod schemas + TS types
utils/ # env, logger, hash, file helpers
errors/ # ApiError / DependencyError / ValidationError
must have required property 'connections':- Plan was generated with incomplete workflow payload; regenerate plan.
must NOT have additional properties:- Workflow/settings payload includes unsupported fields.
referenced workflow ... is not published:- A called sub-workflow in PROD is not published yet.
405 GET method not allowedon credentials:- n8n does not support
GET /credentials/{id}; use list + resolve.
- n8n does not support

