TTR Networks

Beginner · Building a network lab on an Apple Silicon Mac · section 6 of 8

The starter lab

✓ Lab-tested 18 October 2026 on macOS 26.5 on Apple M4 Max, Docker Desktop (Linux 7.0.14-linuxkit, arm64), VyOS 2026.10.16-0024-rolling, FRR 10.2.1

The starter lab puts all three kinds of node in one topology: a native FRR router, an emulated VyOS router and a Linux host, with eBGP between the two routers. It's a template you can grow into any of this site's labs, and every output below comes from running it on this Mac.

FRR (AS 64500) announces its loopback, 198.51.100.1/32. VyOS (AS 64501) announces the host's LAN, 10.0.0.0/24. When BGP is up, the host can reach the FRR loopback through VyOS, so one ping tests everything at once: two Docker networks, the bridge and tap plumbing inside the VM's container, x86 VyOS under emulation, native FRR, and BGP between them.

The VyOS configuration is applied through the console driver, as six set commands and a commit. The memory numbers at the end are the reason to budget carefully: the whole lab uses under 2 GB, and almost all of it is the VM.

Diagram: The starter lab — A native router, an emulated router and a host: everything this site's labs use, in one topology.
  1. eBGP across the native/emulated boundary. FRR (arm64) and VyOS (x86 under QEMU) peer over the core network; they can't tell the difference.
  2. One ping tests everything. The host pings FRR's loopback: through VyOS's tap and bridge, the emulated router, BGP's route, and the native one.
lab-compose.yaml
# A starter lab for an Apple Silicon Mac: a native FRR router, an x86 VyOS VM
# (QEMU inside a container) and a Linux host, on layer-2-only Docker networks.
#   ./get-vyos.sh                                  # once: download the VyOS ISO into ./vyos
#   docker compose -f lab-compose.yaml up -d --build
#   docker compose -f lab-compose.yaml exec vyos vyos-console.py wait
name: arm-lab
services:
  vyos:
    build: { context: ., dockerfile: Dockerfile.vyos }
    image: ttr-vyos-qemu
    cap_add: [NET_ADMIN]                  # bridges and taps inside the container
    devices: ["/dev/net/tun:/dev/net/tun"]
    environment: { VYOS_MEM: "1536", VYOS_SMP: "1", VM_ID: "01" }
    volumes:
      - ./vyos:/vyos:ro                   # vyos.iso, vmlinuz, initrd.img
      - ./vyos-console.py:/usr/local/bin/vyos-console.py:ro
    networks: { core: { interface_name: eth0 }, lan: { interface_name: eth1 } }
  frr:
    image: quay.io/frrouting/frr:10.2.1   # multi-arch: runs natively on arm64
    privileged: true
    volumes: ["./frr-daemons:/etc/frr/daemons:ro", "./frr.conf:/etc/frr/frr.conf:ro", "./vtysh.conf:/etc/frr/vtysh.conf:ro"]
    networks: { core: { interface_name: eth0 } }
  host:
    image: alpine
    privileged: true                      # lets it set its own address and route
    command: sh -c "ip addr add 10.0.0.10/24 dev eth0 && ip route add default via 10.0.0.1 && sleep infinity"
    networks: { lan: { interface_name: eth0 } }
# Layer-2-only networks: Docker assigns no IPv4 addresses or routes, so every
# address in the lab is one you configured.
networks:
  core: { enable_ipv4: false, enable_ipv6: true, ipam: { config: [{ subnet: "fd00:a1::/64" }] } }
  lan: { enable_ipv4: false, enable_ipv6: true, ipam: { config: [{ subnet: "fd00:a2::/64" }] } }
  • Lines 8–17: The VyOS VM's container: NET_ADMIN and /dev/net/tun for its bridges and taps, 1.5 GB for the VM, one vCPU, and a unique VM_ID.
  • Lines 15–16: The ISO files and the console driver, mounted read-only.
  • Lines 18–22: FRR, straight from its multi-arch image. The vtysh.conf mount silences a warning on every command.
  • Lines 23–26: A tiny Alpine host that configures its own address and default route at start-up.
  • Lines 30–32: Both lab networks are layer-2-only.
frr.conf
frr version 10.2.1
frr defaults traditional
hostname frr
service integrated-vtysh-config
!
interface eth0
 ip address 192.0.2.0/31
exit
!
interface lo
 ip address 198.51.100.1/32
exit
!
router bgp 64500
 bgp router-id 198.51.100.1
 ! Lab only: exchange routes without import/export policy.
 no bgp ebgp-requires-policy
 neighbor 192.0.2.1 remote-as 64501
 neighbor 192.0.2.1 description vyos
 !
 address-family ipv4 unicast
  network 198.51.100.1/32
 exit-address-family
