Build it, step by step
This is the whole process from a fresh Mac to the starter lab running, in order. Allow 15 minutes, most of it downloading the VyOS ISO and waiting for the first boot.
- Install Docker Desktop for Mac with Apple silicon. In its settings, give it at least 8 GB of memory (Resources), and turn on Rosetta for x86_64/amd64 emulation (General) if it isn't already.
- Check it with the commands from Check your Mac: the server should say
arm64, and an--platform linux/amd64container should printx86_64. - Put the lab files in one directory:
lab-compose.yaml,Dockerfile.vyos,start-vyos.sh,frr.conf,frr-daemons,vtysh.conf,get-vyos.shandvyos-console.py, all from this lesson. Make the two scripts executable:chmod +x start-vyos.sh get-vyos.sh vyos-console.py. - Fetch VyOS (about 630 MB):
./get-vyos.sh. It leavesvyos.iso,vmlinuzandinitrd.imgin./vyos. macOS'starcan read the ISO directly. - Start the lab:
docker compose -f lab-compose.yaml up -d --build. The first build downloads Debian and QEMU; later starts take seconds. - Wait for VyOS:
docker compose -f lab-compose.yaml exec vyos vyos-console.py wait 900, then… vyos-console.py login. Expect about 4 minutes. - Configure VyOS with the six
setlines from the starter lab, throughvyos-console.py conf '…' '…', or interactively on the console:docker compose -f lab-compose.yaml exec -it vyos socat -,raw,echo=0,escape=0x1d UNIX-CONNECT:/run/console.sock(Ctrl-] to leave). - Check it:
show ip bgp summaryon VyOS,vtysh -c 'show bgp ipv4 unicast'on FRR, and a ping from the host to 198.51.100.1. - Stop it when you're done:
docker compose -f lab-compose.yaml down. VyOS boots from the ISO each time, so it starts fresh; keep yoursetcommands in a file and re-apply them.
From here, every lab on this site is a variation: more FRR containers, more VyOS VMs (each with its own VM_ID), more Linux hosts, more layer-2-only networks.
# Download the VyOS rolling ISO used in this lesson and extract its kernel and initrd.
mkdir -p vyos && cd vyos
curl -LO https://github.com/vyos/vyos-nightly-build/releases/download/2026.10.16-0024-rolling/vyos-2026.10.16-0024-rolling-generic-amd64.iso
mv vyos-*-amd64.iso vyos.iso
tar -xf vyos.iso live/vmlinuz live/initrd.img # bsdtar (macOS) reads ISOs; on Linux use 7z or bsdtar
mv live/vmlinuz live/initrd.img . && rmdir livebgpd=yes
ospfd=no
ospf6d=no
ripd=no
ripngd=no
isisd=no
pimd=no
ldpd=no
nhrpd=no
eigrpd=no
babeld=no
sharpd=no
pbrd=no
bfdd=no
fabricd=no
vrrpd=no
pathd=no
staticd=yes
vtysh_enable=yes
zebra_options=" -A 127.0.0.1 -s 90000000"
bgpd_options=" -A 127.0.0.1"
staticd_options="-A 127.0.0.1"service integrated-vtysh-config#!/usr/bin/env python3
"""Drive the VyOS VM's serial console (QEMU unix socket) and print a clean transcript.
vyos-console.py wait [secs] wait for the login prompt
vyos-console.py login log in as vyos/vyos
vyos-console.py op 'show version' ... run operational commands
vyos-console.py conf 'set ...' ... configure, run set/delete lines, commit, exit
vyos-console.py capture SECS 'cmd' run a command that needs Ctrl-C (monitor traffic), stop after SECS
"""
import os, re, socket, sys, time
SOCK = os.environ.get('VYOS_CONSOLE', '/run/console.sock')
ANSI = re.compile(r'\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b[=>()][0-9A-Za-z]?|\r|\x07|\x08')
OP_PROMPT = r'vyos@[\w-]+:~\$ $'
CONF_PROMPT = r'vyos@[\w-]+# $'
def connect():
s = socket.socket(socket.AF_UNIX)
s.connect(SOCK)
s.settimeout(0.3)
return s
def read_until(s, pattern, timeout):
buf, end = '', time.time() + timeout
while time.time() < end:
try:
data = s.recv(65536)
except socket.timeout:
data = b''
if data:
buf += data.decode('utf-8', 'replace')
if re.search(pattern, ANSI.sub('', buf)):
return ANSI.sub('', buf), True
return ANSI.sub('', buf), False
def send(s, line):
s.sendall(line.encode() + b'\n')
def run(s, cmd, prompt, timeout=300):
send(s, cmd)
out, ok = read_until(s, prompt, timeout)
if not ok:
sys.stderr.write(f'!! timeout waiting for prompt after: {cmd}\n')
return out
def main():
mode, args = sys.argv[1], sys.argv[2:]
s = connect()
if mode == 'wait':
end = time.time() + float(args[0] if args else 900)
while time.time() < end:
send(s, '')
out, ok = read_until(s, r'login: $|\$ $|# $', 10)
if ok:
print('ready:', out.strip().splitlines()[-1])
return
sys.exit('timeout waiting for login prompt')
if mode == 'login':
send(s, '')
out, _ = read_until(s, r'login: $|\$ $', 30)
if out.rstrip().endswith('$'):
print('already logged in'); return
send(s, 'vyos'); read_until(s, r'Password: $', 60)
send(s, 'vyos'); out, ok = read_until(s, OP_PROMPT, 300)
print('logged in' if ok else 'login failed:\n' + out[-500:])
for c in ('set terminal length 0', 'set terminal width 200'):
run(s, c, OP_PROMPT, 60)
return
if mode == 'op':
send(s, ''); read_until(s, OP_PROMPT, 30)
for cmd in args:
sys.stdout.write(run(s, cmd, OP_PROMPT))
print()
return
if mode == 'conf':
send(s, ''); read_until(s, OP_PROMPT, 30)
transcript = run(s, 'configure', CONF_PROMPT, 120)
for cmd in args:
transcript += run(s, cmd, CONF_PROMPT, 120)
transcript += run(s, 'commit', CONF_PROMPT, 900)
transcript += run(s, 'exit', OP_PROMPT, 120)
sys.stdout.write(transcript)
print()
return
if mode == 'capture':
secs, cmd = float(args[0]), args[1]
send(s, ''); read_until(s, OP_PROMPT, 30)
send(s, cmd)
out, _ = read_until(s, r'(?!)', secs) # collect output for SECS
s.sendall(b'\x03')
rest, _ = read_until(s, OP_PROMPT, 60)
sys.stdout.write(out + rest)
print()
return
sys.exit(__doc__)
main()Where next
- Switching basics: layer 2, VLANs and IP addressingAlso in Foundations · Beginner · 40 min
How a switch learns MAC addresses and floods broadcasts, how IP subnets and default gateways decide where packets go, how VLANs split a switch into separate networks, and how trunks and a router connect them. Built on a real switch, three PCs and a VyOS router.
- Why not just keep it simple?Also in Foundations · Beginner · 25 min
Why networks use routing protocols, BGP and redundant links instead of a few static routes, when they shouldn't, and what the extra complexity buys. With a real experiment: one quiet link failure, three designs, measured.
- Linux Network ConnectivityAlso in Foundations · Beginner · 80 min
Find out why a Linux machine can't reach something, layer by layer: interfaces and addresses, routes and ip route get, ARP and the neighbour table, ping and traceroute, DNS, and listening sockets — with real output from a three-host lab.
- Setting up VyOS with NATAlso on VyOS · Beginner · 45 min
A VyOS router with a NAT interface for a private LAN and a public interface for a routed public subnet: plain routing, source NAT, port forwarding and the forward firewall that NAT isn't. Every output comes from a real VyOS lab.