Configuring Docker Macvlan Network: Assigning Static IPs from the Physical LAN to Containers

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Quick Start: Create a Docker Macvlan and Assign a Static IP in 5 Minutes

Asterisk or FreePBX PBX systems running on a Docker bridge frequently run into issues: extensions only get one-way audio, calls drop after 30 seconds, or SIP SDP packets fail to be detected due to NAT traversal issues. Broadcast and multicast packets sent from physical IP phones connected to an external switch also cannot route into Docker’s bridge network.

The fastest solution is to expose the container directly to the physical LAN layer using the macvlan driver. In this mode, the container operates independently and has its own MAC address on equal footing with every other device on the network.

First, identify the network interface connected to the router and check the IP settings on your server:

ip -br addr show
# Example output: eth0 UP 192.168.1.50/24
# Current gateway: 192.168.1.1
# Local subnet: 192.168.1.0/24

Create a Docker network with the macvlan driver bound to the eth0 interface:

docker network create -d macvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  --ip-range=192.168.1.200/28 \
  -o parent=eth0 \
  lan_macvlan

Launch a container with a static IP of 192.168.1.202 from the allocated range:

docker run -d \
  --name voip-service \
  --net=lan_macvlan \
  --ip=192.168.1.202 \
  --restart=always \
  nginx:alpine

From another computer on the same LAN subnet, test connectivity:

ping -c 4 192.168.1.202
curl -I http://192.168.1.202

Ping responds immediately with an RTT of under 1ms. The container now possesses its own MAC address and intercepts traffic directly at Layer 2 without passing through an intermediary NAT layer.

How Does Docker Macvlan Actually Work?

In default bridge mode, Docker provisions a virtual bridge called docker0 and uses iptables MASQUERADE for network address translation (NAT). External switches and routers can only see the host machine’s MAC and IP address. While convenient, this mechanism cripples protocols that rely on broadcast or multicast—such as DHCP relay, VoIP (SIP/RTP), Home Assistant mDNS discovery, or ONVIF camera video streams.

Sub-interface Virtualization Mechanism

Macvlan resolves this at the data link layer (Layer 2). The driver creates virtual sub-interfaces attached directly to the host’s parent interface. Each container is assigned a randomized IEEE 802-compliant MAC address:

  • Independent MAC Address: The network switch sees the container as a distinct physical server plugged directly into a network port.
  • No NAT: The container directly ingests traffic targeted to its IP address. This eliminates routing latency and removes dependency on the Linux kernel’s conntrack table.
  • No Port Mapping Required: You do not need to specify -p 80:80. This completely removes the risk of port conflicts between multiple services running concurrently on the same host.

Key Network Parameters to Remember

  1. --subnet: Must strictly match the subnet of your LAN router (e.g., 192.168.1.0/24).
  2. --gateway: The default gateway IP address (typically your router at 192.168.1.1).
  3. --ip-range: Limits the IP block that Docker is allowed to allocate. For example, 192.168.1.200/28 restricts Docker to 16 addresses (from .200 to .215), preventing IP conflicts with your office DHCP server.
  4. -o parent: The name of the active physical network interface (such as eth0, ens18, or enp3s0).

Deploying with Docker Compose and Fixing Host-to-Container Routing

Complete docker-compose.yml File

With modern Compose specifications, the deprecated top-level version: attribute is no longer needed. Here is a configuration to deploy Pi-hole using Macvlan:

services:
  dns-server:
    image: pihole/pihole:latest
    container_name: local-pihole
    environment:
      TZ: 'Asia/Ho_Chi_Minh'
      WEBPASSWORD: 'AdminSecurePassword123'
    networks:
      lan_net:
        ipv4_address: 192.168.1.205
    restart: unless-stopped

networks:
  lan_net:
    name: lan_macvlan
    driver: macvlan
    driver_opts:
      parent: eth0
    ipam:
      config:
        - subnet: 192.168.1.0/24
          ip_range: 192.168.1.200/28
          gateway: 192.168.1.1

Troubleshooting: Host Cannot Ping the Container

You will quickly encounter this issue: other devices on the LAN can communicate with the container without issue, but running ping 192.168.1.205 directly from the host yields 100% packet loss. This is an intentional security design choice in the Linux kernel. The kernel forbids direct communication between the parent physical interface and child macvlan sub-interfaces operating in bridge mode.

The standard solution is to configure a dedicated macvlan shim interface directly on the host to act as a communication bridge:

# 1. Create a virtual macvlan bridge interface on the host
sudo ip link add macvlan-shim link eth0 type macvlan mode bridge

# 2. Assign an unused IP address to the shim interface
sudo ip addr add 192.168.1.210/32 dev macvlan-shim

# 3. Bring up the interface
sudo ip link set macvlan-shim up

# 4. Route the entire container subnet through this shim
sudo ip route add 192.168.1.200/28 dev macvlan-shim

Once you execute these four commands, the host can immediately reach the container directly without requiring any service restarts.

3 Critical Considerations for Production Deployments

  • Enable Promiscuous Mode on Hypervisors: If the host is a virtual machine running on VMware ESXi or Proxmox VE, you must set Promiscuous Mode, MAC Address Changes, and Forged Transmits to Accept on the vSwitch or Linux Bridge. If disabled, the hypervisor will drop any packets carrying the container’s unique MAC address.
  • Plan Your IP Allocation Carefully: Prevent Docker from overlapping with the router’s automatic DHCP pool. It is best practice to restrict your router’s DHCP pool to 192.168.1.10 through 192.168.1.150, reserving the 192.168.1.200/28 range exclusively for Docker Macvlan.
  • Persist Shim Interface Configuration Across Reboots: Transient ip link and ip route commands do not survive system reboots. Bundle these commands into a custom systemd service or integrate them directly into your Netplan or NetworkManager configuration so they apply automatically at boot time.
Share: