Give a Docker Container Its Own IP with macvlan

Blog / Docker · November 15, 2018 · Updated June 10, 2026 · 10 min read
Give a Docker Container Its Own IP with macvlan

To give a Docker container its own routable IP on your LAN without any port binding, attach it to a macvlan network: create one with docker network create -d macvlan --subnet=... --gateway=... -o parent=eth0, then start the container with --network and --ip. The container gets its own MAC address and IP on the physical network, so other machines reach it directly on every port — no -p host:container mapping and no NAT.

This is the modern, built-in replacement for the old pipework shell-script hack. Since Docker added the macvlan and ipvlan drivers (Engine 1.12+, fully current in Engine 27.x as of 2026), you no longer need any external script to attach a second interface — the network drivers do it natively.

Key takeaways

  • Use the macvlan driver to hand a container its own MAC + IP directly on the LAN, with zero port mapping.
  • docker network create -d macvlan --subnet --gateway -o parent=<nic> defines the network; docker run --network <net> --ip <addr> pins the address.
  • By design the Docker host cannot reach its own macvlan containers — fix it with a small macvlan "shim" interface on the host.
  • The parent NIC usually needs promiscuous mode; many switches and cloud VMs (AWS, GCP) block extra MACs, so macvlan often fails there.
  • Choose ipvlan (L2) instead when the network restricts MAC addresses (cloud, Wi-Fi, switch port-security) — it shares the host's single MAC.
  • The legacy pipework script is obsolete; macvlan/ipvlan are the supported way to do this today.

Why give a container its own IP?

The default Docker bridge network puts every container behind the host's IP and exposes services only through published ports (-p 8080:80). That works for most apps, but it has real limits:

  • One port, one container. If a container publishes host port 8000, no other container can use that host port.
  • Port-range pain. Publishing many ports (e.g. a passive-FTP or SIP range) is slow to map and clutters your run commands.
  • iptables sprawl. Every published port adds NAT/DNAT rules; large deployments accumulate cumbersome iptables chains.
  • NAT hides the real client IP unless you add proxy-protocol or X-Forwarded-For handling.

When a container must look like a first-class machine on the network — a database appliance, a legacy app expecting a fixed IP, a service that needs every port, or something other LAN hosts must dial directly — giving it its own IP is cleaner than juggling port maps.

What is the macvlan driver?

macvlan is a Linux network driver that creates multiple virtual interfaces, each with its own MAC address, on top of a single physical NIC (the "parent", e.g. eth0). Docker's macvlan network driver gives each container one of these virtual interfaces, so the container appears as a distinct physical device on the LAN with its own MAC and IP.

Because traffic is switched at layer 2 by MAC address — no bridge learning, no NAT, no port translation — macvlan is fast and simple to reason about. The container talks to the gateway and the rest of the subnet exactly as a bare-metal host would.

The trade-offs: the parent NIC must accept multiple MAC addresses (promiscuous mode), and — by design — the host and its macvlan containers cannot talk to each other over that parent interface. Both are solvable (see below).

macvlan vs ipvlan vs bridge+ports vs host network

