NetStacksNetStacks

Creating Stacks

Enterprise

Create stack instances from stacks: pick target devices, set shared and per-device variable values, save as a draft, then deploy.

Overview

Controller-only feature

Stacks are a NetStacks Controller capability. Stack instances are managed through the Controller admin UI and the Controller config API (/api/config/…). They are not available in the standalone desktop app.

Creating a stack is the process of turning a reusable stack (the blueprint) into a concrete stack instance. An instance binds a stack to specific target devices and stores the variable values that will be rendered into device configuration. It is the bridge between defining what to deploy (the stack and its ordered services) and actually pushing it to live devices.

When you create a stack instance, you provide:

  • Target devices — The devices that receive the configuration, referenced by their device ID from the inventory. Connection details (host, transport, port, credentials) are resolved from the device record at deploy time.
  • Variable values — Shared values that apply to every target device (for example, ntp_server = 10.0.0.10).
  • Device overrides — Per-device values that differ from the shared defaults (for example, a unique hostname per device).
Instances vs deployments

A stack instance is the saved plan. A deployment is the act of pushing that plan to devices. A newly created instance is in the draft state. You can edit it, then trigger a deployment, which creates a separate deployment record and runs it in the background.

How It Works

Creating an instance collects everything the deploy pipeline needs to render and push configuration to each target device.

Target Device Selection

You select target devices from your device inventory. The instance stores only the device reference — the device ID — not connection details. The host address, transport (gNMI or NETCONF), port, and credentials are looked up from the device record when the deployment runs. Because services in a stack are ordered, target devices are stored per service index (the deploy pipeline accepts a per-service object such as { "0": ["device-id", ...] }, a flat list of { "device_id": "..." } objects, or a simple array of IDs).

Variable Resolution

The variable_values field stores shared variable values; the device_overrides field stores per-device values keyed by device ID. Values are scoped per service within the stack: at deploy time NetStacks merges devices, shared variables, and device variables for each service order before rendering each service's Jinja2 template. Per-device overrides take precedence over shared values, which take precedence over template defaults.

Admin UI Flow

The Controller admin UI provides a step-by-step flow that guides you through instance creation:

  1. Select a stack from the stack library
  2. Name the instance (a descriptive label for tracking)
  3. Select target devices from the inventory
  4. Fill in shared variable values
  5. Fill in per-device variable values and overrides
  6. Review the summary of devices and variables
  7. Save the instance (initial state is draft)

Instance State

A newly created instance starts in the draft state (the database default). It stays a draft — editable at any time — until you trigger a deployment from it. The instance stores a reference to the stack, all variable values, and the list of target devices: everything needed to render and push configuration.

Preview before you deploy

Before deploying, render a dry-run preview of the stack with POST /api/config/stacks/{id}/render to inspect the exact configuration that will be generated for each device. No changes are pushed.

Step-by-Step Guide

Follow this flow to create a stack instance that deploys a monitoring baseline to three core routers.

Step 1: Select a Stack

Open the Controller admin UI, navigate to the stacks area, and start a new instance. Select the stack you want to deploy — for example, "Monitoring Baseline", whose ordered services configure NTP, SNMP, and Syslog.

Step 2: Name the Instance

Enter a descriptive name. Use a convention that includes the site, device role, and a version or date — for example, DC1-Core-Baseline-2026-01. This name carries through to the deployment record and audit history.

Naming conventions

Including the site code, device role, and a date or version identifier makes instances easy to find later — for example, NYC-Edge-Monitoring-v2 or LAX-Spine-Baseline-2026Q1.

Step 3: Select Target Devices

Choose devices from your inventory. Select all devices that should receive this configuration. For this example, select three core routers with device IDs dev-001, dev-002, and dev-003. Each device's transport and credentials come from its inventory record — you do not enter a host or port here.

Step 4: Fill In Shared Variable Values

Enter values that apply to every target device:

  • ntp_server_primary: 10.0.0.10
  • ntp_server_secondary: 10.0.0.11
  • snmp_community: netstack-ro
  • syslog_server: 10.0.0.20

Step 5: Fill In Per-Device Overrides

