Skip to content

Prerequisites

Check these first. Every one of them fails late and confusingly if you skip it.

Host requirements

Requirement Why Used by
Docker + docker compose The whole control plane and every device container everything
containerlab Wires real veth links via network namespaces — Linux only make local-lab-up
openssl Mints the dev RSA keypair the CMS needs for RS256 JWTs make lab-jwt
uv Runs ruff / pyrefly / pytest and the MkDocs tooling make check, make doc-*
Quay pull access The CMS, engine, web client and worker images are private make local-env-init
RAM/CPU The 5 SR Linux nodes are hungry — budget a few GB make local-lab-up

The lab scripts themselves need nothing but python3

gen_clab_topology, gen_device_configs, run_workflow, wait_ready and wait_devices are stdlib-only on purpose: they run on a bare host before any virtualenv exists. uv is only needed for the dev tooling, not to run the lab.

containerlab and sudo-less operation

containerlab creates network namespaces and veth pairs, so it needs privileges. The make targets call containerlab deploy directly, with no sudo, so it must work password-free:

sudo usermod -aG clab_admins $USER          # then re-login for the group to apply
sudo chown root:root "$(command -v containerlab)"
sudo chmod 4755 "$(command -v containerlab)" # SUID -> -rwsr-xr-x

Verify the SUID bit is set:

ls -l "$(command -v containerlab)"   # expect -rwsr-xr-x

Prove it works before the 15-node deploy

The repo ships a two-node probe so you can confirm privileges and that a linux-kind FRR node accepts a swpN interface over a real veth link — without pulling the whole stack:

clab/probe.clab.yml
# Minimal containerlab probe: confirms containerlab can deploy on this host
# (privileges) and that a `linux`-kind FRR node takes a `swpN` interface over a
# real veth link. Uses only frrouting/frr:latest (no build/pull needed).
name: neops-clab-probe
prefix: ""

topology:
  nodes:
    r1:
      kind: linux
      image: frrouting/frr:latest
    r2:
      kind: linux
      image: frrouting/frr:latest
  links:
    - endpoints: ["r1:swp1", "r2:swp1"]
containerlab deploy  -t clab/probe.clab.yml
containerlab destroy -t clab/probe.clab.yml

A successful deploy means containerlab is ready. The failure you are checking for is ... requires root privileges.

Linux only

containerlab wires veths through Linux network namespaces. The lab’s control plane runs anywhere Docker does, but the 15 devices do not come up on macOS or Windows. Where macOS does still matter is the device-readiness poll, which deliberately runs from inside the lab network for exactly this class of reason.

Registry access

The lab is a consumer of published images. Four of the services pull from quay.io/zebbra, which is a private organisation — an anonymous pull is rejected with 401:

docker login quay.io
Image Service
quay.io/zebbra/neops-cms-free:develop cms
quay.io/zebbra/neops-workflow-engine:develop workflow_engine, workflow-engine-client
quay.io/zebbra/neops-web-client:develop web_client
quay.io/zebbra/neops-worker-sdk:develop worker

ghcr.io/nokia/srlinux:26.3 (the SR Linux devices) is public and needs no login.

NPM_TOKEN is not needed here

Most NeOps repos require an NPM_TOKEN with read:packages to install @zebbra/* npm packages. This repo installs no npm packages and publishes nothing — Quay credentials are the only registry access it needs. (In the shared dev environment NPM_TOKEN and PYPI_TOKEN are exported anyway; they are simply unused by this lab.)

Running locally-built images instead

Every published image can be swapped for a local tag through an environment variable — this is how you exercise an unreleased engine or worker against the lab. Copy .env.example to .env and uncomment what you need:

Variable Default
NEOPS_WORKFLOW_ENGINE_IMAGE quay.io/zebbra/neops-workflow-engine:develop
NEOPS_WEB_CLIENT_IMAGE quay.io/zebbra/neops-web-client:develop
NEOPS_WORKER_SDK_IMAGE quay.io/zebbra/neops-worker-sdk:develop
export NEOPS_WORKER_SDK_IMAGE=neops-worker-sdk:latest

A local tag breaks the explicit docker compose pull step

A tag with no registry host (neops-worker-sdk:latest) resolves as docker.io/library/neops-worker-sdk:latest, which does not exist, so the docker compose pull inside make local-env-init / make local-env-up fails. Run that step yourself with --ignore-pull-failures when you override an image this way.

docker compose up -d is unaffected: every overridable service sets pull_policy: missing (itself overridable via NEOPS_*_PULL_POLICY), so a local image that is already present is used without contacting any registry.

Files the lab needs but does not ship

Two things are git-ignored and created for you on first run. If either is missing the stack does not start:

  • cms/jwt/{private,public}.pem — dev-only RSA keypair minted by make lab-jwt. Newer neops-cms-free images require it for RS256 JWT issuance and crash at startup (token_service check_keys) without it. Idempotent, so re-running never invalidates issued tokens. These are throwaway lab credentials, never secrets.
  • cms_api_key.env — the CMS API key the workflow engine authenticates with, minted by make local-env-init.

make local-env-init depends on lab-jwt, so in practice you only run the four quickstart commands.

Next

Quickstart