TTR Networks

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

Native containers and lab networks

✓ 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

Most of a lab should be native arm64 containers, because they run at full speed and use almost no memory. Before you build on an image, check that it has an arm64 build: docker buildx imagetools inspect lists the platforms an image was published for.

FRR publishes arm64 images, so an FRR container on this Mac is a real arm64 Linux running at full speed (uname -m says aarch64). So does Debian, which the Linux hosts in these labs are built on. When an image has no arm64 build, Docker quietly pulls the amd64 one instead and runs it translated. That works, but it's worth knowing when it's happening.

Layer-2-only networks

An ordinary Docker network hands every container an IPv4 address, a subnet and a default route to Docker's own gateway. In a routing lab that's a problem: every router and host gets an extra address and an extra way out that you didn't configure, and traffic can quietly bypass the router you're trying to test.

These labs use networks with Docker's IPv4 turned off (enable_ipv4: false), plus a private IPv6 subnet only because Docker requires some subnet. A container on such a network gets no IPv4 address and no routes: it's a plain Ethernet segment. Every address in the lab is one you configured (in FRR, VyOS or ip addr), so what you see in a routing table is what you built.

You can't reach containers from the Mac

Containers live inside Docker Desktop's Linux VM, and the Mac has no route into its networks. Even an ordinary network's container address doesn't answer a ping from macOS, though other containers reach it instantly. So don't try to SSH or ping into a lab from the Mac's terminal. Work inside it with docker compose exec: docker compose exec r1 vtysh for an FRR router, docker compose exec host sh for a Linux host.

Which platforms an image has (real output)
mac$ docker buildx imagetools inspect quay.io/frrouting/frr:10.2.1 | grep Platform
  Platform:  linux/amd64
  Platform:  linux/arm64
  Platform:  linux/ppc64le
  Platform:  linux/s390x
  Platform:  linux/arm/v7
  Platform:  linux/arm/v6

mac$ docker buildx imagetools inspect debian:bookworm-slim | grep Platform
  Platform:    linux/amd64
  Platform:    unknown/unknown
  Platform:    linux/arm/v7
  Platform:    unknown/unknown
  Platform:    linux/arm64/v8
  Platform:    unknown/unknown
  Platform:    linux/386
  Platform:    unknown/unknown
  Platform:    linux/ppc64le
  Platform:    unknown/unknown

mac$ docker run --rm --entrypoint uname quay.io/frrouting/frr:10.2.1 -m
aarch64
  • Line 3: FRR publishes an arm64 build, so it runs natively.
  • Line 14: So does Debian (arm64/v8). The unknown/unknown entries are attestations, not images.
  • Lines 21–22: Proof: FRR's container reports aarch64.
The demo networks: one ordinary, one layer-2-only
# Two containers: one on an ordinary Docker network, one on a layer-2-only network.
name: l2-demo
services:
  normal:
    image: alpine
    command: sleep infinity
    networks: [normal]
  l2only:
    image: alpine
    command: sleep infinity
    networks: [l2only]
networks:
  normal: {}
  l2only:
    enable_ipv4: false
    enable_ipv6: true
    ipam: { config: [{ subnet: "fd00:99::/64" }] }
  • Line 13: An ordinary Docker network.
  • Lines 14–17: Docker's IPv4 off; an IPv6 ULA subnet only because Docker insists on one.
Ordinary versus layer-2-only (real output)
mac$ docker compose -f l2.yaml exec normal ip -4 addr show eth0
11: eth0@if1322: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    inet 172.25.0.2/16 brd 172.25.255.255 scope global eth0
       valid_lft forever preferred_lft forever

mac$ docker compose -f l2.yaml exec l2only ip -4 addr show eth0

mac$ docker compose -f l2.yaml exec normal ip route
default via 172.25.0.1 dev eth0 
172.25.0.0/16 dev eth0 scope link  src 172.25.0.2 

mac$ docker compose -f l2.yaml exec l2only ip route
  • Line 3: On an ordinary network, Docker hands out an address you didn't choose…
  • Line 9: …and a default route to Docker's gateway: a hidden way out of your lab.
  • Line 6: On the layer-2-only network: no IPv4 address at all…
  • Line 12: …and no routes. Every address and route will be yours.
From the Mac versus from inside (real output)
mac$ ping -c 2 -t 3 172.25.0.2
PING 172.25.0.2 (172.25.0.2): 56 data bytes
Request timeout for icmp_seq 0

--- 172.25.0.2 ping statistics ---
2 packets transmitted, 0 packets received, 100.0% packet loss

mac$ docker compose -f l2.yaml exec normal ping -c 2 172.25.0.2
PING 172.25.0.2 (172.25.0.2): 56 data bytes
64 bytes from 172.25.0.2: seq=0 ttl=64 time=0.027 ms
64 bytes from 172.25.0.2: seq=1 ttl=64 time=0.033 ms

--- 172.25.0.2 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.027/0.030/0.033 ms
  • Lines 1–6: macOS has no route into Docker Desktop's networks: nothing answers.
  • Lines 8–15: From another container, the same address answers in microseconds. Work inside the lab with docker compose exec.