A Routing Protocol Lab with Containerlab and FRRouting
Build a virtual network of routers running real OSPF and BGP, entirely in containers on one machine -- and watch routes actually propagate, fail over, and reconverge.
What you'll build and why
Containerlab wires together container-based network devices into a defined topology from one YAML file; FRRouting is a real, production-grade routing suite (used in actual production networks, not a simplified teaching tool) running inside those containers. This guide builds a small multi-router topology, configures OSPF within it, adds a second autonomous system peered over BGP, and then breaks a link on purpose to watch reconvergence happen -- because routing protocols make much more sense watched live than read about.
Don't build this if: you don't have a specific interest in routing protocols at this level -- most homelab networking (the VLAN and firewall guides elsewhere in this section) doesn't require running your own dynamic routing protocol. This is for understanding how the protocols themselves work, or for a specific use case that actually needs dynamic routing.
How it works
Containerlab reads topology.yml, spins up FRR-based
router containers, wires virtual links between them
exactly as defined
R1 ---- R2
│ │
│ OSPF │ (single autonomous system)
│ │
R3 ------┘
│
│ eBGP peering (two AS's exchanging routes)
│
R4 (a separate AS)
Each "router" is a container running FRR's routing daemons (ospfd, bgpd) -- real routing software, just running in containers instead of on dedicated router hardware. The protocols behave exactly as they would on physical gear.
Before you start
Decision: OSPF first, BGP second. OSPF (an interior gateway protocol, routing within one network you control) is the more approachable starting point; BGP (how separate networks/autonomous systems exchange routes, the protocol that holds the actual internet together) has more moving parts. This guide builds OSPF fully working before introducing BGP, rather than both at once.
Steps
Step 1: Install Containerlab
$ curl -sL https://containerlab.dev/setup.sh | sudo bash -s "all"
The official install script. Check containerlab.dev for current alternative install methods (package managers are also supported) if you prefer not to pipe a script to bash.
Step 2: Define the topology
topology.yml:
name: routing-lab
topology:
nodes:
r1:
kind: linux
image: frrouting/frr:latest
r2:
kind: linux
image: frrouting/frr:latest
r3:
kind: linux
image: frrouting/frr:latest
r4:
kind: linux
image: frrouting/frr:latest
links:
- endpoints: ["r1:eth1", "r2:eth1"]
- endpoints: ["r2:eth2", "r3:eth1"]
- endpoints: ["r1:eth2", "r3:eth2"]
- endpoints: ["r3:eth3", "r4:eth1"]
R1-R2-R3 form a triangle (redundant paths, needed for the reconvergence test later); R4 is peered separately for the BGP step.
Step 3: Deploy the lab
$ sudo containerlab deploy -t topology.yml
Expected output: Containerlab reports each node's status and how to reach it, typically via docker exec or SSH depending on the node kind.
Step 4: Configure OSPF on R1, R2, R3
On each, via FRR's vtysh CLI:
$ sudo docker exec -it clab-routing-lab-r1 vtysh
router ospf
network 10.0.12.0/30 area 0
network 10.0.13.0/30 area 0
!
(Adjust the actual subnet per each link's addressing -- assign RFC1918 point-to-point subnets per link when interface-configuring each router, done via interface eth1 / ip address <addr>/<mask> in the same vtysh session before the OSPF config above.) Repeat with the relevant networks on R2 and R3.
Step 5: Verify OSPF neighbors and routes
r1# show ip ospf neighbor
r1# show ip route ospf
Expected output: R1 shows R2 and R3 as full OSPF neighbors, and routes to networks only directly connected to R2/R3 now appear in R1's routing table too -- learned via OSPF, not manually configured.
Step 6: Configure eBGP between R3 and R4 (two autonomous systems)
On R3:
router bgp 65001
neighbor <r4-interface-ip> remote-as 65002
network <a-network-r1-r2-r3-know-about>/24
!
On R4:
router bgp 65002
neighbor <r3-interface-ip> remote-as 65001
network <a-network-only-r4-knows-about>/24
!
Step 7: Verify the BGP peering and route exchange
r3# show bgp summary
r3# show ip route bgp
Expected output: R3 shows R4 as an established BGP neighbor, and R4's advertised network now appears in R3's routing table with the eBGP-learned route source.
Verify it works
-
show ip ospf neighboron R1, R2, and R3 all show each other asFullneighbors -
show ip route ospfon R1 includes routes to R3's directly-connected networks, learned dynamically -
show bgp summaryon R3 shows R4's session asEstablished - R4's advertised network is reachable (a
pingfrom R1, whose route to it was learned entirely dynamically through OSPF-then-BGP redistribution/route visibility, not manual configuration) - The actual point of this guide -- reconvergence: bring down the R1-R2 link (
sudo containerlab tools disconnector simply shutting the interface within R1'svtysh) while R1-R3-R2 remains available as an alternate path. Confirm OSPF detects the failure and reconverges --show ip route ospfon R1 should still show a route to R2's networks, now via R3 instead. Time roughly how long this takes; this is OSPF convergence, observed directly rather than described
Secure it
- This is a lab, not a hardened deployment: BGP in this guide has no authentication (MD5/TCP-AO) configured -- appropriate for an isolated lab, not for anything resembling a real routing deployment where an unauthenticated peer relationship is a real risk (route hijacking, injected bad routes).
- What's exposed: Containerlab's virtual links are isolated to the lab topology by default -- nothing here is reachable from outside the host unless you deliberately bridge it out.
- If this ever informs real router configuration: BGP authentication, prefix filtering (only accepting expected routes from a peer), and max-prefix limits are all real production concerns this lab intentionally skips for simplicity -- don't carry this config directly into anything internet-facing.
Back it up and maintain it
What matters: topology.yml and any router configs you've built up -- worth version-controlling if you're iterating on the lab design, though the lab itself is fully reproducible from the file.
Update cadence: Containerlab and the FRR image both update independently; pin a specific FRR image tag if you want a stable, reproducible lab rather than picking up FRR changes on every redeploy.
What to monitor: nothing persistent -- this lab exists only while deployed.
Troubleshooting
Logs: sudo docker logs clab-routing-lab-<node>; within vtysh, show logging for FRR's own routing daemon logs.
| Symptom | Likely cause | Diagnostic | Fix |
|---|---|---|---|
OSPF neighbors never reach Full |
Interface addressing mismatch, or the network statement's area/subnet doesn't match the actual interface |
show ip ospf interface to confirm OSPF is actually enabled on the expected interface with the expected area |
Fix the addressing or network statement to match the real interface config |
BGP session stuck in Active/Connect, never Established |
Wrong neighbor IP, wrong remote-as, or no IP connectivity between the two routers at all | ping between the two BGP peer IPs directly, outside of BGP |
Fix the neighbor IP/AS number, or the underlying link/IP config |
| Containerlab deploy fails immediately | Docker not running, or insufficient permissions | sudo systemctl status docker; confirm running as root/with sudo (Containerlab typically requires it) |
Start Docker; rerun with sudo |
| Route doesn't reconverge after breaking a link | No actual alternate path exists in the topology for that specific route, or OSPF hasn't finished recalculating yet | show ip ospf database to confirm the topology as OSPF currently understands it |
Confirm the topology genuinely has redundancy for that path; allow a few seconds for SPF recalculation |
vtysh command not found / can't reach router CLI |
Wrong container name, or FRR's daemons aren't running inside that node | sudo docker exec -it <exact-container-name> vtysh -- confirm exact name via containerlab inspect |
Use the correct container name from containerlab inspect -t topology.yml |
Undo
$ sudo containerlab destroy -t topology.yml
Removes every container and virtual link created for the lab -- fully reversible, nothing persists outside the lab's own deployment.
Go further
- Add route redistribution between OSPF and BGP (learning OSPF-internal routes into BGP and vice versa) for a more complete picture of how enterprise networks actually connect internal and external routing
- Introduce a deliberate route filter or prefix-list on the BGP peering and observe how it changes what each side learns
- Explore Containerlab's support for other network OS images beyond FRR if you want to practice against vendor-specific syntax (Arista, Nokia, and others have supported container images)
Resources
Official documentation:
- Containerlab documentation -- topology definition and deployment, verified this session
- FRRouting documentation -- OSPF and BGP configuration, verified this session
Source and releases:
Community:
- Containerlab Discord -- linked from the official documentation
- FRRouting community
Related DaemonPress projects:
Last verified: 2026-09-21, checked against official Containerlab and FRRouting documentation.