Virtualization

Docker MACVLAN and IPvLAN Configuration on Linux: Step-by-Step Guide (2026)

23 min read

The Back Room Tech is reader-supported. We may earn a commission when you buy through links on our site. Learn more.

Docker’s default bridge network puts containers on a private internal subnet behind NAT (Network Address Translation). Other LAN devices can reach them only through ports you publish. That’s fine for most web apps. It falls apart for containers that need to act like real devices on your network, such as Pi-hole, Home Assistant, DLNA media servers, and network scanners. This guide walks through a complete docker macvlan ipvlan configuration on Linux: the exact docker network create commands, the host-to-container workaround, and fixes for the problems you’re most likely to hit.

By the end, you’ll have a container with its own IP address on your LAN. Any device on the network can reach it directly, with no port mapping. The commands target Ubuntu 24.04 LTS with Docker Engine 29.x. Network IDs, MAC addresses, timings, and version strings in the example output are illustrative and will differ on your system.

What Are the MACVLAN and IPvLAN Drivers?

MACVLAN and IPvLAN are built-in Docker Engine network drivers. They attach containers directly to a physical host interface, called the parent interface. Traffic skips Docker’s internal bridge and NAT layer. Your router and other LAN devices see each container as a separate host.

The key difference is how each driver handles MAC addresses:

MACVLANIPvLAN L2IPvLAN L3 / L3s
MAC address per containerUnique virtual MACShares host’s MACShares host’s MAC
LayerLayer 2 (switched)Layer 2 (switched)Layer 3 (routed)
Broadcast/multicastYesYesNo
Parent must accept extra MACsYes (NIC filter or promiscuous mode)NoNo
Router config neededNoNoYes (static route)
Minimum kernel3.9 (4.0+ recommended)4.2+4.2+
Best forAppliances, DHCP-aware apps, discovery protocolsSwitches with MAC limits, Wi-Fi parents, VMs with MAC filteringLarge or segmented networks, routed container subnets

When to pick which:

  • Default bridge: Use it for anything that only needs a few published ports, such as web apps, databases, and APIs. It’s simpler and works everywhere.
  • MACVLAN: Use it when the container must look like a distinct physical device. Examples: your router assigns DHCP reservations by MAC, or an app relies on mDNS/SSDP discovery.
  • IPvLAN L2: Use it when you want LAN IPs but your switch, hypervisor, or Wi-Fi adapter won’t pass multiple MAC addresses on one port.
  • IPvLAN L3: Use it when you control the router and want containers on their own routed subnet with no broadcast traffic.

Platform limitation: Docker documents the macvlan driver as Linux-only: it isn’t supported in Docker Desktop for Mac or Windows, in Docker Engine on Windows, or in rootless mode. Plan on the same limits for IPvLAN, which also needs direct access to a physical host interface. Public cloud VMs are a poor fit too. Cloud virtual networks typically deliver traffic only for the MAC and IP addresses the platform assigned to the VM, so macvlan usually fails there, and IPvLAN works only if the provider assigns the container IPs to the VM. On a Mac or Windows machine, run Docker Engine inside a Linux VM with a bridged network adapter and follow the steps there.

Official Docker documentation page for the macvlan network driver showing the page title and the introductory section

Prerequisites

Before you start, confirm you have the following:

  • A Linux host running Docker Engine 29.x (Docker Desktop won’t work). This can be bare metal, a Proxmox/ESXi VM, a NAS, or a Raspberry Pi 5
  • Linux kernel 4.2 or newer (required for IPvLAN; MACVLAN works on 3.9+). Ubuntu 24.04’s GA kernel is 6.8 (HWE kernels are newer)
  • Root or sudo access. Rootless Docker won’t work
  • SSH or console access to the host. If you plan to change host interfaces, console access is safer
  • A wired Ethernet connection for MACVLAN. Most Wi-Fi drivers and access points reject extra MAC addresses. Use IPvLAN L2 if Wi-Fi is your only option
  • Your LAN details: subnet (e.g. 192.168.1.0/24), gateway/router IP (e.g. 192.168.1.1), and your router’s DHCP pool range
  • A block of free IPs outside your router’s DHCP pool that you can give to containers
  • A second LAN device (laptop, phone) to test connectivity from