Approach Own LAN IP? Host ↔ container reach Performance Complexity Best for
Bridge + published ports (-p) No (shares host IP) Yes, via mapped ports Slight NAT overhead Low Most apps; one or a few ports
macvlan Yes (own MAC + IP) No by default (needs a shim) Near-native, no NAT Medium Appliances / apps needing a real LAN IP and all ports
ipvlan (L2) Yes (shares host MAC) No by default Near-native, no NAT Medium MAC-limited networks: cloud, Wi-Fi, switch port-security
host network No (uses host's IP + stack) N/A (same stack) Native Low A single high-throughput container, no network isolation

Rule of thumb: stay on bridge + -p unless you specifically need a routable LAN IP. Reach for macvlan when you do, and switch to ipvlan when the network won't tolerate extra MAC addresses.

How do you create a macvlan network?

First gather the facts about the LAN segment your NIC sits on: the subnet, the gateway, and the parent interface name. For example, on a host wired to the 192.168.1.0/24 network behind gateway 192.168.1.1 via eth0:

# Create a macvlan network bound to the eth0 parent interface
docker network create -d macvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  --ip-range=192.168.1.192/28 \
  -o parent=eth0 \
  my_macvlan

# --ip-range carves out a small pool (192.168.1.192-.207) for Docker to
# auto-assign, so it won't collide with your router's DHCP range.

The --ip-range flag is optional but recommended: it limits Docker's auto-assigned addresses to a slice of the subnet that your DHCP server doesn't hand out, preventing IP clashes. macvlan_mode=bridge is the default, so you rarely need to set it explicitly. For a tagged VLAN, point the parent at a sub-interface like -o parent=eth0.20 and Docker creates the 802.1q sub-interface for you.

Run a container with a fixed IP

Attach a container to the network and pin its address with --ip:

# Start nginx with its own IP on the LAN
docker run -d --name web \
  --network my_macvlan \
  --ip=192.168.1.200 \
  nginx:latest

# From ANY other machine on the LAN:
curl http://192.168.1.200/
# -> the nginx welcome page, on every port, with no -p mapping

The container now answers on 192.168.1.200 for all ports — HTTP, HTTPS, anything nginx listens on — with no port publishing. You can verify the interface inside the container with docker exec web ip addr. To assign a specific MAC as well, add --mac-address 02:42:c0:a8:01:c8 (use a locally administered MAC, i.e. one of x2/x6/xA/xE in the first octet).

The Docker Compose equivalent

In a Compose v2 file, declare the macvlan network under networks and pin the service IP with ipv4_address:

services:
  web:
    image: nginx:latest
    networks:
      lan:
        ipv4_address: 192.168.1.200

networks:
  lan:
    driver: macvlan
    driver_opts:
      parent: eth0
    ipam:
      config:
        - subnet: 192.168.1.0/24
          gateway: 192.168.1.1
          ip_range: 192.168.1.192/28

Bring it up with docker compose up -d (the modern v2 plugin — note docker compose, not the retired docker-compose binary). Compose creates the macvlan network on first run and tears it down on docker compose down.

The "host cannot reach the macvlan container" gotcha

This trips up almost everyone: by design, the Docker host cannot ping or connect to its own macvlan containers, and they can't reach the host either — even though every other device on the LAN can. The parent NIC simply won't loop traffic back to itself between the host stack and the macvlan children.

The fix is to add a tiny macvlan shim interface on the host that bridges that gap. Create a host-side macvlan interface on the same parent, give it an unused address, and route the container's IP through it:

# Create a host-side macvlan "shim" so the host can reach containers
sudo ip link add macvlan-shim link eth0 type macvlan mode bridge
sudo ip addr add 192.168.1.223/32 dev macvlan-shim
sudo ip link set macvlan-shim up

# Route the container's IP (or its whole pool) over the shim
sudo ip route add 192.168.1.200/32 dev macvlan-shim
# ...or the whole Docker-assigned range:
sudo ip route add 192.168.1.192/28 dev macvlan-shim

# Now the host can reach the container:
ping 192.168.1.200

These commands are not persistent across reboots — wire them into a systemd unit, a networkd/Netplan config, or an /etc/network/interfaces post-up hook to make the shim survive restarts.

Promiscuous mode and why cloud VMs usually block macvlan

macvlan puts many MAC addresses on one physical port, so the parent NIC must run in promiscuous mode to accept frames for all of them. On bare metal Docker handles this; on a virtual machine you must also enable promiscuous mode on the hypervisor's virtual switch (e.g. the vSwitch security policy in VMware/Proxmox), or the hypervisor silently drops the container's traffic.

This is exactly why macvlan usually does not work on public-cloud VMs. AWS EC2, Google Compute Engine, and Azure enforce MAC/IP anti-spoofing at the virtual NIC: a frame whose source MAC or IP isn't registered to that instance is dropped. The cloud-native equivalents instead of macvlan:

  • AWS: assign secondary private IPs to the instance's ENI, or attach additional ENIs, and bind containers to those.
  • GCP: add alias IP ranges to the VM's NIC.
  • Azure: add a secondary IP configuration to the NIC.

On those platforms, reach for a CNI/overlay or the provider's secondary-IP mechanism rather than macvlan. ipvlan (L2) sometimes works where macvlan doesn't, because it reuses the host's single MAC (see next).

When should you use ipvlan instead?

ipvlan is macvlan's sibling: it also gives each container its own IP on the parent network, but every container shares the parent's single MAC address rather than getting its own. That one difference matters when the surrounding network counts MACs:

  • Switch port-security that limits MACs per port.
  • Wi-Fi, which behaves badly with multiple MACs behind one client.
  • Cloud / virtualized NICs that reject unknown source MACs.

Create an ipvlan L2 network almost identically — just swap the driver and add the mode:

docker network create -d ipvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  -o parent=eth0 \
  -o ipvlan_mode=l2 \
  my_ipvlan

docker run -d --name web2 --network my_ipvlan --ip=192.168.1.201 nginx:latest

Because ipvlan doesn't add MACs, it needs no promiscuous mode, which is friendlier to VMs and locked-down switches. The catch: with a single shared MAC, DHCP-per-container and some L2 features don't behave as you'd expect, so static IPs (as above) are the norm. ipvlan_mode=l3 exists for routed setups but loses broadcast/ARP within the subnet.

These networking patterns matter once you scale beyond one host. If you're orchestrating many containers, see our guide on clustering Docker containers with Docker Swarm, and if you're new to the platform start with what Docker is and how it works with Python or deploying a Django project into a Docker container.

Frequently Asked Questions

How do I give a Docker container its own IP address without port binding?

Create a macvlan network bound to your physical NIC, then run the container on it with a fixed address: docker network create -d macvlan --subnet=192.168.1.0/24 --gateway=192.168.1.1 -o parent=eth0 my_macvlan followed by docker run -d --network my_macvlan --ip=192.168.1.200 nginx. The container gets its own MAC and IP directly on the LAN and is reachable on every port, with no -p host:container mapping and no NAT.

What is the difference between macvlan and ipvlan?

Both give a container its own IP on the parent network. With macvlan each container gets its own MAC address; with ipvlan all containers share the host's single MAC. Use macvlan on bare metal where extra MACs are fine. Use ipvlan when the network limits MAC addresses — cloud VMs, Wi-Fi, or switches with port-security — because it needs no promiscuous mode and won't be dropped for using an unknown MAC.

Why can't my Docker host reach its macvlan container?

This is expected behaviour, not a bug. The Linux macvlan driver does not loop traffic between the host's own stack and the macvlan child interfaces on the same parent NIC, so the host and its containers can't talk — even though every other LAN device can. The fix is to add a host-side macvlan "shim" interface on the same parent, give it a spare IP, and route the container's address through it.

Does macvlan work on AWS, GCP, or Azure VMs?

Usually not. Public-cloud virtual NICs enforce MAC/IP anti-spoofing and drop frames whose source MAC or IP isn't registered to the instance, which breaks macvlan. Instead use the provider's secondary-IP feature: secondary private IPs or extra ENIs on AWS, alias IP ranges on GCP, or additional IP configurations on Azure. ipvlan sometimes works where macvlan fails because it reuses the host's MAC.

Is the pipework script still needed for container IPs?

No. pipework was a shell script used in early Docker to attach a second interface to a running container; it is obsolete. Since Docker Engine 1.12 the built-in macvlan and ipvlan network drivers do this natively, and they remain the supported approach in Engine 27.x (2026). Use docker network create -d macvlan ... instead of any external script.

How do I set a fixed IP and MAC address for a container?

Pin the IP with --ip and, optionally, the MAC with --mac-address on a macvlan or ipvlan network: docker run -d --network my_macvlan --ip=192.168.1.200 --mac-address 02:42:c0:a8:01:c8 nginx. Use a locally administered MAC (first octet ending in 2, 6, A, or E) to avoid clashes. Keep the chosen IP outside your DHCP pool, or constrain Docker's pool with --ip-range when creating the network.

Get help running containers in production

Routable container IPs, macvlan/ipvlan networking, and the host-reach and cloud-MAC gotchas above are the kind of details that quietly break deployments. MicroPyramid has shipped 50+ projects over 12+ years and runs Docker workloads on bare metal and in the cloud for clients worldwide. If you want hands-on help, our server maintenance services cover container networking, hardening, and uptime, and our cloud migration services help you move these workloads to AWS, GCP, or Azure the right way. Talk to us about your setup.

Share this article