For each target device, enter the device-specific values:

  • dev-001: hostname = core-rtr-01.dc1, snmp_location = DC1-Row1-Rack3
  • dev-002: hostname = core-rtr-02.dc1, snmp_location = DC1-Row2-Rack7
  • dev-003: hostname = core-rtr-03.dc1, snmp_location = DC1-Row3-Rack12

Step 6: Review and Save

The summary shows every device with its resolved variable values. Verify that shared values are correct and per-device overrides are assigned to the right devices, then save. The instance is created in the draft state, ready to preview or deploy.

Code Examples

All endpoints below are served by the Controller config API. Set $TOKEN to a bearer token and $BASE to your Controller URL (for example, https://controller.example.net/api).

Create a Stack Instance

POST a new instance to /api/config/instances. The body provides stack_id, a name, target_devices, variable_values (shared), and device_overrides (per device). Target devices reference inventory device IDs only — no host or port.

create-stack-instance.shbash
curl -s -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  "$BASE/config/instances" \
  -d '{
    "stack_id": "9f3a2b1c-0000-4000-8000-000000000001",
    "name": "DC1-Core-Baseline-2026-01",
    "target_devices": ["dev-001", "dev-002", "dev-003"],
    "variable_values": {
      "ntp_server_primary": "10.0.0.10",
      "ntp_server_secondary": "10.0.0.11",
      "snmp_community": "netstack-ro",
      "syslog_server": "10.0.0.20"
    },
    "device_overrides": {
      "dev-001": { "hostname": "core-rtr-01.dc1", "snmp_location": "DC1-Row1-Rack3" },
      "dev-002": { "hostname": "core-rtr-02.dc1", "snmp_location": "DC1-Row2-Rack7" },
      "dev-003": { "hostname": "core-rtr-03.dc1", "snmp_location": "DC1-Row3-Rack12" }
    }
  }'
Per-service target format

The admin UI stores target_devices, variable_values, and device_overrides keyed by service index (for example, { "0": ["dev-001"], "1": ["dev-001"] }) so each ordered service can target a different set of devices. The simple flat arrays above also work and are expanded across all services by the deploy pipeline.

Instance Creation Response

The API returns the created instance with its ID and state:

instance-response.jsonjson
{
  "id": "a7c4e2d1-0000-4000-8000-000000000010",
  "org_id": "00000000-0000-4000-8000-000000000001",
  "stack_id": "9f3a2b1c-0000-4000-8000-000000000001",
  "name": "DC1-Core-Baseline-2026-01",
  "target_devices": ["dev-001", "dev-002", "dev-003"],
  "variable_values": {
    "ntp_server_primary": "10.0.0.10",
    "ntp_server_secondary": "10.0.0.11",
    "snmp_community": "netstack-ro",
    "syslog_server": "10.0.0.20"
  },
  "device_overrides": {
    "dev-001": { "hostname": "core-rtr-01.dc1", "snmp_location": "DC1-Row1-Rack3" },
    "dev-002": { "hostname": "core-rtr-02.dc1", "snmp_location": "DC1-Row2-Rack7" },
    "dev-003": { "hostname": "core-rtr-03.dc1", "snmp_location": "DC1-Row3-Rack12" }
  },
  "state": "draft",
  "created_by": "11111111-0000-4000-8000-000000000001",
  "created_at": "2026-01-15T10:00:00Z",
  "updated_at": "2026-01-15T10:00:00Z"
}

List and Get Instances

list-instances.shbash
# List all stack instances (optionally filter by ?stack_id=<uuid>)
curl -s -H "Authorization: Bearer $TOKEN" \
  "$BASE/config/instances" | jq .

# Get a specific instance by ID
curl -s -H "Authorization: Bearer $TOKEN" \
  "$BASE/config/instances/a7c4e2d1-0000-4000-8000-000000000010" | jq .

Edit, Then Deploy

Update a draft (or already-deployed) instance with a PUT, then deploy it. Deploy takes no request body — it reads the instance's saved configuration, creates a deployment record, and runs it in the background.