Switch, router, and hypervisor considerations:

  • MAC address limits / port security: Managed switches with port security, and some enterprise access ports, only allow 1–2 MACs per port. Every MACVLAN container adds a new MAC. If the switch hits its limit, it silently drops the traffic or shuts down the port.
  • Promiscuous mode: The parent NIC has to accept frames addressed to container MACs. On a physical host the kernel handles this: it adds each container MAC to the NIC’s unicast address filter, and falls back to promiscuous mode if the NIC can’t filter that many addresses. On virtual machines, the hypervisor usually drops frames for MACs it didn’t assign, so you must allow them at the hypervisor level. In VMware ESXi, that means promiscuous mode, MAC address changes, and forged transmits on the port group. In Hyper-V, it’s MAC address spoofing on the VM’s network adapter.
  • Dedicated NIC (optional but nice): A second network card, such as an Intel i226-V 2.5GbE adapter, gives you a clean parent interface for containers. It also keeps host management traffic separate.

Reference Environment

ComponentVersion
OSUbuntu 24.04 LTS (kernel 6.8)
Docker Engine29.x (29.0 or later)
Host IP192.168.1.50 on eth0
LAN subnet192.168.1.0/24
Gateway192.168.1.1
Router DHCP pool192.168.1.100–192.168.1.189
Container range192.168.1.192/27 (.192–.223)

Change these values to match your own network in every command below.

Step-by-Step Guide

Step 1: Confirm Docker Engine and Kernel Support

Check that you’re running Docker Engine on Linux and not a Desktop context:

docker version --format '{{.Server.Os}} / Engine {{.Server.Version}}'
uname -r

Expected output:

linux / Engine 29.x.x
6.8.0-xx-generic

Your kernel string will be newer if you run an HWE kernel. Anything 4.2 or later works.

If docker version shows Docker Desktop, or the command fails on a rootless context, stop here. These drivers won’t work in that setup.

Next, confirm the kernel modules are available:

# modinfo only reads module metadata; it changes nothing
modinfo macvlan --field description
modinfo ipvlan --field description

Expected output:

Driver for MAC address based VLANs
Driver for L3 (IPv6/IPv4) based VLANs

If either module is missing, install the extra kernel modules. On Ubuntu that’s sudo apt install linux-modules-extra-$(uname -r).

Step 2: Identify the Parent Interface

The parent interface is the NIC that connects the host to your LAN. Find it from the default route:

ip route show default

Expected output:

default via 192.168.1.1 dev eth0 proto dhcp src 192.168.1.50 metric 100

This one line gives you two values. The gateway is 192.168.1.1 and the parent interface is eth0. On current Ubuntu and Debian you’ll often see names like enp3s0 or eno1 instead. Use whatever name appears after dev.

Confirm the subnet:

ip -4 addr show eth0

Expected output:

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
inet 192.168.1.50/24 brd 192.168.1.255 scope global dynamic eth0

The /24 on the host address means your subnet is 192.168.1.0/24.

Terminal output of `ip route show default` with the default route line visible, highlighting the gateway 192.168.1.1 and the interface name eth0 after "dev"

Tip: If the parent interface is a Linux bridge such as vmbr0 on Proxmox or br0 on a NAS, use the bridge itself as the parent. It usually works better than the bridge’s physical member port.

Step 3: Reserve an Address Range Outside Your DHCP Pool

Docker doesn’t ask your router for addresses. Docker’s own IPAM (IP Address Management) hands out container IPs from the range you define. It can’t see your router’s DHCP lease table. If the two ranges overlap, you’ll get duplicate IPs.

Log in to your router and note the DHCP pool. In this example it’s .100–.189. Pick a block outside it. This guide uses 192.168.1.192/27, which covers .192 to .223 and gives 32 addresses.

Before you commit to the range, check that nothing already uses it. Ping a few addresses from the host:

# -c 1 sends a single ping; -W 1 waits 1 second for a reply
for ip in 192 200 210 223; do ping -c 1 -W 1 192.168.1.$ip > /dev/null && echo "192.168.1.$ip IN USE" || echo "192.168.1.$ip free"; done

Expected output:

192.168.1.192 free
192.168.1.200 free
192.168.1.210 free
192.168.1.223 free

If you have a network scanner handy, such as nmap -sn 192.168.1.192/27, it’s more thorough than ping. Some devices don’t answer ICMP.

