Pitfalls and their fixes
These are the problems that actually stopped labs on this Mac, and what fixed each one. Most of them fail quietly, so knowing them in advance saves hours.
What the kernel can't do
Docker Desktop's Linux kernel is built for running containers, not routers, and some features network labs need are simply missing. Checking its configuration (/proc/config.gz) shows, among others:
- No TCP-MD5 or TCP-AO. BGP passwords can't be enforced. FRR logs Unable to set TCP MD5 option and brings the session up without authentication. The BGP debugging lesson shows exactly this.
- No VRF. Linux VRFs (
ip link add … type vrf) can't be created, so labs that need VRFs on Linux or FRR containers won't work. A VyOS VM has its own kernel, so VRFs inside VyOS are unaffected. - No SRv6 lightweight tunnels.
- Present: MPLS, VXLAN, Geneve, WireGuard, bonding, 802.1Q with VLAN-filtering bridges, nftables, GRE, IPsec (XFRM) and netem for simulating delay and loss.
Features inside a full VM (VyOS) depend on the VM's own kernel, not Docker Desktop's: this list is about containers only.
Never bridge Docker networks together inside a container
The tempting way to build a "switch" is one container with a Linux bridge and one Docker network per port. Don't. Docker's own bridges reflect broadcasts back to the port they came from, so a bridge joining two Docker networks turns the first ARP request into a broadcast storm. It hung Docker Desktop completely, twice, until it was restarted.
For switching labs, build the whole switch fabric inside one container instead: network_mode: none, network namespaces as hosts, veth pairs as cables, and a tap for any VyOS VM. The switching lesson builds a lab exactly this way. (The VyOS start script does bridge Docker interfaces, but each bridge joins one Docker network to one tap, never two networks together, and it turns off MAC learning on the Docker side, because Docker reflects the VM's own broadcasts back and would otherwise teach the bridge the VM's MAC address on the wrong port.)
Smaller traps
| Symptom | Cause | Fix |
|---|---|---|
| A VM container exits with code 137 | Out of memory: Docker Desktop's VM ran out | Raise Docker Desktop's memory, lower VYOS_MEM, or run fewer VMs |
| Two VyOS VMs on one network can't talk | Identical MAC addresses | A different VM_ID for every VM |
| VyOS VM freezes under load | Multi-vCPU TCG guest | VYOS_SMP=1 |
| DHCPv6 client on VyOS fails | No SMBIOS UUID, so no DUID | Keep the -uuid line in start-vyos.sh |
sysctl -w fails: read-only file system | Containers get a read-only /proc/sys | privileged: true, or sysctls: in compose |
| The first command after boot goes to the login prompt | VyOS shows login: before it has finished starting | After wait, run login and check it says logged in; retry if not |
docker compose exec hangs | A script piped in on stdin | Pass it as an argument: sh -c "$(cat script.sh)" |
exec format error | No interpreter for amd64 binaries | Rosetta in Docker Desktop's settings, or the tonistiigi/binfmt install (re-run after restarts) |
| Can't open configuration file /etc/frr/vtysh.conf on every FRR command | The FRR image ships without one | Mount a one-line vtysh.conf: service integrated-vtysh-config |
| Unexpected addresses and default routes in containers | An ordinary Docker network | enable_ipv4: false on lab networks |
| Captured packets bigger than the MTU | Segmentation offload in the virtual NICs | ethtool -K eth0 tso off gso off gro off before capturing |
mac$ docker run --rm alpine sh -c 'zcat /proc/config.gz | grep -E "^(# )?CONFIG_(TCP_MD5SIG|TCP_AO|NET_VRF|MPLS_ROUTING|WIREGUARD|VXLAN|BONDING|BRIDGE_VLAN_FILTERING|VLAN_8021Q|XFRM_USER|NF_TABLES|NET_SCH_NETEM|MACVLAN|GENEVE|NET_IPGRE|IPV6_SEG6_LWTUNNEL)[= ]"'
CONFIG_XFRM_USER=y
CONFIG_NET_IPGRE=y
# CONFIG_TCP_AO is not set
# CONFIG_TCP_MD5SIG is not set
# CONFIG_IPV6_SEG6_LWTUNNEL is not set
CONFIG_NF_TABLES=y
CONFIG_BRIDGE_VLAN_FILTERING=y
CONFIG_VLAN_8021Q=y
CONFIG_NET_SCH_NETEM=y
CONFIG_MPLS_ROUTING=y
CONFIG_BONDING=y
CONFIG_WIREGUARD=y
CONFIG_MACVLAN=y
CONFIG_VXLAN=y
CONFIG_GENEVE=y
# CONFIG_NET_VRF is not set- Lines 4–5: No TCP-AO or TCP-MD5: BGP passwords can't be enforced in containers.
- Line 6: No SRv6 lightweight tunnels.
- Line 17: No VRFs.
- Lines 7–16: Everything else a typical lab needs is here: nftables, VLAN-filtering bridges, 802.1Q, netem, MPLS, bonding, WireGuard, macvlan, VXLAN and Geneve, plus GRE and IPsec (lines 2–3).
Check your understanding
You want a switching lab: four hosts on one switch. What's the safe way to build it on Docker Desktop?
- One container with network_mode: none, namespaces as hosts, veth pairs as cables and a Linux bridge as the switch.
- A switch container with a Linux bridge joining four Docker networks, one per host.
- Four hosts on one ordinary Docker network.
- A VyOS VM with four Docker networks bridged in one bridge group.