# alembic
the connective tissue of your network automation layer, co-evolved with ai
alembic is a data-model-first converger + loader for dcim/ipam systems. it defines a canonical, vendor-neutral ir (intermediate representation) and an engine that validates, plans, and applies changes to a backend. it supports multiple backend adapters, including NetBox, Nautobot, Infrahub, generic REST APIs, and PeeringDB (read-only).
## when not to use alembic
alembic is built to converge a vendor-neutral data model onto one or more dcim/ipam backends, and to migrate between them. it is probably the wrong tool when:
- **you have a single backend and a simple one-off load.** a pynetbox script or csv import is less machinery for the same result.
- **your infrastructure is ephemeral or event-driven.** alembic reconciles batches against a known desired state; it is not an event processor or a real-time sync daemon.
- **you need built-in approval, rbac, or audit workflows.** alembic is a cli that emits deterministic plans; wrap it in your own ci/review process, or use a platform that bundles those.
## how it compares
most tools manage the boxes in your infrastructure graph: the state inside a single system. alembic works on the edges, the correspondence between systems. it transforms data from a source of truth into a vendor-neutral model, validates it, and converges that model onto one or more backends. each alternative below owns a box; alembic connects them:
- **terraform + netbox provider**: covers the apply segment for a single backend, with hand-written hcl resources. alembic builds the desired state by transforming your source of truth into the ir, then applies it to multiple backends from that one model.
- **nautobot ssot / netbox diode**: cover ingestion into one system. alembic does the same source-to-infra load, but through a backend-neutral model with an explicit plan/diff, and can target several backends from it.
- **infrahub native sync**: infrahub is itself a source of truth with its own transform/sync into infrahub. alembic keeps the canonical model outside any single backend, so infrahub is one destination among several.
- **ansible (netbox.netbox collection)**: connects source data to a backend imperatively, task by task. alembic covers the same path with a typed ir, schema validation, and a deterministic create/update/delete plan.
- **pynetbox / custom scripts**: the same source-to-infra glue, hand-rolled. alembic provides the transformation, validation, stable uids across renames, and reproducible plans so you do not rebuild that each time.
## concepts
- **ir objects**: typed objects with stable `uid` (uuid), a `type`, a human `key`, and `attrs`.
- **relationships**: references are always by `uid`, never backend ids.
- **plan**: a deterministic list of operations (create/update/delete) generated by diffing desired ir against observed backend state.
- **state store**: maps `uid` to backend ids (integers or uuids) per type to keep renames stable; defaults to `.alembic/state.json` and can use postgres.
## quickstart
1) create an inventory file (yaml) describing your desired state. see `examples/inventory.yaml`.
2) validate:
```bash
cargo run -p alembic-cli -- validate -f examples/inventory.yaml
```
3) plan (writes a json plan file):
```bash
# netbox
NETBOX_URL=https://netbox.example.com NETBOX_TOKEN=... \
cargo run -p alembic-cli -- plan --backend netbox -f examples/inventory.yaml -o plan.json
# nautobot
NAUTOBOT_URL=https://nautobot.example.com NAUTOBOT_TOKEN=... \
cargo run -p alembic-cli -- plan --backend nautobot -f examples/inventory.yaml -o plan.json
# infrahub
INFRAHUB_URL=https://infrahub.example.com INFRAHUB_TOKEN=... \
cargo run -p alembic-cli -- plan --backend infrahub -f examples/inventory.yaml -o plan.json
```
4) apply:
```bash
NETBOX_URL=https://netbox.example.com NETBOX_TOKEN=... \
cargo run -p alembic-cli -- apply --backend netbox -p plan.json --allow-delete
```
5) optional: generate a django app from an inventory:
```bash
cargo run -p alembic-cli -- cast django -f examples/inventory.yaml -o ./out \
--project alembic_project --app alembic_app
```
## adapter coverage
netbox and nautobot adapters are schema-driven and resolve types dynamically via their content type/object type APIs.
the infrahub adapter uses graphql and can optionally provision missing types/fields by generating
an infrahub schema file and applying it during `apply`.
- any `type` with a REST endpoint can be observed and applied
- `attrs` are passed through using backend field names
- `ref`/`list_ref` fields are resolved via state mappings during apply
- custom fields and tags are mapped by adapters when supported by the backend
- netbox custom object types can be provisioned on apply when the netbox custom objects plugin is installed
relationships are validated strictly by schema and `uid` references.
## workspace layout
- `crates/alembic-core`: ir types, serde, validation primitives
- `crates/alembic-engine`: loader, graph validation, planning, state store
- `crates/alembic-adapter-registry`: adapter config + registry for the cli
- `crates/alembic-adapter-netbox`: netbox adapter
- `crates/alembic-adapter-nautobot`: nautobot adapter
- `crates/alembic-adapter-infrahub`: infrahub graphql adapter
- `crates/alembic-adapter-generic`: generic rest adapter
- `crates/alembic-adapter-peeringdb`: peeringdb adapter
- `crates/alembic-cli`: cli binary
## notes
- input files never contain backend ids.
- plans are deterministic and stable-sorted.
- deletes are gated behind `--allow-delete`.
## beyond the open core
the dcim/ipam and generic-rest adapters here are the open core. the same ir drives config and deployment across live and lab fabrics, plus monitoring, dns, and access, through a separate, commercial ops layer.
## documentation
- `docs/index.md`
- `docs/ir.md`
- `docs/inventory.md`
- `docs/map.md`
- `docs/engine.md`
- `docs/plan.md`
- `docs/state.md`
- `docs/cli.md`
- `docs/cast.md`
- `docs/netbox.md`
- `docs/nautobot.md`
- `docs/infrahub.md`
- `docs/external-adapters.md`
- `docs/development.md`
- `docs/case-studies/`