Step 4: Create the MACVLAN Network

Now create the network. Every flag here matters:

# -d macvlan         : use the macvlan driver
# --subnet           : must match your real LAN subnet exactly
# --gateway          : your router IP (set it explicitly; see callout below)
# --ip-range         : Docker only assigns container IPs from this block
# --aux-address      : reserves .223 so Docker never gives it to a container (we use it for the host shim in Step 8)
# -o parent=eth0     : attach to the host's LAN interface
sudo docker network create -d macvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  --ip-range=192.168.1.192/27 \
  --aux-address="host-shim=192.168.1.223" \
  -o parent=eth0 \
  lan_macvlan

Expected output is the new network ID:

3f9c2a7e1b04d5c86e2f1a9b7d3c0e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d

Terminal showing the full multi-line docker network create -d macvlan command with --subnet, --gateway, --ip-range, --aux-address and -o parent=eth0 flags, followed by the returned 64-character network ID

Important: set --gateway on Docker Engine 29.0 and later. According to the Docker Engine 29 release notes, starting with 29.0.0, macvlan and IPvLAN L2 networks don’t configure a default gateway in containers unless you pass --gateway explicitly. Without it, containers can reach the local subnet but have no route to the internet. Always set --gateway to your router’s real IP. It’s correct on older engines too, where the auto-picked first address was often wrong anyway.

The macvlan driver defaults to bridge mode. That mode lets containers on the same macvlan network talk to each other directly. You’ll almost never need to change it. The other modes (vepa, private, passthru) are for specialized switch setups.

Step 5: Inspect the Network

Verify Docker stored what you intended:

docker network inspect lan_macvlan --format '{{json .IPAM.Config}} {{json .Options}} {{.Driver}}'

Expected output:

[{“Subnet”:”192.168.1.0/24″,”IPRange”:”192.168.1.192/27″,”Gateway”:”192.168.1.1″,”AuxiliaryAddresses”:{“host-shim”:”192.168.1.223″}}] {“parent”:”eth0″} macvlan

For the full JSON view, run docker network inspect lan_macvlan without --format. Check three things: “Driver”: “macvlan”, the IPAM block with your subnet and gateway, and “parent”: “eth0” under Options.

Full JSON output of docker network inspect lan_macvlan showing "Driver": "macvlan", the IPAM Config block with Subnet 192.168.1.0/24, IPRange 192.168.1.192/27 and Gateway 192.168.1.1, and Options containing "parent": "eth0"

Step 6: Attach a Container With a Fixed IP

Start a test Nginx container on the new network with a static address from your range:

# --network attaches only to lan_macvlan (no bridge, no NAT)
# --ip pins the address; omit it to let Docker pick the next free IP from --ip-range
# no -p flag needed: every port the container listens on is reachable at its own IP
sudo docker run -d \
  --name lan-test \
  --network lan_macvlan \
  --ip 192.168.1.200 \
  nginx:stable

Confirm it’s running and check its address:

docker ps --filter name=lan-test --format 'table {{.Names}}\t{{.Status}}\t{{.Networks}}'
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{.MacAddress}}{{end}}' lan-test

Expected output:

NAMES STATUS NETWORKS
lan-test Up 8 seconds lan_macvlan
192.168.1.200 02:42:c0:a8:01:c8

Docker generates that MAC address, and it’s unique to this container. That’s the “MAC” in MACVLAN. The PORTS column would be empty, too. You don’t publish ports on macvlan, because the container owns its IP directly.

Terminal showing docker run with --network lan_macvlan --ip 192.168.1.200, followed by docker ps output with lan-test in "Up" status on lan_macvlan and docker inspect returning 192.168.1.200 and a 02:42 MAC address

Step 7: Verify From Another LAN Device

Test from a different machine on the LAN, such as a laptop or another server. Don’t test from the Docker host itself. Step 8 explains why that fails.

ping -c 3 192.168.1.200

Expected output:

PING 192.168.1.200 (192.168.1.200) 56(84) bytes of data.
64 bytes from 192.168.1.200: icmp_seq=1 ttl=64 time=0.512 ms
64 bytes from 192.168.1.200: icmp_seq=2 ttl=64 time=0.387 ms
64 bytes from 192.168.1.200: icmp_seq=3 ttl=64 time=0.401 ms

