Stacks API
Deploy multi-template configuration stacks to devices with variable overrides, deployment tracking, and rollback support.
Overview
The Stacks API lets you combine multiple configuration templates into a single deployable unit called a stack. Stacks define a set of templates with default variable values, and stack instances assign those templates to specific devices with optional per-device variable overrides.
Use stacks to deploy consistent configurations across your network: a “DC1 Edge Router” stack might combine VLAN, BGP, and ACL templates and deploy them to every edge router with device-specific values for router IDs, neighbor IPs, and ASNs.
Stack deployments follow a state machine: pending → validating → in_progress → completed or failed. Monitor progress via the status endpoint.
How It Works
Stack Templates
A stack template references one or more configuration templates and defines default variable values. Think of it as a blueprint for a deployment.
Stack Instances
A stack instance is a concrete deployment of a stack template to a set of devices. Each instance tracks which devices are targeted and any per-device variable overrides.
Variable Override Precedence
Variables are resolved in this order (highest priority first):
- Device-specific overrides (set per device in the instance)
- Stack template defaults (set when creating the stack)
- Template defaults (defined in the config template itself)
Deployment States
| State | Description |
|---|---|
pending | Instance created, not yet deployed |
validating | Templates being rendered and validated |
in_progress | Configuration being pushed to devices |
completed | All devices configured successfully |
failed | One or more devices failed |
Step-by-Step Guide
1. Authenticate
Obtain a JWT token from the Authentication API.
2. Create Config Templates
Ensure the individual configuration templates exist via the Templates API.
3. Create a Stack Template
Combine templates into a stack with default variable values.
4. Create a Stack Instance
Assign devices and set per-device variable overrides.
5. Deploy
Trigger deployment and monitor the state transitions.
6. Monitor Progress
Poll the status endpoint to track per-device completion.
Code Examples
Create a Stack Template
curl -X POST https://netstacks.example.net/api/stacks \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"name": "DC1 Edge Router Config",
"description": "Standard edge router config: VLANs + BGP + ACLs",
"template_ids": [
"b2c3d4e5-f6a7-8901-bcde-f23456789012",
"a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"c3d4e5f6-a7b8-9012-cdef-345678901234"
],
"variables": {
"site": "dc1",
"local_asn": "65001",
"acl_name": "EDGE-INBOUND"
}
}'
# Response (201 Created):
# {
# "id": "d4e5f6a7-b8c9-0123-defg-456789012345",
# "name": "DC1 Edge Router Config",
# "template_ids": ["b2c3...", "a1b2...", "c3d4..."],
# "variables": {"site": "dc1", "local_asn": "65001", "acl_name": "EDGE-INBOUND"},
# "created_at": "2026-03-10T16:00:00Z"
# }import requests
base_url = "https://netstacks.example.net/api"
headers = {"Authorization": "Bearer eyJhbGciOiJIUzI1NiIs..."}
# Create a stack template
stack = requests.post(f"{base_url}/stacks", headers=headers, json={
"name": "DC1 Edge Router Config",
"description": "Standard edge router config: VLANs + BGP + ACLs",
"template_ids": [
"b2c3d4e5-f6a7-8901-bcde-f23456789012",
"a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"c3d4e5f6-a7b8-9012-cdef-345678901234"
],
"variables": {
"site": "dc1",
"local_asn": "65001",
"acl_name": "EDGE-INBOUND"
}
})
stack_data = stack.json()
print(f"Created stack: {stack_data['id']}")const BASE_URL = "https://netstacks.example.net/api";
const headers = {
Authorization: "Bearer eyJhbGciOiJIUzI1NiIs...",
"Content-Type": "application/json",
};
const stackResp = await fetch(`${BASE_URL}/stacks`, {
method: "POST",
headers,
body: JSON.stringify({
name: "DC1 Edge Router Config",
description: "Standard edge router config: VLANs + BGP + ACLs",
template_ids: [
"b2c3d4e5-f6a7-8901-bcde-f23456789012",
"a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"c3d4e5f6-a7b8-9012-cdef-345678901234",
],
variables: {
site: "dc1",
local_asn: "65001",
acl_name: "EDGE-INBOUND",
},
}),
});
const stack = await stackResp.json();
console.log(`Created stack: ${stack.id}`);Create Instance with Device Overrides and Deploy
# Create an instance assigning devices with per-device overrides
curl -X POST https://netstacks.example.net/api/stacks/d4e5f6a7-b8c9-0123-defg-456789012345/instances \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"name": "dc1-edge-deploy-2026-03",
"device_ids": [
"f47ac10b-58cc-4372-a567-0e02b2c3d479",
"e36bd20a-47bb-3261-9456-1d13a1b2c368"
],
"device_overrides": {
"f47ac10b-58cc-4372-a567-0e02b2c3d479": {
"router_id": "10.0.1.1",
"neighbor_ip": "10.0.1.2",
"vlan_id": "100"
},
"e36bd20a-47bb-3261-9456-1d13a1b2c368": {
"router_id": "10.0.2.1",
"neighbor_ip": "10.0.2.2",
"vlan_id": "200"
}
}
}'
# Deploy the instance
curl -X POST https://netstacks.example.net/api/stacks/d4e5f6a7-b8c9-0123-defg-456789012345/instances/inst-uuid/deploy \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
# Response:
# { "state": "validating", "started_at": "2026-03-10T16:05:00Z" }# Create instance with per-device variable overrides
instance = requests.post(
f"{base_url}/stacks/{stack_data['id']}/instances",
headers=headers,
json={
"name": "dc1-edge-deploy-2026-03",
"device_ids": [
"f47ac10b-58cc-4372-a567-0e02b2c3d479",
"e36bd20a-47bb-3261-9456-1d13a1b2c368"
],
"device_overrides": {
"f47ac10b-58cc-4372-a567-0e02b2c3d479": {
"router_id": "10.0.1.1",
"neighbor_ip": "10.0.1.2",
"vlan_id": "100"
},
"e36bd20a-47bb-3261-9456-1d13a1b2c368": {
"router_id": "10.0.2.1",
"neighbor_ip": "10.0.2.2",
"vlan_id": "200"
}
}
}
)
instance_id = instance.json()["id"]
# Deploy
deploy = requests.post(
f"{base_url}/stacks/{stack_data['id']}/instances/{instance_id}/deploy",
headers=headers
)
print(f"Deployment state: {deploy.json()['state']}")// Create instance with per-device overrides
const instanceResp = await fetch(
`${BASE_URL}/stacks/${stack.id}/instances`,
{
method: "POST",
headers,
body: JSON.stringify({
name: "dc1-edge-deploy-2026-03",
device_ids: [
"f47ac10b-58cc-4372-a567-0e02b2c3d479",
"e36bd20a-47bb-3261-9456-1d13a1b2c368",
],
device_overrides: {
"f47ac10b-58cc-4372-a567-0e02b2c3d479": {
router_id: "10.0.1.1",
neighbor_ip: "10.0.1.2",
vlan_id: "100",
},
"e36bd20a-47bb-3261-9456-1d13a1b2c368": {
router_id: "10.0.2.1",
neighbor_ip: "10.0.2.2",
vlan_id: "200",
},
},
}),
}
);
const instance = await instanceResp.json();
// Deploy
const deployResp = await fetch(
`${BASE_URL}/stacks/${stack.id}/instances/${instance.id}/deploy`,
{ method: "POST", headers }
);
const deployResult = await deployResp.json();
console.log(`Deployment state: ${deployResult.state}`);Check Deployment Status
curl "https://netstacks.example.net/api/stacks/d4e5f6a7-.../instances/inst-uuid/status" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
# Response:
# {
# "state": "in_progress",
# "progress": { "total": 2, "completed": 1, "failed": 0 },
# "devices": [
# { "device_id": "f47ac10b-...", "name": "core-rtr-01.dc1", "state": "completed" },
# { "device_id": "e36bd20a-...", "name": "core-rtr-02.dc1", "state": "in_progress" }
# ]
# }import time
# Poll deployment status until complete
while True:
status = requests.get(
f"{base_url}/stacks/{stack_data['id']}/instances/{instance_id}/status",
headers=headers
).json()
print(f"State: {status['state']} ({status['progress']['completed']}/{status['progress']['total']})")
if status["state"] in ("completed", "failed"):
break
time.sleep(5)// Poll deployment status
async function waitForDeployment(stackId: string, instanceId: string) {
while (true) {
const resp = await fetch(
`${BASE_URL}/stacks/${stackId}/instances/${instanceId}/status`,
{ headers: { Authorization: "Bearer eyJhbGciOiJIUzI1NiIs..." } }
);
const status = await resp.json();
console.log(`State: ${status.state} (${status.progress.completed}/${status.progress.total})`);
if (status.state === "completed" || status.state === "failed") return status;
await new Promise((r) => setTimeout(r, 5000));
}
}Questions & Answers
- How do I deploy a stack to multiple devices?
- Create a stack instance with an array of
device_idsand then call the deploy endpoint. Each device receives the rendered configuration from all templates in the stack. - How do I set per-device variable overrides?
- Include a
device_overridesobject in the instance creation request. Each key is a device UUID and the value is an object of variable overrides for that specific device. - What are the deployment states?
- Deployments progress through:
pending→validating→in_progress→completedorfailed. Each device within the deployment also has its own state. - How do I check deployment progress?
- Call
GET /api/stacks/:id/instances/:instance_id/status. The response includes the overall state, progress counters (total, completed, failed), and per-device status. - Can I roll back a failed deployment?
- NetStacks tracks configuration snapshots before deployment. If a deployment fails, you can view the pre-deployment config backup and manually restore it or create a new deployment with the previous configuration.
- How do I view deployment history?
- List all instances for a stack via
GET /api/stacks/:id/instances. Each instance record includes its deployment state, timestamps, and device results.
Troubleshooting
Deployment Stuck in “validating”
A template rendering error is preventing deployment. Check that all required variables are provided (in stack defaults or device overrides) and that the template Jinja2 syntax is valid. Review the deployment status for error details.
Device Unreachable During Deploy
The Controller cannot reach the device via SSH. Verify the device IP, port, and credentials. Use the Devices API credential test endpoint to diagnose connectivity.
Variable Override Not Applied
Check the override precedence: device-specific overrides take priority over stack defaults, which take priority over template defaults. Ensure the variable name in the override exactly matches the template variable name (case-sensitive).
Partial Failure
Some devices succeeded while others failed. The deployment status shows per-device results with error messages. Common causes include device connectivity issues, credential problems, or device-specific template rendering errors.
Related Features
- Templates API — Create the individual configuration templates that stacks combine.
- Devices API — Manage the devices that stacks deploy to.
- API Authentication — Obtain tokens for API access.
- Error Codes — Reference for deployment errors and validation failures.
- Creating Stacks — UI guide for creating and deploying stacks.