exit
  • Lines 14–19: eBGP to VyOS. The lab turns off FRR's eBGP policy requirement to stay short; real sessions need import and export policy (see the BGP lessons).
  • Line 22: Announce the loopback.
VyOS: interfaces and BGP, through the console driver (real output)
mac$ time docker compose -f lab-compose.yaml exec -T vyos vyos-console.py conf 'set interfaces ethernet eth0 address 192.0.2.1/31' 'set interfaces ethernet eth1 address 10.0.0.1/24' 'set protocols bgp system-as 64501' 'set protocols bgp neighbor 192.0.2.0 remote-as 64500' 'set protocols bgp neighbor 192.0.2.0 address-family ipv4-unicast' 'set protocols bgp address-family ipv4-unicast network 10.0.0.0/24'
configure
WARNING: You are currently configuring a live-ISO environment, changes will not persist until installed
[edit]
vyos@vyos# set interfaces ethernet eth0 address 192.0.2.1/31
[edit]
vyos@vyos# set interfaces ethernet eth1 address 10.0.0.1/24
[edit]
vyos@vyos# set protocols bgp system-as 64501
[edit]
vyos@vyos# set protocols bgp neighbor 192.0.2.0 remote-as 64500
[edit]
vyos@vyos# set protocols bgp neighbor 192.0.2.0 address-family ipv4-unicast
[edit]
vyos@vyos# set protocols bgp address-family ipv4-unicast network 10.0.0.0/24
[edit]
vyos@vyos# commit
[edit]
vyos@vyos# exit
Warning: configuration changes have not been saved.
exit
vyos@vyos:~$ 

real	0m30.437s
user	0m0.037s
sys	0m0.022s
  • Lines 5–15: Six set lines: two interfaces, and BGP to FRR announcing the LAN.
  • Line 3: A live-ISO system: changes don't survive a restart, so keep your set commands in a file.
  • Line 17: The commit, the slow part under emulation.
  • Line 24: Thirty seconds for the whole session.
VyOS's view, and a ping from the host (real output)
mac$ docker compose -f lab-compose.yaml exec -T vyos vyos-console.py op 'show ip bgp summary'
show ip bgp summary

IPv4 Unicast Summary:
BGP router identifier 192.0.2.1, local AS number 64501 VRF default vrf-id 0
BGP table version 2
RIB entries 2, using 320 bytes of memory
Peers 1, using 30 KiB of memory

Neighbor        V         AS   MsgRcvd   MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd   PfxSnt Desc
192.0.2.0       4      64500         5         5        2    0    0 00:00:10            1        2 N/A

Total number of neighbors 1
vyos@vyos:~$ 

mac$ docker compose -f lab-compose.yaml exec -T host ping -c 3 198.51.100.1
PING 198.51.100.1 (198.51.100.1): 56 data bytes
64 bytes from 198.51.100.1: seq=0 ttl=63 time=1.600 ms
64 bytes from 198.51.100.1: seq=1 ttl=63 time=0.653 ms
64 bytes from 198.51.100.1: seq=2 ttl=63 time=0.507 ms

--- 198.51.100.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.507/0.920/1.600 ms
  • Line 11: Established with FRR: 1 prefix received, 2 sent (the LAN, plus FRR's own loopback back to it, which FRR ignores).
  • Lines 16–23: The host reaches FRR's loopback through the emulated VyOS router: everything works.
FRR's view (real output)
mac$ docker compose -f lab-compose.yaml exec -T frr vtysh -c 'show bgp ipv4 unicast'
BGP table version is 2, local router ID is 198.51.100.1, vrf id 0
Default local pref 100, local AS 64500
Status codes:  s suppressed, d damped, h history, u unsorted, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found

     Network          Next Hop            Metric LocPrf Weight Path
 *>  10.0.0.0/24      192.0.2.1                0             0 64501 i
 *>  198.51.100.1/32  0.0.0.0                  0         32768 i

Displayed 2 routes and 2 total paths
  • Line 11: The host's LAN, learned from VyOS (AS 64501).
What the lab costs (real output)
mac$ docker stats --no-stream --format 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}' $(docker compose -f lab-compose.yaml ps -q)
NAME             CPU %     MEM USAGE / LIMIT
arm-lab-frr-1    0.00%     44.36MiB / 7.747GiB
arm-lab-host-1   0.00%     692KiB / 7.747GiB
arm-lab-vyos-1   1.92%     1.71GiB / 7.747GiB
  • Lines 3–4: The native router and host: 44 MiB and under 1 MiB.
  • Line 5: The VyOS VM: 1.71 GiB. Budget for VMs, not containers.