Sub-millisecond replies, which is what you want on a local switch. Then check that Nginx answers on port 80 without any port mapping:

curl -I http://192.168.1.200

Expected output:

HTTP/1.1 200 OK
Server: nginx/1.x.x
Content-Type: text/html

The exact nginx version in the Server header depends on what the nginx:stable tag points to when you pull it.

Also confirm the container can reach the internet through your gateway:

sudo docker exec lan-test getent hosts docker.com

A returned IP address means DNS and the default route both work. If it hangs, jump to the “no default gateway” issue in troubleshooting.

Terminal on a second LAN machine showing successful ping replies from 192.168.1.200 with ttl=64 and curl -I returning HTTP/1.1 200 OK with an nginx Server header

Step 8: Enable Host-to-Container Communication

Try pinging the container from the Docker host itself:

ping -c 2 192.168.1.200

From 192.168.1.50 icmp_seq=1 Destination Host Unreachable
From 192.168.1.50 icmp_seq=2 Destination Host Unreachable

That’s expected, and it catches almost everyone once. The Linux kernel deliberately blocks traffic between a macvlan (or ipvlan) sub-interface and its own parent. Everything else on the LAN can reach the container, but the host can’t. It bites when a reverse proxy on the host forwards to the container. It also bites when the host uses a Pi-hole container for DNS.

There are two documented workarounds.

Workaround A: Attach the container to a bridge network too. This is the fastest option:

# adds a second interface on Docker's default bridge; the host reaches the container through it
sudo docker network connect bridge lan-test
docker inspect -f '{{.NetworkSettings.Networks.bridge.IPAddress}}' lan-test

172.17.0.2

The host can now reach the container at 172.17.0.2. The trade-off: the container now has two interfaces, two IP addresses, and possibly two candidate default routes. Depending on which default route wins, outbound traffic and DNS lookups might leave through the bridge’s NAT instead of the macvlan. An app that binds to one specific address only answers on that interface, so it must listen on both addresses (or 0.0.0.0) for this to help. That’s fine for “host talks to container” traffic. It’s messy for anything that cares about source IPs, and it’s a quick fix rather than a production pattern. Undo it with sudo docker network disconnect bridge lan-test.

Workaround B: Create a macvlan shim interface on the host. This one’s cleaner. You give the host its own macvlan interface on the same parent. The host then talks to containers macvlan to macvlan, which the kernel allows. This is where the .223 aux-address from Step 4 comes in:

# create a macvlan interface on the host, attached to the same parent, in bridge mode
sudo ip link add macvlan-shim link eth0 type macvlan mode bridge
# /32 = only this one address; avoids adding a conflicting route for the whole /24
sudo ip addr add 192.168.1.223/32 dev macvlan-shim
sudo ip link set macvlan-shim up
# send traffic for the container range out the shim instead of eth0
sudo ip route add 192.168.1.192/27 dev macvlan-shim

Why this route works: the host already has a connected route for 192.168.1.0/24 on eth0. Linux picks the most specific matching route, so the /27 via the shim wins for .192–.223 while the rest of the LAN still goes out eth0. Before adding it, run ip route show 192.168.1.192/27. If a route for that range already exists, ip route add fails with “File exists”. Remove the old route first, or use ip route replace instead of add.

Test it:

ping -c 2 192.168.1.200

64 bytes from 192.168.1.200: icmp_seq=1 ttl=64 time=0.061 ms
64 bytes from 192.168.1.200: icmp_seq=2 ttl=64 time=0.048 ms

Warning: These ip commands don’t survive a reboot, and a typo in ip route can break host networking. Run them from a local console or IPMI/iKVM session the first time. Don’t run them over an SSH session you depend on.

To make the shim persistent, create a systemd unit:

# /etc/systemd/system/macvlan-shim.service
[Unit]
Description=Host macvlan shim for Docker macvlan containers
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
# the leading "-" ignores the error if no old shim exists; this makes restarts safe
ExecStartPre=-/usr/sbin/ip link del macvlan-shim
ExecStart=/usr/sbin/ip link add macvlan-shim link eth0 type macvlan mode bridge
ExecStart=/usr/sbin/ip addr add 192.168.1.223/32 dev macvlan-shim
ExecStart=/usr/sbin/ip link set macvlan-shim up
ExecStart=/usr/sbin/ip route replace 192.168.1.192/27 dev macvlan-shim
ExecStop=/usr/sbin/ip link del macvlan-shim

