Parent-attached modes (macvlan and ipvlan)¶
docker-net-dhcp supports three attachment modes:
| mode | how containers reach the LAN | each child's MAC | host changes required |
|---|---|---|---|
bridge |
a veth pair plugged into a Linux bridge you maintain, or one the plugin makes from a spare NIC (parent, v2.3.0) |
random per veth | yes: you bring the bridge, or a spare NIC that stays up with no address |
macvlan |
a per-container macvlan child (bridge mode by default) of a host NIC |
distinct (kernel-generated); the parent's under passthru |
none, the host NIC is untouched; passthru takes it from the host |
ipvlan |
a per-container ipvlan child (L2 mode) of a host NIC | shared with parent | none, the host NIC is untouched |
Picking between macvlan and ipvlan¶
Both modes attach directly to a host NIC and require no Linux bridge. The difference is at L2: each container's MAC.
macvlanis the default choice. Each container gets its own MAC, which is what most LANs and DHCP servers expect. The Fritz.Box (or any home/SOHO router) sees each container as a fully distinct device.ipvlanis the right pick when the upstream switch or hypervisor refuses to bridge multiple MACs from one port. Common triggers: managed switches with sticky-MAC port-security enabled, Wi-Fi access points that refuse multi-MAC bridging, hypervisor vSwitches with strict port policies. Children share the parent's MAC; the LAN distinguishes containers by IP only.
If macvlan works on your LAN, use macvlan. ipvlan is the escape hatch for hostile L2.
Quick start¶
Create a network attached to one of the host's NICs (eth0 below;
substitute yours, and ip -brief link lists them):
# On arm64 use the -arm64 tag. A network stores this exact reference
# as its driver, so it must name the plugin you installed.
docker network create \
--driver=ghcr.io/claymore666/docker-net-dhcp:v2.3.1 \
--ipam-driver=null \
-o mode=macvlan \
-o parent=eth0 \
lan-dhcp
Then attach any container the usual way. No static IP, no labels, no
sidecar, no cap_add:
docker compose up -d
docker inspect app | jq '.[0].NetworkSettings.Networks'
# IPAddress is the lease your DHCP server handed out
What happens under the hood¶
docker runtriggers libnetwork'sCreateEndpointagainst the plugin.- The plugin creates a macvlan child on the parent NIC (submode = bridge, so children on the same parent can talk to each other), still in the host netns.
- A one-shot DHCP acquisition runs on the new link: DHCPDISCOVER → REQUEST → ACK from your LAN's DHCP server. The lease (IP, mask, gateway) is captured.
- The plugin returns the link name to libnetwork via
Join. Docker moves the link into the container's netns and renames it (typicallyeth0). - A persistent DHCP client runs inside the container netns to renew the lease for the lifetime of the endpoint. It is a goroutine in the plugin process and never a child process, and it never configures the link itself: the plugin applies every lease change via netlink.
- On
docker stop, libnetwork callsLeave→ the persistent client is stopped. By default it does not release the lease: the address stays leased until it expires, or until the container comes back and re-claims it, exactly as it would for a physical host that rebooted (v1.9.0+, #800). A network created withrelease_lease=on_stophands it back here instead, and gives up the stable MAC and address across a restart to do so (v2.1.1+, #962). A network created withrelease_lease=on_removekeeps both, and a sweep hands the addresses back about a minute later if no container has claimed them (v2.2.0+, #984). - The macvlan link is reaped automatically when the container netns is destroyed.
The host's NIC config (IP, routes, netplan/systemd-networkd,
/etc/network/interfaces) is never touched. The one host link the
plugin adds is a vlan sub-interface (below), which it creates, marks
and removes itself.
Constraints¶
- The parent NIC must support macvlan/ipvlan children. Physical Ethernet, VLAN sub-interfaces, and bonds work; bridges, macvlans, and ipvlans do not (you can't stack these on top of each other).
- A VLAN of the parent is one option away since v2.3.0:
-o parent=eth0 -o vlan=100attaches the children toeth0.100, which the plugin creates when it is missing and removes with the last network on it, never one it did not create.<parent>.<id>must fit the kernel's 15 bytes. The sub-interface is a parent of its own, so a macvlan network oneth0.100and an ipvlan network oneth0do not clash. See VLAN sub-interfaces (#902). - The parent NIC must be administratively
UPbefore you create the network. The plugin won't bring it up for you, since host config is off-limits. Avlansub-interface the plugin creates is brought up by the plugin; its parent must beUPalready. - Like any macvlan/ipvlan setup: a container on a child interface cannot reach the parent NIC's own host IP, and vice-versa. This is a kernel-level rule and never a plugin restriction. For host↔container traffic you'd need bridge mode or a second NIC.
- The parent no longer needs an address on the leased subnet for
conflict detection. It used to: the plugin checked each new lease
against the segment (v1.6.0+, #524) by resolving the address from the
parent, and a host answers an ordinary ARP request only if it can route
a reply back to the sender, so an address-less parent left the check
undetermined. Since 2.0 the DHCP client runs RFC 5227 from
inside the container instead, and a §2.1.1 Probe carries an all-zero
sender protocol address that Linux answers for any local target without
consulting a route. A bare parent is fine. See
conflict_checkand/Plugin.Health. - ipvlan-specific: custom MAC addresses are unsupported (children
share the parent's MAC). Passing
--mac-addressondocker runwith an ipvlan network will fail withinvalid MAC address. - ipvlan-specific: only L2 mode leases.
-o ipvlan_mode=l3andl3sare refused atdocker network create, because such a child sends no broadcast and its DHCPDISCOVER reaches no server or relay.-o macvlan_mode=picksbridge,vepa,privateorpassthrufor macvlan. See macvlan and ipvlan sub-modes. - ipvlan-specific: if your DHCP server keys reservations solely
on MAC and ignores DHCP option 61 (client identifier), ipvlan
won't work as a stability mechanism, because every ipvlan slave shares
the parent's MAC, so the server has no way to tell them apart.
Use
mode=macvlanif your server is MAC-only. (See "DHCP identity" below for what the plugin sends.) - ipvlan-specific: only one of macvlan or ipvlan can be active on
a given parent NIC at a time. The kernel rejects mixing them with
EBUSY. Use one mode per parent. - Docker's default IPAM is never the allocator: the LAN's DHCP server
is the address source of truth. Pass
--ipam-driver=null, or, from v2.1.0, this plugin's own IPAM driver (reference).ipvlantakes--ipam-driver=nullonly: Docker sets the MAC it generates for an IPAM driver on the container's interface at start, and an ipvlan interface cannot change its MAC (#949). - One DHCP-served network per container. If a container also joins a bridge or other Docker network, that's its problem to coordinate.
Verifying¶
After creating a container on the network:
# Container's view
docker exec <container> ip -4 addr show
docker exec <container> ip -4 route show
# Host's view of the lease
docker inspect <container> | jq '.[0].NetworkSettings.Networks'
# Upstream DHCP server's view (Fritz.Box, pfSense, etc.)
# Check the active leases page in your router's UI; the container's
# MAC and hostname should appear.
A container on a macvlan should be pingable from any other host on the LAN on the IP its DHCP server handed it.
If you also want to confirm the lease is not colliding with something
already on the segment, read acd_probes_sent before believing
address_conflicts_v4 is zero. With no probes the two readings are
identical, and "the detector never ran" is what the fault behind #524
looked like. A zero is also the honest answer on a network created with
-o conflict_check=off, which asks for no probes at all. Both counters
are on /Plugin.Health.
Troubleshooting¶
"parent interface is unsuitable for macvlan" means you passed a bridge,
macvlan, or ipvlan as parent. Use a real NIC, a VLAN sub-interface,
or a bond.
"ipvlan does not support a custom MAC address" means docker run --mac-address
is not compatible with mode=ipvlan because ipvlan children share the
parent's MAC. Drop the --mac-address flag, or switch the network to
mode=macvlan if you need distinct MACs.
"parent interface is down" is fixed by ip link set <parent> up.
The plugin won't toggle host link state.
Container gets no IP: check that the parent NIC is on the right L2
segment, that DHCP traffic isn't being filtered (some managed switches
have DHCP snooping or storm-control turned on), and that the upstream
DHCP server has a free lease in its pool. -o validate_dhcp=true catches
all three at network-create time instead of at the first docker run.
Everything not specific to these modes (general symptoms, the Compose merge trap, health-endpoint problems) is in the driver reference.
Where the rest of the documentation lives¶
This page covers choosing and setting up macvlan or ipvlan. The behaviour of the plugin itself is identical in every mode and is documented once, in the driver reference:
- All driver options, including
parent,validate_dhcp, and the rest - Requesting a specific address
- Restart stability: how MAC and IP survive
docker restart - DHCP identity: what the plugin sends as hostname, vendor class, and client ID
- DHCPv6
- Recovery after a plugin restart
/Plugin.Healthand the counters- Plugin settings:
LOG_LEVEL,AWAIT_TIMEOUT,STATE_DIR,METRICS_ADDR
For how it works under the hood, see How it works.