Docker in a dedicated VLAN including IPv6

To test AdGuard Home I wanted to quickly spin up a Docker container. That worked right away, but it didn’t correctly log the clients’ IPv6 addresses. The solution was to place the container on the network as its own host.

Step 1: Create a VLAN on UniFi UDM‑Pro

Because containers with macvlan don’t properly use DHCP, I created a VLAN with its own IP range. In the UDM‑Pro web UI go to “Settings” → “Network” → “New Virtual Network”. Assign an IP range and a VLAN ID (I used ID 2). Set DHCP to None. Under IPv6, also assign a subnet, for example fd01::1/64.

Step 2: Create the macvlan network in Docker

The Docker network was created with the following command, according to the docs1.

docker network create -d macvlan --ipv6 --subnet=192.168.3.0/24 --gateway=192.168.3.1 --subnet=fd01::/64 --gateway=fd01::1  -o parent=eno1.2 -o macvlan_mode=bridge macvlan1
  • ipv6: enable IPv6 for the network
  • subnet: the VLAN’s address range, once for IPv4 and once for IPv6
  • gateway: the router/gateway
  • parent: parent interface. The .2 denotes the VLAN ID. Docker automatically creates the sub‑interface (e.g., eno1.2); you don’t need to create it manually in advance.

Note: For IPv6 in Docker you must enable IPv6 in the daemon (e.g., set {"ipv6": true} in /etc/docker/daemon.json) and restart the service. Otherwise containers may not obtain an IPv6 address.

Compatibility: The macvlan driver is Linux‑only and not available on Docker Desktop (Mac/Windows).

Step 3: Start a test container

Start a container and verify that everything works.

[root@server docker_macvlan]: docker run -it --rm --network=macvlan1 archlinux
[root@218c8aacb6b8 /]: ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host proto kernel_lo 
       valid_lft forever preferred_lft forever
47: eth0@if40: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:c0:a8:03:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.3.2/24 brd 192.168.3.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fd01::42:c0ff:fea8:302/64 scope global tentative dynamic mngtmpaddr proto kernel_ra 
       valid_lft 86399sec preferred_lft 86399sec
    inet6 fd01::3/64 scope global nodad 
       valid_lft forever preferred_lft forever
    inet6 fe80::42:c0ff:fea8:302/64 scope link proto kernel_ll 
       valid_lft forever preferred_lft forever

[root@218c8aacb6b8 /]: ping 192.168.3.1
PING 192.168.3.1 (192.168.3.1) 56(84) bytes of data.
64 bytes from 192.168.3.1: icmp_seq=1 ttl=64 time=0.305 ms
^C
--- 192.168.3.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.305/0.305/0.305/0.000 ms

Step 4: AdGuardHome

Create the AdGuardHome container as follows2:

Here we assign a fixed IP address so the container can later be used as the DNS server for clients. Without --ip, Docker would pick an address from the subnet by itself and it could change between runs.

docker run --name adguardhome\
  --restart unless-stopped\
  -v adguard-workdir:/opt/adguardhome/work\
  -v adguard-confdir:/opt/adguardhome/conf\
  --network=macvlan1\
  --ip 192.168.3.20\
  --ip6 fd01::20\
  -d adguard/adguardhome