edit-and-deploy.shbash
# Update variable values / overrides on an existing instance
curl -s -X PUT \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  "$BASE/config/instances/a7c4e2d1-0000-4000-8000-000000000010" \
  -d '{
    "variable_values": { "syslog_server": "10.0.0.21" }
  }'

# Deploy the instance (no body) — returns the new deployment record
curl -s -X POST \
  -H "Authorization: Bearer $TOKEN" \
  "$BASE/config/instances/a7c4e2d1-0000-4000-8000-000000000010/deploy" | jq .

Dry-Run Preview

Render the stack against your variable values to see the exact configuration that would be generated per device, without pushing anything:

render-preview.shbash
curl -s -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  "$BASE/config/stacks/9f3a2b1c-0000-4000-8000-000000000001/render" \
  -d '{
    "variable_values": {
      "0": {
        "devices": ["dev-001", "dev-002", "dev-003"],
        "shared_vars": { "ntp_server_primary": "10.0.0.10" },
        "device_vars": { "dev-001": { "hostname": "core-rtr-01.dc1" } }
      }
    }
  }' | jq '.jobs[] | {device_id, service_order, rendered_config}'

Questions & Answers

Q: How do I create a stack instance?
A: Use the Controller admin UI or POST to /api/config/instances. Provide a stack_id, a name, target devices, shared variable_values, and optional per-device device_overrides. The instance is created in the draft state, ready to preview or deploy.
Q: What state is a new instance in?
A: draft. That is the database default for new instances. The instance stays editable as a draft until you deploy it via POST /api/config/instances/{id}/deploy, which creates a separate deployment record.
Q: Do I specify a host and SSH port for each target device?
A: No. Target devices are referenced by their inventory device ID only. Connection details — host, transport (gNMI or NETCONF), port, and credentials — are resolved from the device record at deploy time.
Q: Can I deploy to devices in different locations?
A: Yes. The target device list can mix devices from any site, rack, or geography in your inventory. There is no restriction on combining devices in one instance.
Q: What happens if a target device is offline?
A: Creating the instance always succeeds because it only stores the plan. When you deploy, the pipeline connects to each device during the in-progress stage (the pre-flight backup captures the current config first). Unreachable devices are recorded as failed for that device, and the deployment's per-device results show which devices could not be reached.
Q: Can I modify an instance after creating it?
A: Yes. Edit it with PUT /api/config/instances/{id} to change the name, target devices, variable values, or overrides, then re-deploy with POST /api/config/instances/{id}/deploy. Each deploy creates a fresh deployment record.
Q: How can I preview the config before deploying?
A: Use the dry-run render endpoint POST /api/config/stacks/{id}/render. It returns one render job per device/service with the fully rendered configuration, so you can review exactly what would be pushed.
Q: Can I create multiple instances from the same stack?
A: Yes. A stack is a reusable blueprint. Create as many instances as you like, each targeting different devices with different values — for example, one for DC1 core routers and another for DC2, both from the same "Monitoring Baseline" stack.

Troubleshooting

Missing required variables

If a deploy or render reports missing variables, make sure every variable the stack's templates reference has a value. Shared variables need a value in variable_values; per-device variables need a value in variable_values (as a default) or in device_overrides for each device. Run a render preview to see which variable is unresolved.

Device not found in inventory

If a device is not selectable, confirm it exists in the device inventory and that you have permission to deploy to it. Devices must be added to the inventory before they can be targeted. See Adding Devices.

Deploy fails to connect to a device

Configuration is pushed over the device's configured transport (gNMI or NETCONF), not raw SSH/CLI. If a device fails during deployment, verify its transport, port, and credentials in the inventory record, and confirm the management interface is reachable on the transport port. The deployment's per-device logs record the connection error.

Permission errors on creation

Creating and deploying instances requires operator-level permissions. Verify your account has the appropriate role and that the target devices are within your organization scope.

Learn more about related NetStacks features:

  • Stack Templates — Define reusable stacks with ordered services, variable classification, and atomic deployment
  • Stack Instances — Deploy, monitor, and manage instances on target devices
  • Variable Overrides — Customize variable values per device within a deployment
  • Deployment History — Review deployment timelines, logs, and per-device results
  • Adding Devices — Add devices to your inventory before targeting them