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
--subnet: Must strictly match the subnet of your LAN router (e.g.,192.168.1.0/24).--gateway: The default gateway IP address (typically your router at192.168.1.1).--ip-range: Limits the IP block that Docker is allowed to allocate. For example,192.168.1.200/28restricts Docker to 16 addresses (from.200to.215), preventing IP conflicts with your office DHCP server.-o parent: The name of the active physical network interface (such aseth0,ens18, orenp3s0).
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, andForged Transmitsto 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.10through192.168.1.150, reserving the192.168.1.200/28range exclusively for Docker Macvlan. - Persist Shim Interface Configuration Across Reboots: Transient
ip linkandip routecommands 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.

