Deployment History
EnterpriseReview NetStacks deployment timelines, per-device results, rendered configs, backups, and logs via the Controller config API.
Overview
Every stack deployment in NetStacks — the act of rendering a stack and pushing the result to a set of devices — is recorded with full audit detail. Deployment history gives you a complete record of what was pushed, to which devices, by whom, and whether each device succeeded or failed.
Stacks, deployments, and deployment history are part of the NetStacks Controller. They are managed through the Controller config API under /api/config and the admin UI. The desktop application does not create or store deployment history.
The deployment pipeline runs in six stages and logs each one:
- Render — resolve variables and render the stack services into per-device configuration jobs.
- Pre-flight backup — pull the current config from every target device and store it as a versioned backup (this always runs).
- Push — apply the rendered config to each device over its transport (gNMI or NETCONF).
- Validate — compare the pushed config against the device state to confirm it took effect.
- Confirm — finalize confirmed commits where supported (or let them expire on drift), then update per-device status and write aggregate counts back to the deployment record.
- Sync — pull the full post-deploy config from each device for history.
Each deployment records the user who initiated it in the created_by field, so history doubles as an audit trail: you can always trace who deployed what stack, to which devices, and when.
What History Records
Deployment record
Each deployment creates one record holding the high-level status and aggregate counts. It references the stack it was created from via stack_id and stores the inputs used for the run.
id,org_id,stack_id,name— identity and the stack that was deployed.status— the deployment state (for examplecompleted,failed,rolling_back,rolled_back).target_devices,variable_values,device_overrides— the inputs used for this run.total_devices,succeeded_count,failed_count— aggregate per-device outcome counts.created_by— the user who started the deployment.started_at,completed_at,created_at,updated_at— lifecycle timestamps.
Per-device records
For each device in the deployment, a device-deployment record stores what was pushed and how it went:
service_name— the stack service that produced this config.status— the per-device outcome.rendered_config— the exact configuration text pushed to the device.target_pathandoperation— the config path and operation applied (for example a gNMI/NETCONF path withmerge,replace, orupdate).backup_config_id— a reference to the stored, versioned backup captured for this device before the push. The backup is a config version, not inline text; resolve it through the device config history.error_message— the failure description when the device failed.started_atandcompleted_at— per-device timing.
Deployment logs
The pipeline writes timestamped log entries as it runs. Each log entry has a level (info, warn, or error), a human-readable message, and an optional device_id so stage-level events (no device) and per-device events can be distinguished. Logs do not carry a structured details object — they are level plus message.
Reviewing History
Walk through reviewing deployment history to understand what was deployed, when, and whether it succeeded.
Step 1: Find the deployment
In the Controller admin UI, open the stack and review its deployments, or list them through the API (see the next section). Deployments are returned newest first and can be filtered by stack_id.
Step 2: Open a deployment
Open a deployment to see its overall status, aggregate counts, timing, and the full per-device breakdown. The detail view always includes the device records; you do not need to request them separately.
Step 3: Inspect per-device results
For each device, review the status. For succeeded devices, read the rendered_config to confirm exactly what was pushed and the operation/target_path that was applied. For failed devices, read the error_message.
Step 4: Review the pre-flight backup
Every device record carries a backup_config_id pointing at the versioned config pulled before the push. Resolve that backup through the device's config history to compare the prior state against the rendered config and see exactly what changed.
Step 5: Read the logs
Fetch the deployment logs to follow the run stage by stage: render, pre-flight backup, push, validate, confirm, and sync. Filter by level to focus on warn and error entries when diagnosing a failure.
Step 6: Roll back if needed
A completed or failed deployment can be rolled back. Rollback restores devices that have a stored backup — over gNMI it re-pushes the pre-flight config; for NETCONF confirmed-commit it relies on the auto-rollback timer. The rollback is itself logged into the same deployment.
API Examples
All history endpoints live under the Controller config API at /api/config/deployments and require an authenticated bearer token.
List deployments
# List deployments for your organization (newest first).
# Optional query params: stack_id, limit, offset.
curl -s -H "Authorization: Bearer $TOKEN" \
"https://controller.example.net/api/config/deployments" | jq .
# Filter to a single stack
curl -s -H "Authorization: Bearer $TOKEN" \
"https://controller.example.net/api/config/deployments?stack_id=$STACK_ID&limit=20" | jq .Deployment list response
[
{
"id": "f8b3c1a2-0000-4000-8000-000000000001",
"org_id": "0a1b2c3d-0000-4000-8000-000000000000",
"stack_id": "a7c4e2d1-0000-4000-8000-000000000000",
"name": "DC1-Core-Baseline-2026-01",
"status": "completed",
"total_devices": 3,
"succeeded_count": 3,
"failed_count": 0,
"created_by": "11111111-2222-3333-4444-555555555555",
"started_at": "2026-01-15T10:05:01Z",
"completed_at": "2026-01-15T10:05:45Z",
"created_at": "2026-01-15T10:05:00Z",
"updated_at": "2026-01-15T10:05:45Z"
},
{
"id": "c2d9e4f5-0000-4000-8000-000000000002",
"org_id": "0a1b2c3d-0000-4000-8000-000000000000",
"stack_id": "a7c4e2d1-0000-4000-8000-000000000000",
"name": "DC1-Core-Baseline-2026-01",
"status": "failed",
"total_devices": 3,
"succeeded_count": 2,
"failed_count": 1,
"created_by": "11111111-2222-3333-4444-555555555555",
"started_at": "2026-01-10T14:30:02Z",
"completed_at": "2026-01-10T14:31:15Z",
"created_at": "2026-01-10T14:30:00Z",
"updated_at": "2026-01-10T14:31:15Z"
}
]Get a deployment with per-device details
# The detail endpoint always returns the device records inline.
curl -s -H "Authorization: Bearer $TOKEN" \
"https://controller.example.net/api/config/deployments/$DEPLOYMENT_ID" | jq .Deployment detail with device results
{
"id": "f8b3c1a2-0000-4000-8000-000000000001",
"org_id": "0a1b2c3d-0000-4000-8000-000000000000",
"stack_id": "a7c4e2d1-0000-4000-8000-000000000000",
"name": "DC1-Core-Baseline-2026-01",
"status": "completed",
"total_devices": 3,
"succeeded_count": 3,
"failed_count": 0,
"created_by": "11111111-2222-3333-4444-555555555555",
"started_at": "2026-01-15T10:05:01Z",
"completed_at": "2026-01-15T10:05:45Z",
"created_at": "2026-01-15T10:05:00Z",
"updated_at": "2026-01-15T10:05:45Z",
"devices": [
{
"id": "dd000001-0000-4000-8000-000000000001",
"deployment_id": "f8b3c1a2-0000-4000-8000-000000000001",
"device_id": "de000001-0000-4000-8000-000000000001",
"service_name": "NTP Configuration",
"status": "completed",
"rendered_config": "{\"openconfig-system:ntp\":{\"config\":{\"enabled\":true}}}",
"target_path": "/system/ntp",
"operation": "merge",
"backup_config_id": "bc000001-0000-4000-8000-000000000001",
"error_message": null,
"started_at": "2026-01-15T10:05:02Z",
"completed_at": "2026-01-15T10:05:08Z"
},
{
"id": "dd000002-0000-4000-8000-000000000002",
"deployment_id": "f8b3c1a2-0000-4000-8000-000000000001",
"device_id": "de000002-0000-4000-8000-000000000002",
"service_name": "NTP Configuration",
"status": "completed",
"rendered_config": "{\"openconfig-system:ntp\":{\"config\":{\"enabled\":true}}}",
"target_path": "/system/ntp",
"operation": "merge",
"backup_config_id": "bc000002-0000-4000-8000-000000000002",
"error_message": null,
"started_at": "2026-01-15T10:05:03Z",
"completed_at": "2026-01-15T10:05:09Z"
}
]
}Get deployment logs
curl -s -H "Authorization: Bearer $TOKEN" \
"https://controller.example.net/api/config/deployments/$DEPLOYMENT_ID/logs" | jq .Deployment log entries
[
{
"id": "10000001-0000-4000-8000-000000000001",
"deployment_id": "f8b3c1a2-0000-4000-8000-000000000001",
"device_id": null,
"level": "info",
"message": "Stage 1/6: Rendering stack templates",
"created_at": "2026-01-15T10:05:01Z"
},
{
"id": "10000002-0000-4000-8000-000000000002",
"deployment_id": "f8b3c1a2-0000-4000-8000-000000000001",
"device_id": null,
"level": "info",
"message": "Stage 2/6: Pre-flight backup",
"created_at": "2026-01-15T10:05:02Z"
},
{
"id": "10000003-0000-4000-8000-000000000003",
"deployment_id": "f8b3c1a2-0000-4000-8000-000000000001",
"device_id": "de000001-0000-4000-8000-000000000001",
"level": "info",
"message": "Structured backup saved (v7)",
"created_at": "2026-01-15T10:05:03Z"
},
{
"id": "10000004-0000-4000-8000-000000000004",
"deployment_id": "f8b3c1a2-0000-4000-8000-000000000001",
"device_id": null,
"level": "info",
"message": "Stage 3/6: Pushing configurations",
"created_at": "2026-01-15T10:05:04Z"
},
{
"id": "10000005-0000-4000-8000-000000000005",
"deployment_id": "f8b3c1a2-0000-4000-8000-000000000001",
"device_id": "de000001-0000-4000-8000-000000000001",
"level": "info",
"message": "Configuration applied successfully",
"created_at": "2026-01-15T10:05:08Z"
}
]Roll back a deployment
# Only 'completed' or 'failed' deployments can be rolled back.
# Devices that have a stored backup are restored.
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
"https://controller.example.net/api/config/deployments/$DEPLOYMENT_ID/rollback" | jq .Questions & Answers
- Q: Which endpoints expose deployment history?
- A: Three read endpoints under
/api/config/deployments:GET /api/config/deploymentslists deployments (filterable bystack_id, withlimit/offset),GET /api/config/deployments/{id}returns one deployment with its device records inline, andGET /api/config/deployments/{id}/logsreturns the log stream. - Q: Do I need a flag to get per-device results?
- A: No. The detail endpoint always returns the deployment plus its
devicesarray. There is noinclude_devicesquery parameter. - Q: When is a backup captured?
- A: Always. Stage 2 of the pipeline pulls the current config from every target device before any change is pushed, regardless of any flag. The structured backup is stored as a versioned device config and linked to the device record through
backup_config_id; a CLI backup is also pulled where applicable. - Q: Is the backup config stored inline on the device record?
- A: No. The device record stores a
backup_config_id— a reference to a stored config version — not inline backup text. Resolve that ID through the device's config history to read the backed-up config. - Q: What do deployment logs contain?
- A: Each log entry has a
level(info,warn, orerror), amessage, an optionaldevice_id, and acreated_attimestamp. Logs do not include a structured details object. - Q: How do I compare what changed on a device?
- A: Compare the device record's
rendered_config(what was pushed) against the backup referenced bybackup_config_id(the prior state captured pre-flight). Together they show exactly what the deployment changed. - Q: Can I roll back from history?
- A: Yes, for
completedorfaileddeployments. Rollback restores devices that have a stored backup — gNMI re-pushes the pre-flight config, while NETCONF confirmed-commit relies on the auto-rollback timer. The rollback actions are logged into the same deployment.
Troubleshooting
Deployment not appearing in the list
Deployments are scoped to your organization. Verify your token belongs to the same org that owns the stack, and that you are not filtering by a different stack_id. The list is paginated — raise limit or adjust offset if you are looking for older runs.
A device has no usable backup to compare
The pre-flight backup runs for every deployment, but it can still fail for an individual device — for example if the device was unreachable when the backup was pulled. In that case the logs contain an error entry for that device at the pre-flight stage and backup_config_id may be unset. Check the logs for the "Pre-flight backup failed" message.
Logs look sparse
Logs are level plus message only — there is no structured details payload. Stage-level entries have a null device_id; per-device entries carry the device ID. To diagnose a failure, read the error entries and the corresponding device record's error_message.
Rollback rejected
Rollback is only allowed for deployments whose status is completed or failed. A deployment still in progress, or already rolling_back/rolled_back, cannot be rolled back again. Wait for the run to finish, then retry.
Deployment duration seems long
Compare the per-device started_at and completed_at timestamps to find the slowest device. Long durations usually come from slow device transport responses or large config payloads. The pre-flight backup stage also adds time because it pulls current config from every target before pushing.
Related Features
Learn more about related NetStacks features:
- Stack Instances — Deploy and manage the instances that generate deployment history.
- Creating Stacks — Build the stacks that deployments are created from.
- Variable Overrides — The per-device overrides recorded with each deployment.
- Template Versioning — Track template changes that lead to redeployments.
- Audit Logs — Organization-wide audit trail for administrative actions.