Troubleshooting
Common failure modes and their fixes. If your problem is not here, check the Infrahub documentation or open an issue.
Bootstrap timeout
Symptom:
[ERROR] ConnectionRefusedError: [Errno 111] Connection refused
or the bootstrap script exits immediately with "Infrahub not reachable".
Cause: The bootstrap script runs too soon after docker compose up.
Infrahub takes 20–40 seconds to initialise its database and API server.
Fix:
# Wait for Infrahub to be ready, then bootstrap manually:
uv run invoke start
sleep 40
uv run invoke bootstrap
Alternatively, run invoke init which includes a built-in wait, but if your
machine is slow, increase the sleep in tasks.py:
# tasks.py — find the sleep call in init_demo and increase it
time.sleep(40) # change to 60 or more on slow machines
Generator fails: "No free physical interface"
Symptom:
RuntimeError: No free physical interface on pe-01
Cause: The L3VpnGenerator first looks for an InterfacePhysical on the
target PE already described L3VPN <vpn name> — that is how a pre-wired PE-CE
port is bound. When there is no such port it falls back to the lowest-numbered
interface with status = free, and this error means there is none left: every
interface on that PE is already active.
On the financial dataset each PE ships with two free customer-facing
spares; the ports wired to the pre-provisioned CEs (Ethernet3/Ethernet4 on
pe-01 and pe-08) are deliberately active so they can never be handed to
another service.
Fix: Create a free interface on the PE via the Infrahub UI or GraphQL:
curl -s -X POST http://localhost:8000/graphql \
-H "X-INFRAHUB-KEY: 06438eb2-8019-4776-878c-0941b1f1d1ec" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation {
InterfacePhysicalCreate(data: {
name: {value: \"Ethernet10\"},
status: {value: \"free\"},
mtu: {value: 9000},
device: {hfid: [\"pe-01\"]}
}) { ok object { id } }
}"
}'
Repeat for whichever PE is out of free interfaces.
Configuration push fails: "not supported on this hardware platform"
Symptom:
eAPI error: CLI command 11 of 57 'ip routing' failed: invalid command
last result: {'errors': ["Unavailable command (not supported on this hardware
platform) (at token 1: 'routing')"]}
Cause: The node answered eAPI before its agents finished registering their
CLI commands, so the parser genuinely did not know the routing keyword yet.
It is a timing problem, not a configuration problem — the same file pushes
cleanly to the nodes that had longer to boot, which is why the failure tends to
appear part-way through a run.
Fix: None needed. scripts/push_arista.py polls a read-only command served
by the same agent that owns ip routing, and only pushes once that answers. If
it times out instead, the node is genuinely stuck — check
docker logs clab-mpls-backbone-1-<node>. Booting twelve cEOS nodes is
memory-hungry; give Docker more RAM if nodes are being killed.
Pushed configuration does not match Infrahub
Symptom: The pushed command counts or interface names do not match what Infrahub shows, or a device rejects interfaces the topology does not have.
Cause: lab/devices/*.cfg are fetched once by invoke lab.deploy and then
reused by invoke lab.push-arista. After the dataset or a service changes, the
files on disk are stale.
Fix: Re-run invoke lab.deploy. It re-fetches the topology artifact and
every per-device configuration, then redeploys the lab so the running nodes and
the configuration files agree.
LDP does not start, so inter-site VPN traffic fails
Symptom: Every prefix is exchanged correctly — IS-IS is up, the iBGP mesh is up, PE-CE eBGP is up, and each PE holds the customer prefixes in the right VRF — but a host on one customer site cannot reach the same customer's other site. On any PE:
show mpls ldp neighbor
! Agent 'LdpAgent' is not running
! LDP is not enabled
Cause: Not a configuration defect. The rendered PE configuration contains the
full mpls ldp block, including no shutdown and the per-interface statements,
and it pushes without error — but cEOS never starts the LDP agent. It appears
to be a limitation of the cEOS-lab image rather than something the configuration
can influence.
Consequence: An L3VPN needs an MPLS label path between PEs to carry VPN traffic. Without LDP there is no transport, so VPNv4 routes cannot be used for forwarding even though BGP has exchanged them. The control plane is complete and demonstrable; the data plane between sites is not.
What still works: everything up to the label path — IS-IS adjacencies, the full iBGP mesh, PE-CE eBGP, VRF route import/export, and customer LAN reachability within a site (host to its own CE gateway over the tagged VLAN).
Fix: Unverified. This was diagnosed against cEOS 4.29.0.2F (which reports
itself as 4.28.9M), whose LDP agent never starts. The lab now runs 4.36.0.1F, so
the gap may already be closed — if you can confirm either way on a real deploy
(which needs an x86_64 host), update this page. To test another build,
point CEOS_IMAGE at it (CEOS_IMAGE=<image> uv run invoke lab.deploy). Segment routing instead of LDP would
be the other option, and a larger change.
Containerlab deploy hangs at "Running postdeploy actions"
Symptom: invoke lab.deploy creates every node and every link within a
second or two, then stops making progress. The last lines repeat once per cEOS
node and nothing follows them, for tens of minutes:
INFO Created link: pe-07:eth4 ▪-▪ pe-08:eth2
INFO Running postdeploy actions for Arista cEOS 'pe-07' node
INFO Running postdeploy actions for Arista cEOS 'pe-08' node
There is no error, no traceback and no exit — containerlab inspect shows the
containers up, and docker logs on a node is empty or stops early.
Cause: the nodes booted as containers but EOS never came up inside them.
containerlab's cEOS postdeploy step opens the EOS CLI (docker exec … Cli) and
waits for a prompt before pushing the management and interface configuration.
That wait is an unbounded retry loop — for { …; time.Sleep(2s); continue } in
containerlab's utils/networkcli.go, with no deadline and no attempt cap — so
an EOS that never finishes booting shows up as an indefinite stall rather than
a failure.
invoke lab.deploy bounds it at 30 minutes and prints the per-node container
state on timeout, which is what separates a node that died from one still
booting. Adjust with LAB_DEPLOY_TIMEOUT_SECONDS.
Three things cause it:
-
An ARM host. The most common one, and the one that looks least like itself. cEOS-lab is 32-bit x86 in every published build and Rosetta 2 does not translate 32-bit x86, so on Apple Silicon EOS reaches
agentsToStart=[...], loops onProcMgr.service, and exits 255 roughly 17 minutes later — while thecust-*netshoot hosts stay up, sodocker psstill looks like a deployed lab. Confirm with:uname -m # aarch64 / arm64 = unsupporteddocker ps -a --filter name=clab- --format '{{.Names}} {{.Status}}'Exited (255)on theceosnodes with thecust-*hosts up is the signature.invoke lab.deploynow refuses ARM hosts before deploying, so this only appears on an older checkout or withLAB_ALLOW_UNSUPPORTED_ARCH=true. There is no image that fixes it — see Architecture. -
cEOS-lab older than 4.32.0F on a cgroups v2 host. Earlier builds require cgroups v1 and silently fail to boot otherwise. cgroups v2 is the default on Ubuntu 21.04+, OrbStack, and most current distributions. Check the host:
stat -fc %T /sys/fs/cgroup/ # cgroup2fs = v2, tmpfs = v1From 4.32.0F onward cEOS-lab detects the host's cgroup version itself. The images
invoke lab.deployselects are all newer than that, so this only bites a pinned older build — checkCEOS_IMAGE. -
Not enough memory. cEOS wants ~2 GB per node, and the default financial dataset runs 12 of them. On an undersized host the nodes thrash or get OOM-killed mid-boot, which looks identical from the outside:
free -hdmesg -T | grep -iE 'oom|killed process'docker stats --no-streamSee Containerlab prerequisites for sizing. Use the isp dataset (2 lab nodes) if the host cannot take the financial one.
Confirming which: ask a node directly.
docker exec -it clab-mpls-backbone-1-pe-01 systemctl is-system-running
starting long after deploy means EOS is stuck mid-boot (any of the three);
running means EOS is up and the problem is elsewhere. systemctl list-jobs
on the node shows which unit is blocking.
Fix: deploy on a host that meets the
prerequisites, with a cEOS image of
4.32.0F or newer matching the host architecture. Tear the partial lab down
first — uv run invoke lab.destroy — so the retry starts clean.
Port conflict: Infrahub already running
Symptom:
Error response from daemon: driver failed programming external connectivity ...
Bind for 0.0.0.0:8000 failed: port is already allocated
Cause: Another process (or a leftover container) is using port 8000, 4200 (Prefect), or 8501 (Streamlit).
Fix:
# Find what is using the port:
lsof -i :8000
# Stop leftover containers from this project:
docker compose -p sp-demo down
# If another unrelated project is running on the same port,
# override the port in .env:
echo "INFRAHUB_PORT=8001" >> .env
# Then update INFRAHUB_ADDRESS accordingly:
echo 'INFRAHUB_ADDRESS="http://localhost:8001"' >> .env
uv run invoke start
Containerlab: image pull failure
SR Linux (ghcr.io/nokia/srlinux)
Error response from daemon: Head "https://ghcr.io/...": unauthorized
SR Linux is on GitHub Container Registry and requires a GitHub token:
docker login ghcr.io -u <github-username> -p <personal-access-token>
Create a PAT at GitHub settings with read:packages scope.
Arista cEOS
The OpsMill mirror (registry.opsmill.io/external/ceos-image) is public — no
Arista account and no manual import.
Do not read the registry's architecture metadata as fact. docker import
stamps the importing host's architecture into the image configuration, so a tag
imported on an ARM machine claims arm64 while carrying the same 32-bit x86
EOS as every other tag. Docker then treats it as native, arranges no
emulation, warns about nothing, and the node fails to boot. cEOS-lab has no
ARM build; invoke lab.deploy refuses ARM hosts for that reason.
uname -m # aarch64/arm64 cannot run cEOS-lab
echo "${CEOS_IMAGE:-<unset>}" # what you asked for, if anything
To run your own Arista-supplied build, import it and point CEOS_IMAGE at it:
docker import cEOS-lab-4.36.0F.tar.xz ceos:local
CEOS_IMAGE=ceos:local uv run invoke lab.deploy
Streamlit catalog: "Infrahub not reachable"
Symptom: The Streamlit app loads but shows "Cannot connect to Infrahub".
Cause: INFRAHUB_ADDRESS is not set or points to the wrong host/port.
Fix:
# Verify the env var is set:
grep INFRAHUB_ADDRESS .env
# If running Streamlit outside Docker Compose, ensure the address
# points to the correct host:
echo 'INFRAHUB_ADDRESS="http://localhost:8000"' >> .env
uv run streamlit run service_catalog/app.py
If you are running the Streamlit app inside Docker Compose, use the internal service name:
INFRAHUB_ADDRESS=http://infrahub-server:8000
Schema load order error
Symptom:
SchemaNotFound: Node 'RoutingProtocol' not found
Cause: The SP schemas (schemas/sp/) reference base and extension nodes
that were not loaded first.
Fix: Always load schemas in order:
infrahubctl schema load schemas/base/
infrahubctl schema load schemas/extensions/
infrahubctl schema load schemas/sp/
invoke bootstrap does this automatically. If you loaded schemas manually
in the wrong order, run invoke destroy && invoke init to start clean.
Proposed Change: check fails after generator runs
Symptom: The pe_interface_alloc check fails even though the generator
ran and set the interface.
Cause: The check reads the current branch state. If the generator ran on
a previous branch and the change was already merged, the interface status may
already be cust on main, making it appear unavailable for a new site.
Fix: Use a different (free) interface for the new site, or set an
existing cust-status interface back to free if it was decommissioned.
Proposed Change: schema integrity failure blocks the merge
Symptom: Every check the demo owns passes, every artifact validator passes,
and the diff is correct — but the proposed change carries a failed
Schema Integrity validator and merging is refused:
Unable to merge proposed change containing failing checks
Its checks report conflicts on schema paths nothing in the change touched, most often:
schema/IpamIPAddress/uniqueness_constraints
schema/RoutingBGPSession/local_ip/cardinality
schema/RoutingBGPSession/remote_ip/min_count
Cause: Not a data or schema problem in this repository. Compare the schema on the two branches and they are identical:
curl -s -H "X-INFRAHUB-KEY: $INFRAHUB_API_TOKEN" \
"$INFRAHUB_ADDRESS/api/schema?branch=main" > /tmp/main.json
curl -s -H "X-INFRAHUB-KEY: $INFRAHUB_API_TOKEN" \
"$INFRAHUB_ADDRESS/api/schema?branch=<your-branch>" > /tmp/branch.json
diff <(jq -S '.nodes[] | select(.name=="BGPSession")' /tmp/main.json) \
<(jq -S '.nodes[] | select(.name=="BGPSession")' /tmp/branch.json)
The conflicts also carry "branch": "placeholder" and "id": "unknown" rather
than real references. Seen on Infrahub 1.10.6.
Behaviour: intermittent, and sticky to the branch once it happens. Closing the proposed change and opening a new one against the same branch reproduces it exactly; a service created on a fresh branch is unaffected. Most branches never see it — an empty branch does not raise the validator at all.
Fix: Recreate the service on a new branch. The catalog does that for you: submit the wizard again and delete the stuck branch afterwards. There is no way to clear the failed validator on the branch that has it, and no configuration in this repository influences whether it appears.
yamllint: line too long
Symptom:
[error] line too long (105 > 100 characters)
Cause: Bootstrap YAML files sometimes have long inline object specs.
Fix: Break the long line or raise the limit in .yamllint.yml:
rules:
line-length:
max: 120
level: warning # demote from error to warning if needed
uv sync fails
Symptom: uv sync fails with a resolver error or requires-python
mismatch.
Fix:
# Verify your Python version:
python --version # must be 3.10, 3.11, or 3.12
# If using pyenv:
pyenv install 3.12
pyenv local 3.12
uv sync