MACVLAN and IPVLAN: Giving Containers Real IPs
When a container needs to be a first-class citizen on your LAN instead of hiding behind the host

Contents
By default a Docker container lives behind the host like a flat in a building with one shared front door. It gets a private address on an internal bridge network, and when it talks to the outside world the host translates its traffic — the same NAT trick your router does, one layer down. For most services that’s exactly right: you publish a port, the host forwards it, done. But some containers are miserable behind that front door, and for them you want the container to have its own address directly on your LAN, its own knock on the network, visible to every other device as a peer. That’s what MACVLAN and IPVLAN deliver, and knowing when to reach for them saves a lot of fighting with port mappings that were never going to work.
Why a container would want its own IP
Three kinds of service push back hard against being NATed behind the host, and they’re worth recognising because they’re the ones that send you searching for this in the first place.
The first is anything that needs a privileged or singular port the host already uses. A DNS sinkhole wants port 53, and so does the host’s own resolver — putting the container on its own IP ends the squabble instantly, because now there are two addresses and 53 is free on both. If you’ve been wrestling a Pi-hole or AdGuard Home install over port 53 conflicts, this is the clean way out.
The second is anything that relies on broadcast or multicast discovery — mDNS, SSDP, the chatter that lets home-automation and media devices find each other. That discovery traffic doesn’t survive being NATed through a bridge; the container needs to be on the L2 segment where the broadcasts live. Home Assistant finding your smart plugs, a DLNA server announcing itself, an AirPrint bridge — these want a real LAN presence.
The third is simply wanting a device to be reachable by a stable, memorable address that isn’t “the host’s IP, port 8443”. When a container has its own LAN IP, you can give it a DNS name, firewall it as its own entity, and treat it like any other device on the network. That plays nicely with the split-horizon DNS setup, where each service earns its own name.
If none of those bite, don’t reach for this. The default bridge with published ports is simpler, better isolated, and fine. MACVLAN and IPVLAN buy LAN presence at the cost of some sharp edges, so spend that cost only when you’re getting the presence in return.
How the two differ
Both attach containers directly to a physical (or VLAN) interface so they get addresses in your LAN’s subnet. The difference is what they do about MAC addresses, and that difference decides which one works on your network.
MACVLAN gives every container its own unique MAC address as well as its own IP. To the switch, each container looks like a genuinely separate physical device plugged into the port. This is the more intuitive model and usually the default choice on wired networks.
IPVLAN gives each container its own IP but they all share the host’s MAC address. The switch sees one MAC with several IPs behind it. This matters in two real situations. Wi-Fi access points typically refuse to pass more than one MAC per associated client, so MACVLAN over Wi-Fi silently fails and IPVLAN is the only option. And some managed switches enforce port security that limits MACs per port, which MACVLAN trips and IPVLAN sidesteps. IPVLAN also comes in an L3 mode that routes rather than bridges, useful for larger setups but a step up in complexity.
The quick rule I use: wired with no port-security limits, reach for MACVLAN; on Wi-Fi or a locked-down switch port, reach for IPVLAN.
Setting up MACVLAN
You create a Docker network bound to your physical interface, telling it the subnet and gateway of your LAN, and reserving a slice of address space so container IPs don’t collide with your DHCP pool:
| |
Then attach a container and hand it a fixed address from that range:
| |
Notice there’s no ports: mapping. The container has its own IP, so port 53 (and everything else) is reachable at 192.168.1.242 with no forwarding and no conflict with the host. That’s the whole payoff.
Static addresses or DHCP?
The example above pins a static IP, which is what I do in practice because a service you reach by address or DNS name wants a stable one. Docker can instead let containers pull an address from your LAN’s own DHCP server via its IPAM DHCP plugin, so the container appears in your router’s lease table like any other device. That’s tempting for the neatness of one place assigning all addresses, and it works, but it adds a moving part that fails in confusing ways when the plugin or the lease renewal hiccups. The pragmatic middle ground is to keep the container static, put its address outside the DHCP pool as shown, and add a matching DNS entry so you never type the number. Reserve DHCP-assigned container addresses for the rare case where something genuinely expects to be handed a lease.
One more planning note: because these containers consume real LAN addresses, they count against your subnet’s space and your address plan. A handful is nothing on a /24, but if you’re spinning up dozens of services this way, size the carved-out ip-range deliberately and keep it documented alongside your static reservations so a future you doesn’t double-assign an address and spend an hour hunting the collision.
The gotcha everyone hits: the host can’t reach its own containers
Here’s the trap that has cost more homelabbers an evening than any other: with MACVLAN, the host cannot talk to its MACVLAN containers, and they can’t talk to the host. Every other device on the LAN reaches them fine — it’s specifically the host they live on that’s cut off. This is by design in the Linux kernel’s MACVLAN driver, and it bites in nasty ways. If your monitoring runs on the host and tries to scrape a MACVLAN container, it can’t. If the container is your DNS resolver and you set the host to use it, the host loses DNS.
The fix is to create a MACVLAN shim on the host: a tiny secondary MACVLAN interface on the host itself, plus a route telling the host to reach the container IP range via that shim. It re-establishes the one path the driver deliberately omits.
| |
Make that persistent (a systemd unit or your distro’s networking config) or it evaporates on reboot and the host-to-container gap reopens. IPVLAN in L2 mode does not have this host-isolation quirk, which is one more reason it’s sometimes the gentler choice despite the shared-MAC oddity.
Troubleshooting
Container has an IP but nothing can reach it. Confirm the ip-range you carved out is inside the LAN subnet and outside the router’s DHCP pool — an overlap means either Docker and the router hand out the same address (chaos) or the router never routes to it. Also check the container’s IP actually answers ARP from another LAN device with arping 192.168.1.242.
The host specifically can’t reach it, everything else can. That’s the isolation-by-design behaviour above. Add the MACVLAN shim and route. This is nearly always the cause when “it works from my laptop but the host’s healthcheck fails”.
MACVLAN works on Ethernet but not over Wi-Fi. Expected — Wi-Fi won’t pass multiple MACs per client. Switch that network to IPVLAN, where the containers share the host’s single MAC.
Some LAN devices see the container, others don’t, intermittently. The parent interface may need promiscuous mode to accept frames for the extra MACs, and some virtualised or bonded setups block it. Check ip link shows PROMISC on the parent, and be aware that a few cloud/VM environments forbid MACVLAN outright for this reason.
Switch port keeps disabling. Port security limiting MACs per port. Either raise the limit on that port or move to IPVLAN so only the host’s MAC ever appears. This overlaps with proper network segmentation — if you’re already running VLANs, you can bind the MACVLAN to a VLAN sub-interface (eth0.30) and drop the container straight onto the right segment.
Container IP survives but loses connectivity after a host reboot. Usually the shim/route wasn’t made persistent, or the Docker network came up before the parent interface. Order the units so the physical interface is up first.
The verdict
MACVLAN and IPVLAN are the right tool for a narrow, real set of problems: containers that need a privileged port the host already holds, services that depend on broadcast and multicast discovery, and anything you want to treat as a proper networked device with its own address and name. For those, giving the container a real LAN IP turns a frustrating mess of port juggling into something that plainly works, and I run several services this way with no regrets.
For everything else, the honest advice is to stay on the default bridge. MACVLAN’s host-isolation surprise, the DHCP-range discipline, the promiscuous-mode requirements, and the Wi-Fi limitation are all real friction, and you should only accept them when the LAN presence is genuinely worth it. Pick MACVLAN on wired networks, fall back to IPVLAN on Wi-Fi or locked-down switch ports, remember the shim so the host can reach its own containers, and this becomes a dependable technique rather than a mysterious one.




