Quickstart
Four commands. The first two set up the control plane, the third builds the network, the fourth populates the CMS.
Do not run this casually
make local-lab-up builds two images, pulls the whole NeOps stack and
deploys 15 device containers. The SR Linux nodes alone want a few GB of
RAM, and the first run takes a couple of minutes. Make sure you have been
through Prerequisites.
1. Mint the CMS keypair
Writes cms/jwt/{private,public}.pem with openssl if they are not already there. The CMS mounts them read-only and refuses to start without them. Idempotent — an existing keypair is left alone.
You rarely run this directly: local-env-init depends on it.
2. Bring up the control plane
This is the one-time-per-environment step. In order, it:
touch cms_api_key.env— the engine’senv_filemust exist before compose reads it.docker compose pullthendocker compose up -d— the base stack (docker-compose.yml): CMS, workflow engine, monitor app, web client, Postgres, Elasticsearch, Redis. Await_healthservice makesup -dblock until CMS, engine and web client report healthy.- Mints a CMS API key via
manage.py generate_api_keyand writes it intocms_api_key.env. - Runs
./apply_cms_config— grants theneopsuser a full-permission role (lab-admin,default_permission=7) and configures theGlobalscope’s columns, drill-down and dashboard. docker compose up -d --force-recreate workflow_engine—env_filechanges do not trigger a recreate on their own, so the engine is restarted to pick up the new token.
Why the CMS config is applied before the engine restart
The CMS caches each user’s accessible scopes in memory for 10 minutes. If
the engine queries the CMS as neops before the grant lands, that stale
(empty) cache hides the Global scope from the web client for up to ten
minutes — the classic “scope not available when first used” report.
Granting first means the first neops query caches the grant.
3. Build the network
The long one. It depends on build-docker, then:
./gen_clab_topology— rendersgenerated/neops-lab.clab.json, the per-device FRR/SR Linux configs, and three of the four discovery parameter files fromtopology.json.docker compose up -dwith the worker overlay — adds theworkerand the one-shotlab_bootstrapcontainers and creates thelab-netbridge (172.30.0.0/24) that containerlab attaches the devices to.docker compose wait lab_bootstrap— blocks until the one-shot container that POSTs everyworkflows/*.yamlto the engine has exited, and propagates its exit code.containerlab deploy— the 15 devices with real point-to-point links. Deployed early on purpose: SR Linux boots slowly, so it overlaps with the waits below../wait_ready fb.base.neops.io/global_discover_network:0.1.0— the worker registers its function blocks with the engine asynchronously after its container starts; this polls the engine’s per-function-block worker registry until an online worker exists.docker compose exec -T worker python3 lab/wait_devices— polls TCP 22 on every device’s management IP, from inside the lab network.
Each of those waits exists because of a real, reproduced failure. See Troubleshooting for the symptom each one prevents, and The /app/lab mount for why step 6 has a lab/ prefix that host commands do not.
When it finishes you get a banner:
Lab is up (containerlab: 10 FRR + 5 Nokia SR Linux, real links).
Web client: http://localhost:8080/
Engine UI: http://localhost:3031
Engine API: http://localhost:3030/
CMS admin: http://localhost:8001/admin/ (neops / neops)
CMS GraphQL: http://localhost:8001/graphql
4. Populate the CMS
Runs the discovery workflow and waits for a terminal state. Covered in detail on the next page.
Where to look
| URL | What you get |
|---|---|
| http://localhost:8080/ | Web client — entity browser; the devices land here after step 4 |
| http://localhost:3031 | Monitor app — workflow definitions and live execution monitoring |
| http://localhost:3030 | Workflow engine REST API |
| http://localhost:8001/admin/ | CMS Django admin — log in as neops / neops |
| http://localhost:8001/graphql | CMS GraphQL API |
Shutting down
make local-lab-down # destroy the devices, stop the stack, KEEP the volumes
make local-env-prune # docker compose down -v — the true reset, drops ES + Postgres
Full reset from scratch:
make local-lab-down local-env-prune
make local-env-init && make local-lab-up && make local-lab-discover
