NetStacksNetStacks

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 Lifecycle

Stack deployments follow a state machine: pending validatingin_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):

  1. Device-specific overrides (set per device in the instance)
  2. Stack template defaults (set when creating the stack)
  3. Template defaults (defined in the config template itself)

Deployment States

StateDescription
pendingInstance created, not yet deployed
validatingTemplates being rendered and validated
in_progressConfiguration being pushed to devices
completedAll devices configured successfully
failedOne 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

create-stack.shbash
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"
# }
create-stack.pypython
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']}")
create-stack.tstypescript
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

deploy-stack.shbash
# 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" }
deploy-stack.pypython
# 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']}")
deploy-stack.tstypescript
// 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

check-status.shbash
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" }
#   ]
# }
poll-status.pypython
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-status.tstypescript
// 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_ids and 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_overrides object 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 validatingin_progress completed or failed. 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.