[Install]
WantedBy=multi-user.target

Enable it:

sudo systemctl daemon-reload
sudo systemctl enable macvlan-shim.service

Created symlink /etc/systemd/system/multi-user.target.wants/macvlan-shim.service → /etc/systemd/system/macvlan-shim.service.

The unit takes over from the next boot. Because of the ExecStartPre cleanup line, you can also run sudo systemctl restart macvlan-shim.service at any time to rebuild the shim, for example after the parent NIC was reset. The unit hard-codes eth0, the .223 address, and the /27 range. If you rename the interface or change the container range, update the unit to match, then run sudo systemctl daemon-reload. Check it after a reboot with ip addr show macvlan-shim and ip route show 192.168.1.192/27.

Step 9: Create an IPvLAN L2 Network

IPvLAN L2 looks like macvlan from the LAN’s point of view. The difference is that every container uses the host’s MAC address. Your switch sees one MAC and many IPs.

The kernel won’t let macvlan and ipvlan interfaces share one parent at the same time. While a macvlan container is running on eth0, starting an ipvlan container on eth0 fails with a “device or resource busy” error, even though docker network create itself may succeed. Docker’s ipvlan driver also refuses to create a second ipvlan network on a parent that another ipvlan network already uses. (Current Engine releases do allow several macvlan networks on one parent, except in passthru mode.) Clean up the macvlan test first:

Warning: The commands below stop and delete the lan-test container and the lan_macvlan network. Skip this if you’re keeping the macvlan setup and plan to use IPvLAN on a different NIC or VLAN.

sudo docker rm -f lan-test
sudo docker network rm lan_macvlan

Now create the IPvLAN L2 network:

# -d ipvlan              : use the ipvlan driver
# -o ipvlan_mode=l2      : Layer 2 mode (the default, stated here for clarity)
# --gateway              : required for a working default route on Engine 29.0+
sudo docker network create -d ipvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  --ip-range=192.168.1.192/27 \
  --aux-address="host-shim=192.168.1.223" \
  -o parent=eth0 \
  -o ipvlan_mode=l2 \
  lan_ipvlan

8b1e4d2f6a3c9e0b7d5f1a2c4e6b8d0f2a4c6e8b0d2f4a6c8e0b2d4f6a8c0e2b

Terminal showing docker network create -d ipvlan with --subnet, --gateway, --ip-range, -o parent=eth0 and -o ipvlan_mode=l2, followed by the returned network ID

Run a container and check its MAC:

sudo docker run -d --name ipvlan-test --network lan_ipvlan --ip 192.168.1.201 nginx:stable
sudo docker exec ipvlan-test cat /sys/class/net/eth0/address
cat /sys/class/net/eth0/address

bc:24:11:5a:7e:03
bc:24:11:5a:7e:03

Both lines match, so the container shares the host’s MAC. Ping 192.168.1.201 from another LAN device to verify, just as in Step 7.

The shared MAC has a cost. Anything that identifies clients by MAC sees all your ipvlan containers as one device. That includes DHCP reservations, router client lists, and parental controls. The host-to-container limitation still applies too. For an ipvlan shim, use type ipvlan mode l2 instead of type macvlan mode bridge in the Step 8 commands.

Docker Docs IPvlan network driver page showing the options table for ipvlan_mode, ipvlan_flag and parent with their values and defaults

Step 10: Create an IPvLAN L3 Network (Optional)

L3 mode turns the host into a router for a separate container subnet. There’s no broadcast or ARP between the container subnet and the LAN, so discovery protocols won’t work. The upside: it scales cleanly and keeps broadcast noise off your network.

Use a subnet that doesn’t exist on your LAN:

# -o ipvlan_mode=l3 : routed mode; the container's default route points at its own interface
# --gateway is ignored in L3 mode, so it's omitted here
sudo docker network create -d ipvlan \
  --subnet=10.50.0.0/24 \
  -o parent=eth0 \
  -o ipvlan_mode=l3 \
  routed_ipvlan
sudo docker run -d --name l3-test --network routed_ipvlan --ip 10.50.0.10 nginx:stable

LAN devices don’t know how to reach 10.50.0.0/24 yet. Add a static route on your router: destination 10.50.0.0/24, next hop 192.168.1.50 (the Docker host). The exact menu varies by router. That route is the return path: without it, replies to the containers never find their way back. On a Linux client, you can test without touching the router:

# run on another Linux machine on the LAN
sudo ip route add 10.50.0.0/24 via 192.168.1.50
ping -c 2 10.50.0.10

Internet access from the routed subnet: The static route lets LAN clients reach the containers. For the containers to reach the internet, your router must also NAT traffic from 10.50.0.0/24. Many consumer routers only NAT their own LAN subnet, so check that setting (or add an outbound NAT rule) if containers can reach the LAN but not the internet. Any firewall between the router and the Docker host must permit the routed subnet as well.

L3 vs L3s: l3s (L3 symmetric) behaves like l3, but traffic passes through the host’s netfilter (iptables/nftables) hooks. So host firewall rules apply to container traffic. Choose l3s if you need to filter container traffic on the host. It needs a fairly recent kernel and Docker Engine, and Ubuntu 24.04 with Engine 29.x qualifies. Use -o ipvlan_mode=l3s in the same command.

Configuration

Key Options Reference

FlagDriverPurposeExample
-d / --driverbothSelect drivermacvlan, ipvlan
--subnetbothLAN (or routed) subnet192.168.1.0/24
--gatewayboth (ignored in L3)Router IP; must be explicit on 29.0+192.168.1.1
--ip-rangebothPool Docker assigns from192.168.1.192/27
--aux-addressbothReserve IPs Docker must skiphost-shim=192.168.1.223
-o parent=bothHost interface to attach toeth0, eth0.20
-o macvlan_mode=macvlanbridge (default), vepa, private, passthrubridge
-o ipvlan_mode=ipvlanl2 (default), l3, l3sl2
-o ipvlan_flag=ipvlanbridge (default), private, vepabridge

Placing Containers on a VLAN (802.1q)

If your switch supports VLAN tagging, set the parent to a dotted sub-interface. Docker creates the tagged interface when you create the network and removes it when you delete the network. A managed switch such as the TP-Link TL-SG108E or a UniFi switch can carry the tagged traffic.

# eth0.20 = VLAN ID 20 on eth0; the switch port must carry VLAN 20 tagged
sudo docker network create -d macvlan \
  --subnet=192.168.20.0/24 \
  --gateway=192.168.20.1 \
  --ip-range=192.168.20.64/26 \
  -o parent=eth0.20 \
  iot_vlan20

This is also the clean way to run both macvlan and ipvlan on one physical NIC. Put each driver on a different VLAN sub-interface.

Docker Compose Example

Create the network once with docker network create as shown above. Then reference it as external so Compose doesn’t try to manage it:

# /opt/stacks/pihole/compose.yaml
services:
  pihole:
    image: pihole/pihole:latest
    container_name: pihole
    restart: unless-stopped
    networks:
      lan_macvlan:
        ipv4_address: 192.168.1.210   # must be inside --ip-range
    environment:
      TZ: "America/New_York"
    volumes:
      - /opt/stacks/pihole/etc-pihole:/etc/pihole

networks:
  lan_macvlan:
    external: true   # created manually; Compose won't recreate or delete it
cd /opt/stacks/pihole && sudo docker compose up -d

Tips and Troubleshooting

Error: -o parent interface was not found on the host: eth0

Why it happens: The interface name is wrong. Many hosts use predictable names like enp3s0.

Fix: Run ip route show default, copy the name after dev, and recreate the network with -o parent= set to that name.

Error: network dm-xxxxxxxx is already using parent interface eth0

Why it happens: Another ipvlan network already uses that parent. Docker’s ipvlan driver allows only one ipvlan network per parent interface. Macvlan networks hit a similar error only when one of them uses passthru mode. A different symptom, “device or resource busy” when a container starts, means a macvlan and an ipvlan container are trying to share the same parent.

Fix: Find the existing network with docker network ls --filter driver=ipvlan (or driver=macvlan). Either reuse it or remove it with sudo docker network rm NETWORK_NAME. To run both drivers, use separate NICs or separate VLAN sub-interfaces (eth0.10, eth0.20).

