Neo4j Policy-Based Backup / Restore
π Documentation site: https://lex00.github.io/neo4j-backup/
Validated, policy-driven backup and restore for self-hosted Neo4j Enterprise β as
Dagster / Airflow pipelines or a scheduler-agnostic
CLI, exercised end to end against a real Neo4j Enterprise + object-store
stack. The database instances stay agentless: restore is pure Cypher over Bolt
(seed-from-URI + alias swap), and backup is neo4j-admin run from a separate runner.
The deliverable is the orchestration β a Dagster code location,
an equivalent Airflow DAG set, and a scheduler-agnostic
neo4j-backup CLI for CI/cron, all over one shared engine
(neo4j_backup_core); pick whichever you run β and its validation. The pipeline is
object-store-agnostic β it takes a
configurable bucket and writes <group>/<slug>/<physical>/ prefixes; teams bring their
own bucket / IAM / KMS structure and adapt the runner placement to their environment
(see Adapting). Cloud provisioning is out of scope.
Configuration: every config, and exactly where it lives
There are four configs β no more. Two are files in this repo you copy/edit; two live in your Dagster deployment. That's the complete list:
| Config | Exact location | Whose file | What it controls |
|---|---|---|---|
| 1. Backup policy | policies/demo.yaml β copy to policies/<you>.yaml |
this repo (you edit) | which databases/groups to back up, their aliases, schedule tiers, retention |
| 2. Environment variables | your code location's environment β locally .env (from .env.example); in prod, your Dagster env/secrets |
you set them | point at your Neo4j (NEO4J_BOLT_URI, NEO4J_PASSWORD), your bucket (BACKUP_BUCKET, AWS_REGION), the backup source, and NEO4J_BACKUP_POLICY = path to #1 |
| 3. Concurrency lanes | orchestrator/deploy/dagster.yaml β merge into your instance's dagster.yaml |
this repo (copy the lines) | how many full vs diff backups run at once |
| 4. Code-location entry | your workspace.yaml (OSS) or dagster_cloud.yaml (Dagster+) |
your Dagster repo | registers this package with Dagster |
Defaults: only NEO4J_PASSWORD is required (everything else has sane defaults), so the
only file you must write is #1, the policy β see the Policy page for
a complete annotated example and the full field reference.
What to put in each β the step-by-step Configuration walkthrough (in the orchestrator README).
Who this is for
Ops / platform / data-engineering teams that:
- self-host Neo4j Enterprise β online backup and seed-from-URI restore are Enterprise features (Community has only offline dump/load);
- run Dagster or Airflow and want backups as a code location / DAG set (the two
adapters are interchangeable) β or neither: the scheduler-agnostic
neo4j-backupCLI drives the same core from CI or cron (see CI recipes); - store artifacts in an S3-compatible object store (or Azure Blob / GCS,
CLOUD=azure|gcp); - are comfortable operating
neo4j-adminand Cypher; and - have apps connect via Neo4j aliases, or are willing to migrate to them (the restore model is an alias swap β see orchestrator/README.md).
Not for: Neo4j Community (no online backup), Neo4j Aura (managed, has its own snapshots), or anyone wanting a turnkey/GUI product. This is tooling to adapt, not an appliance β deployment specifics are yours.
Front-ends β pick by what you already run
One shared core (neo4j_backup_core), four front-ends over it. The policy, storage layout, and
every seam are identical across them. Three schedule the cadence; the MCP server drives the
exceptions.
| Front-end | Pick when |
|---|---|
| Dagster / Airflow | you run an orchestrator and want concurrency lanes, dynamic policy fan-out, retries with backoff, and run-level observability |
neo4j-backup CLI + CI / cron |
a small fleet with no orchestrator β you accept CI's limits (scratch, best-effort cron, no orchestration) for a much lighter setup |
| MCP server | an operator drives DR/status through an agent β read-only by default, guarded mutations; the exceptions, not the cadence |
The CLI is not a co-equal orchestrator: in CI you get scheduling and a serialized lane, not the fan-out/observability the adapters give. It is the lightweight option, with the trade-offs stated in CI.md.
No lock-in
It is just orchestration around standard pieces:
- Standard
neo4j-adminbackups β ordinary.backupartifacts in your bucket, restorable withneo4j-admin/ seed-from-URI without this package. - Standard Cypher for restore (
CREATE DATABASE β¦ seedURI,ALTER ALIAS) β nothing proprietary, no custom format. - Plain-YAML policy, standard Neo4j aliases, a standard Dagster code location.
- Your object store, IAM, KMS, and placement β the pipeline is object-store-agnostic.
Remove the tooling and you are left with standard Neo4j backups in your own bucket.
Configurable & resilient (optional)
Beyond the four configs, the pipeline exposes optional knobs β all default to the validated behaviour, and the full list is the env table. Every seam lives in the shared core, so both adapters inherit it:
- Credentials β
SECRET_PROVIDERpulls the Neo4j password from AWS Secrets Manager (or a custom provider), resolved per connection so rotation needs no redeploy. - Bolt resilience β automatic bounded retry on transient cluster failures (leader re-election, dropped sessions, expired tokens), classified by Neo4j status code, not message text.
- Seed topology β declare
TOPOLOGY n PRIMARIES m SECONDARIESin policy so a restore comes back with its intended redundancy, not the DBMS default. - Cypher version β
SEED_CYPHER_VERSION=5on a Cypher-5 cluster (adds the requiredexistingData; Cypher 25 omits it). - Cutover β default alias swap, or repoint an external router/proxy via
CUTOVER_STRATEGY=external. - Path layout β bring your own object-store key scheme via
PATH_LAYOUT. - Policy source β point
NEO4J_BACKUP_POLICYats3://β¦(not just a local file) to change what/whom is backed up without a redeploy; aPOLICY_CACHE_TTLcache with last-known-good fallback keeps it safe. Getting the file to S3 is your deployment's job. For an authenticated endpoint (Vault, config API), override the fetch withPOLICY_LOADER=module.callable. - Encryption on every write β
S3_SSEsets the SSE-KMS header on the pipeline's boto3 PUT/COPY;BACKUP_UPLOAD=pipelineroutes neo4j-admin's writes through boto3 too (it has no SSE setting of its own), so strict buckets that deny header-less PutObject work end to end.
Command-line interface
For teams without an orchestrator, neo4j-backup runs the same policy-driven operations from a
shell β CI, cron, or by hand. It is a third adapter over neo4j_backup_core, subprocess-only, and
installs with the base package (not on PyPI β pin a tag, or vendor and pip install ./orchestrator):
pip install "neo4j-backup-dagster @ git+https://github.com/lex00/neo4j-backup@v0.4.0#subdirectory=orchestrator"
neo4j-backup --json targets # what the policy covers
neo4j-backup --json backup demo # back up a group
neo4j-backup --json verify demo # consistency-check the latest backups
neo4j-backup --json restore demo --dry-run # preview the plan; add --confirm to apply
| Command | Does | Mutates? |
|---|---|---|
targets |
list the policy's group/member targets | no |
backup <group> [--kind AUTO\|FULL\|DIFF] |
back up every database in a group | writes artifacts |
verify <group> |
consistency-check the latest backups | no |
aggregate <group> |
collapse each chain into a recovered full, in place | yes |
restore <group> [--until <iso>] [--replace] |
restore a group (alias-swap / by-name, PITR) | yes |
prune |
delete backups past each group's retention | yes |
metadata export / metadata restore [--key <k>] |
DBMS metadata as replayable Cypher | export writes / restore mutates |
system-backup |
binary FULL backup of the system database |
writes artifacts |
Every command speaks the CLI contract: --json emits one result envelope,
exit codes gate CI, and the mutating commands require --confirm (preview with --dry-run, which
reports the blast radius). Schedule it with the CI recipes; point an agent at it with
AGENTS.md.
Documentation map
| Doc | What |
|---|---|
| POLICY.md | The backup policy β a complete annotated example + every field. |
| CLI-CONTRACT.md | The neo4j-backup CLI contract β JSON envelope, exit codes, and the dry-run/confirm guards. |
| CI.md | Scheduling the CLI from CI (GitHub / GitLab / Forgejo) β execution model, secrets, caveats. |
| AGENTS.md | Driving the CLI with a coding/ops agent β safety rules, command surface, worked prompts (no server needed). |
| MCP.md | The optional operator MCP server β read-only by default, guarded mutations (confirm + dry-run + verify-before-drop). |
| RECOVERY.md | The three recovery modes (full / differential / PITR) with exact Cypher. |
| IMPORT.md | Bulk import: build a seed .backup off-cluster from raw CSV/Parquet on ephemeral hardware. |
| DESIGN.md | The architecture: execution surface, db-group model, naming authority, encryption, runner resources, Dagster pipeline, restore + verification, and the configurable seams + resilience (Β§11.5). The main read. |
| STACK.md | The local stack and how to run it (just fresh β backup β restore). |
| CHANGELOG.md | Notable changes per version (Keep a Changelog / SemVer). |
| RELEASING.md | Versioning + release process (git-tag, vendored source; no PyPI). |
| orchestrator/ | The neo4j_backup_dagster package (the Dagster orchestration), with its own README + deploy/. Includes the configuration walkthrough. |
| airflow/ | The equivalent Airflow 3.x DAG set over the same core β the DAG inventory, pools-as-lanes, dag.test() validation, and the DagsterβAirflow concept map. |
Diagrams
Graphviz sources in diagrams/; render to SVG with just diagrams
(requires graphviz).
| Diagram | Shows |
|---|---|
| architecture | Execution surface β agentless instances, runner does backup, Cypher does restore |
| storage-layout | <group>/<slug>/<physical>/ layout and per-store chains |
| restore-cutover | Seed a fresh physical β verify β move the alias |
| dagster-pipeline | Assets, jobs, schedules, sensor |
| naming | alias β slug β physical naming authority |
Quick start (local)
just fresh # boot the stack + demo group from scratch
just backup demo # online backup to object storage (SSE-KMS)
just restore demo # seed-from-URI restore + alias swap
just demo-pitr # point-in-time recovery over a real chain
just diagrams # render the diagrams to SVG
The Dagster package is validated against this same stack β see orchestrator/README.md.
What's validated
The full loop runs four ways: shell (just), Dagster (orchestrator/smoke_*.py), Airflow
(airflow/smoke_*.py), and PITR. Backups are SSE-KMS encrypted, restore reads them via seed-from-URI over Bolt,
verification consistency-checks artifacts non-destructively, and the per-store layout
makes differential chains correct. RUNNER_MODE=k8s is validated on k3d for both adapters
(just k3d-smoke for Dagster, just airflow-k8s-smoke for Airflow). Decisions locked and
open risks are in DESIGN.md Β§13.
CI & docs site
- CI (
.github/workflows/ci.yml): naming parity (naming.py==naming.sh), Dagster definitions validation, and the Airflow DAG import-error check on every push β no Docker/Neo4j needed. - Docs (
.github/workflows/pages.yml): this documentation + rendered diagrams publish to GitHub Pages. One-time: repo Settings β Pages β Source = "GitHub Actions". Build locally withjust docs(needspip install mkdocs-material).