← Back to Case Studies Network Engineering

Zero Touch Provisioning Server

Docker Compose stack for automated Cisco Catalyst 9200/9300/9500 provisioning — DHCP, HTTP config delivery, FastAPI config generator with Jinja2 templates, and a Next.js dashboard. Designed to take a switch from factory reset to fully configured in the 5-15 minute ZTP boot window, no engineer on-site.

Docker FastAPI Jinja2 Next.js dnsmasq SQLite Python

Problem

Deploying a new Cisco Catalyst switch at a remote site typically requires a network engineer — either on-site or walking someone through console cable commands over the phone. For organizations running multi-site deployments, every switch replacement or new deployment means scheduling a truck roll or a long remote session. A factory-reset switch sitting in a box is useless until someone configures it, and that someone usually has a CCIE and a $200/hr billing rate.

The provisioning process itself is straightforward: assign a hostname, configure VLANs, set up uplinks, apply QoS, enable management access. It’s the same playbook every time with per-site variables swapped in. That’s exactly the kind of work that shouldn’t require a human.

Solution

Built a Docker Compose stack that implements Cisco’s Zero Touch Provisioning protocol end-to-end. A factory-reset Catalyst 9200, 9300, or 9500 plugs into the network, gets a DHCP lease pointing it to the ZTP server, downloads a Python bootstrap script, phones home with its serial number, receives a fully rendered configuration, and applies it — all without human intervention.

The stack has four services: dnsmasq for DHCP with ZTP options, nginx for HTTP config delivery, a FastAPI config generator that renders Jinja2 templates against a per-device inventory, and a Next.js dashboard for managing inventory and monitoring provisioning status.

Architecture

Factory-Reset Switch (IOS-XE Guest Shell)

    ├── DHCP Request ──────────→ dnsmasq (Option 67: ztp.py)

    ├── HTTP GET /ztp.py ──────→ nginx (bootstrap script)

    └── POST /api/provision ───→ FastAPI Config Generator
            │                         │
        Serial Number            Jinja2 Templates
        Platform Type            Per-client inventory (SQLite)
        IOS-XE Version           Rendered config returned

                                 Next.js Dashboard

                                 Provisioning status
                                 Device inventory
                                 Template management

Inventory and variable sets follow a per-client directory convention. Adding a new site means adding a row to the inventory with the switch serial number and site-specific variables — hostname, management IP, uplink config, VLAN assignments. The ZTP script on the switch handles the rest.

Key Decisions

On-switch Python bootstrap via Guest Shell. The ZTP Python script runs inside IOS-XE Guest Shell, which provides a Linux environment on the switch itself. The script phones home with the switch’s serial number, platform, and IOS-XE version, then applies the returned config. This keeps all intelligence server-side — the on-switch script is intentionally thin.

Per-client layout, multi-tenant on the roadmap. Inventory and variable sets follow a per-client directory convention, so the same ZTP server can grow into handling multiple organizations with completely different network designs — the managed-service-provider model of one stack for all clients. Template rendering today uses a shared template store; full per-client template isolation is on the roadmap.

Raspberry Pi field appliance prototype. For sites without existing infrastructure (greenfield deployments, construction trailers, temporary remote locations), prototyped a Raspberry Pi 4 running the full stack. Designed for PoE power and WiFi management; the current prototype runs on bench power. Ship the Pi with the switches — plug everything in, walk away.

Docker Compose over bare metal. The entire stack runs as docker compose up. No manual service configuration, no dependency conflicts, no “works on my machine.” The same compose file runs on a rack server, a NUC in the server room, or the Raspberry Pi prototype.

Results

  • Designed to take a factory-reset Catalyst switch to fully configured in the 5-15 minute ZTP boot window
  • Targets Catalyst 9200, 9300, and 9500 platforms (IOS-XE ZTP)
  • Template-driven config rendering with Jinja2
  • Real-time provisioning dashboard showing device status and history
  • Serial number-based device identification — no manual input required
  • Raspberry Pi field appliance prototype for greenfield sites
  • Full Docker Compose stack: one command to deploy

How This Scales

  • Multi-vendor expansion — Extend beyond Cisco IOS-XE to support Arista ZTP and Juniper phone-home protocols using the same template/inventory architecture.
  • SaaS model — Hosted ZTP service where clients upload templates and register serial numbers through the dashboard. Switches phone home to a cloud endpoint instead of an on-prem server.
  • Firmware management — Add IOS-XE image hosting and INSTALL mode upgrades as part of the ZTP flow. Switch gets the right firmware and config in one pass.
  • Integration with NetBox — Pull device inventory and IP assignments directly from NetBox instead of maintaining a separate SQLite database.

Tech Stack

  • Config Engine: FastAPI, Jinja2, SQLite (device inventory)
  • ZTP Protocol: dnsmasq (DHCP Option 67), nginx (HTTP delivery)
  • On-Switch: Python (IOS-XE Guest Shell bootstrap script)
  • Dashboard: Next.js
  • Deployment: Docker Compose
  • Prototype: Raspberry Pi 4 field appliance

Need something similar?

I've built this before. Let's talk about adapting it for your needs.