Container Works on the LAN but Can’t Reach the Internet

Why it happens: There’s no default gateway inside the container. On Docker Engine 29.0 and later, you get this when the network was created without --gateway. On any version, you get it when --gateway points to the wrong IP.

Fix: Check the route inside the container:

sudo docker exec lan-test cat /proc/net/route

If there’s no line with destination 00000000, the container has no default route. Remove and recreate the network with --gateway=192.168.1.1, set to your router’s real address. Disconnect containers first (docker rm -f or docker network disconnect).

No Connectivity at All From the LAN

Why it happens: It’s usually one of four things. The parent interface is wrong. The subnet doesn’t match the real LAN. A switch port security feature is dropping unknown MACs. Or the host is a VM whose hypervisor filters MACs, or a cloud VM.

Fix:

  • Recheck -o parent=, --subnet, and --gateway with docker network inspect.
  • From another device, run ip neigh show 192.168.1.200 after a ping. If you see FAILED or INCOMPLETE, ARP isn’t getting through. That points to a Layer 2 block.
  • On a VM, enable promiscuous mode, forged transmits, and MAC changes (ESXi) or MAC address spoofing (Hyper-V). On Proxmox, check that the VM’s NIC has the firewall off or permits the extra MACs.
  • On a managed switch, raise or disable the per-port MAC limit on the host’s port.
  • If none of that is an option, switch to IPvLAN L2. It uses only the host’s MAC.

Container Didn’t Get an IP From My Router’s DHCP

Why it happens: Docker’s IPAM assigns container IPs itself. It never sends a DHCP request to your router, so the router won’t show a lease for the container.

Fix: Nothing’s broken here; that’s how Docker’s IPAM works. Use --ip-range to keep Docker’s pool outside the router’s DHCP pool. Assign --ip explicitly for anything you need to find again. If your router needs to know about the container, add a static DNS entry for its IP. Running a DHCP client inside the container fights with Docker’s IPAM and isn’t supported.

Duplicate IP / Intermittent Connectivity

Why it happens: A container IP collides with another device. Usually the container range overlaps the DHCP pool, or a device has a manually set static IP.

Fix: From another machine, run ip neigh show 192.168.1.200 a few times. If the MAC keeps changing, two devices are fighting over the address. Move the container and shrink --ip-range so it’s outside all DHCP and static assignments. Use --aux-address to block any address you know is taken.

Doesn’t Work on Mac, Windows, or a Cloud VM

Why it happens: Docker Desktop, Docker on Windows, and rootless Docker don’t support the macvlan driver, and IPvLAN needs the same direct NIC access. Cloud virtual networks typically drop traffic from MACs they didn’t assign.

Fix: Run Docker Engine on a Linux host with real Layer 2 access. That could be a mini PC, a NAS, a Raspberry Pi 5, or a VM with a bridged adapter (NAT adapters won’t work). In the cloud, stick with bridge networking plus published ports, or use the provider’s secondary-IP features.

Quick Tips

  • Test from a second device, always. Most “macvlan is broken” reports turn out to be people pinging from the Docker host.
  • Document your IP plan. One comment in your Compose file listing the router’s DHCP range and the Docker range saves you from collisions six months from now.
  • Put the host on a UPS if containers provide DNS or DHCP. When a Pi-hole or DHCP container goes down with the host, the whole LAN notices. So does everyone else in the house.

Wrapping Up

You now have containers with real LAN addresses. MACVLAN handles apps that need to look like separate physical devices. IPvLAN L2 handles networks that won’t tolerate extra MACs, and IPvLAN L3 handles routed subnets where you control the router. The shim closes the host-to-container gap, and an explicit --gateway keeps the Engine 29 gateway behavior from biting you.

My take: default to bridge networking, and only reach for these drivers when a container truly needs Layer 2 presence. When it does, MACVLAN is the least surprising choice on wired networks. IPvLAN L2 is the fallback the moment a switch or hypervisor starts dropping MACs.

StepActionApplies To
1–3Verify engine/kernel, find parent, plan IP rangeBoth drivers
4–7Create, inspect, attach, verifyMACVLAN
8Bridge attach or host shimBoth drivers
9Create L2 network (shared MAC)IPvLAN L2
10Create routed subnet + static routeIPvLAN L3/L3s

Resources