Skip to content

Instantly share code, notes, and snippets.

@xirixiz
Last active June 10, 2024 08:44
Show Gist options
  • Save xirixiz/ecad37bac9a07c2a1204ab4f9a17db3c to your computer and use it in GitHub Desktop.
Save xirixiz/ecad37bac9a07c2a1204ab4f9a17db3c to your computer and use it in GitHub Desktop.
Add a PiHole instance on a macvlan enabled Docker network (Synology eth0 example)
#!/bin/bash
# NAS IP: 192.168.1.10 in this example
# DHCP scope reservation for macvlan: 192.168.1.210/28 (Details below)
## Network: 192.168.1.210/28
## HostMin: 192.168.1.211
## HostMax: 192.168.1.224
## Hosts/Net: 14
# Create a Synology macvlan0 bridge network attached to the physical eth0, and add the ip range scope (sudo)
ip link add macvlan0 link eth0 type macvlan mode bridge
# Specify part of the eth0 scope you'd like to reserve for macvlan0
ip addr add 192.168.1.210/28 dev macvlan0
# Bring up the macvlan0 adapter
ip link set macvlan0 up
# Check virtual adapter status with ifconfig
ifconfig
# Output should be something like this:
macvlan0 Link encap:Ethernet HWaddr 92:8D:43:0E:E2:D8
inet addr:192.168.1.210 Bcast:0.0.0.0 Mask:255.255.255.240
inet6 addr: fe80::908d:43ff:fe0e:e2d8/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:79 errors:0 dropped:0 overruns:0 frame:0
TX packets:48 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1
RX bytes:34863 (34.0 KiB) TX bytes:16322 (15.9 KiB)
# Create a macvlan Docker network using eth0
docker network create --driver=macvlan --gateway=192.168.1.1 --subnet=192.168.1.0/24 --ip-range=192.168.1.210/28 -o parent=eth0 macvlan
# It's also possible to create a scheduled task at startup as the root user, it's wise to append the following in front of the above commands
while ! ip link show eth0 | grep -q 'state UP'; do
sleep 1
done
# Perform a basic test with NGINX
docker run --net=macvlan -dit --name nginx-test-01 --ip=192.168.1.211 nginx:alpine nginx-debug -g 'daemon off;'
# Browse to http://192.168.1.211 in your local network, you should see the nginx welcome page! ...Don't forget to remove the container afterwards...
docker rm nginx-test-01 --force
# Now start PiHole on a macvlan enabled IP address f.e.
# Also I've added a fake mac address so the container always uses the samen mac, handy to make a reservation in your DHCP scope or do whatever you like to do with it.
DOCKERHOME=<some path>
NAME=pihole-macvlan
IMAGE=pihole/pihole
docker run --detach \
--name ${NAME} \
--restart always \
--volume /etc/localtime:/etc/localtime:ro \
--volume ${DOCKERHOME}/data/${NAME}/config:/etc/pihole \
--volume ${DOCKERHOME}/data/${NAME}/dnsmasq.d:/etc/dnsmasq.d \
--cap-add NET_ADMIN \
--dns=127.0.0.1 \
--dns=1.1.1.1 \
--env "DNS1=1.1.1.1" \
--env "DNS2=1.0.0.1" \
--env "ServerIP=192.168.1.212" \
--env "DNSMASQ_LISTENING=all" \
--env "WEBPASSWORD=<secret>" \
--env "TZ=Europe/Amsterdam" \
--network macvlan \
--ip "192.168.1.212" \
--mac-address "02:42:c0:a8:01:d7" \
${IMAGE}
# Cleanup macvlan
ip link set macvlan0 down
ip link delete macvlan0
docker network rm macvlan
# Happy days!
@Jip-Hop
Copy link

Jip-Hop commented Oct 29, 2019

I think I solved it :) Didn't have anything to do with Macvlan after all. I misconfigured Policy Based Routing on my Asus router (Asuswrt-Merlin). Apparently it routed local destinations (other LAN subnets) through my VPN provider. This caused issues when trying to access devices on my main LAN subnet from other subnets, like a WiFi network on another subnet or VPN TUN clients on another subnet. Still need to investigate the Policy Based Routing settings a bit more, but at least it wasn't a Macvlan limitation and there's nothing wrong with your instructions 😄

@keitetran
Copy link

keitetran commented Nov 22, 2019

Thank bro,
We can use DNS server app on DMS and Pihole. :D
But on router -> Active DHCP Leases not show pihole IP
Can i add 02:42:c0:a8:01:d7 ,192.168.1.210 to static DHCP ?

@wozniakpawel
Copy link

wozniakpawel commented Apr 17, 2020

Hi xirixiz. I'm stuck trying to get pihole working concurrently with a node.js server on Raspberry Pi 4. I want to avoid port conflicts and thought macvlan would be the best solution. Could you have a look and see if you can help?

I've got a node.js server running on wlan0, port 80, ip 192.168.0.51. Then, I do the following:

docker network create --driver=macvlan --gateway=192.168.0.1 --subnet=192.168.0.51/24 -o parent=wlan0 piholenet
sudo ip link add piholenet0 link wlan0 type macvlan mode bridge
sudo ip addr add 192.168.0.208/28 dev piholenet0
sudo ifconfig piholenet0 up
docker run --net=piholenet -d --ip=192.168.0.208 -p 80:80 nginx

So far so good, I have both interfaces up and running with different IP and MAC addresses:

piholenet0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.0.208 netmask 255.255.255.240 broadcast 0.0.0.0
inet6 fe80::3257:c36e:cf43:8c9c prefixlen 64 scopeid 0x20
ether 3e:9b:6e:26:e8:83 txqueuelen 1000 (Ethernet)
RX packets 226 bytes 15635 (15.2 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 134 bytes 22827 (22.2 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.0.51 netmask 255.255.255.0 broadcast 192.168.0.255
inet6 fe80::291a:bf94:ff88:f094 prefixlen 64 scopeid 0x20
ether dc:a6:32:3c:62:c8 txqueuelen 1000 (Ethernet)
RX packets 42672 bytes 4612986 (4.3 MiB)
RX errors 0 dropped 76 overruns 0 frame 0
TX packets 13875 bytes 4097060 (3.9 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

However, when I go to 192.168.0.51 or 192.168.0.208 they both show the website I am hosting on node.js. I also can't see piholenet0 listed on my router. What am I doing wrong?

@xirixiz
Copy link
Author

xirixiz commented Apr 17, 2020

It's been a while since I used macvlan, but can you try it like this:

Cleanup first (remove containers using piholenet first!):

sudo ip link set piholenet0 down
sudo ip link delete piholenet0
docker network rm piholenet

Create a 'fresh' macvlan and run a nginx container:

sudo ip link add piholenet0 link wlan0 type macvlan mode bridge
sudo ip addr add 192.168.0.210/28 dev piholenet0
sudo ip link set piholenet0 up
docker network create --driver=macvlan --gateway=192.168.0.1 --subnet=192.168.0.0/24 -o parent=wlan0 piholenet
docker run --net=piholenet -dit --name nginx-test-01 --ip=192.168.0.211 nginx:alpine nginx-debug -g 'daemon off;'

@wozniakpawel
Copy link

Thanks so much for the response!

I've tried the above but no luck, ended up with piholenet0 with ip 192.168.1.210. Neither 192.168.1.210 nor 192.168.0.210 show any nginx welcome page - connection times out.
I retried the above changing this line:

sudo ip addr add 192.168.1.210/28 dev piholenet0

to:

sudo ip addr add 192.168.0.210/28 dev piholenet0

Exactly the same thing as before happens - my node.js website is accessible from both wlan0 192.168.0.51 and piholenet0 192.168.0.210.

If you have any more ideas - great, I'll try them out, if not - don't worry about it. It's already taken me over a day of trying and it's not really worth any extra effort.

I'm a bit surprised about how little there is online about macvlan and port conflict workarounds. Your guide is pretty much the best one around.

@xirixiz
Copy link
Author

xirixiz commented Apr 18, 2020

I had the same struggle back then :). It was indeed hard to find proper documentation, so thanks for the compliment! I can try to simulate it on my nas, it won't take much time, so I'll do that Today. I`ll get back to you later.

@xirixiz
Copy link
Author

xirixiz commented Apr 18, 2020

I've updated the gist based. I followed all steps as described in the gist, and that's working fine now. I noticed port mapping doesn't work with macvlan for example, so I removed it. Anyway, hope you manage to get it working now. I've updated my previous comment as well, please try again.

Cleanup first and follow the order I specified (docker network create after the virtual adapter creation, as I've had some issues doing it in a different order).

@wozniakpawel
Copy link

Still no luck. I've also tried to change a few things here and there to narrow down the problem. If I stop the node.js service and simply run
docker run --rm -d --network host --name my_nginx nginx
I can see the nginx start page. If I run the same with
docker run --rm -d --network piholenet --name my_nginx --ip=192.168.0.211 nginx
I can't ping 192.168.0.211 at all. If I run
docker run --rm -d --network piholenet --name my_nginx --ip=192.168.0.210 nginx
and enable node.js service on host, I get back to seeing the node.js website at 192.168.0.210, otherwise I get connection timed out.
Thanks a lot for your help, I think I'll give up at this point. It's really not worth any more of our time.

@wozniakpawel
Copy link

wozniakpawel commented Apr 18, 2020

Good news! There is nothing wrong with the setup we've been trying to do. It's apparently an ARP issue in the Linux Kernel version used in Raspbian. Solved with a simple
sudo rpi-update
as per Docker MACVLAN only works Outbound
After this, following your guide has worked perfectly fine:

sudo ip link add piholenet0 link wlan0 type macvlan mode bridge
sudo ip addr add 192.168.0.210/28 dev piholenet0
sudo ip link set piholenet0 up
docker network create --driver=macvlan --gateway=192.168.0.1 --subnet=192.168.0.0/24 -o parent=wlan0 piholenet
docker run --net=piholenet -dit --name nginx-test-01 nginx:alpine nginx-debug -g 'daemon off;'

nginx is available at 192.168.0.210 while my node.js server is available at 192.168.0.51
Now I will simply put together a docker-compose file and run pihole in a container.

EDIT:
Never mind. It worked for a couple of minutes and then went back to showing my node.js website on both.

After removing docker networks (docker network rm piholenet) and killing all docker processes, I have narrowed the problem down to:

sudo ip link add piholenet0 link wlan0 type macvlan mode bridge
sudo ip addr add 192.168.0.210/28 dev piholenet0
sudo ip link set piholenet0 up

Which makes my node.js website appear on both wlan0 IP (192.168.0.51) and piholenet0 IP (192.168.0.210). I'll have to do some reading on network bridging.

@xirixiz
Copy link
Author

xirixiz commented Apr 19, 2020

Did not think of that either, but great job finding it out! 👍 🥳

@solidus1983
Copy link

Was struggling to do with with protainer Pihole container, just followed your commands and boom i now have a working pihole container that is on the local network as if it was a physical computer connected to my main network!

So much for using Portainers GUI to do this it was forever causing ARP'ing issues. Always said CLI was better this proves it.

Thank you

@canadadanac
Copy link

Hi Bram. I signed up to GitHub specifically so I could thank you for posting this! Extremely helpful! I did a lot of research on this topic (Pihole in Docker on Synology with macvlan). This is by far the best resource I've found! Thanks!

@xirixiz
Copy link
Author

xirixiz commented Sep 7, 2020

Hi @canadadanac, how nice to specially register to tell me this! Thanks, much appreciated! Glad it helped you out a lot! 👍

@markdumay
Copy link

markdumay commented Sep 19, 2020

Hi Bram, great gist, very helpful! I created a script inspired by your guide here, it might help others too. I added a static route from the host to the Pi-hole container to get it to work for my particular Synology setup (which uses a custom Docker installation). It should also persist reboots and DSM updates.

@xirixiz
Copy link
Author

xirixiz commented Sep 22, 2020

Hi @markdumay, good job! I hope it'll help people.

Currently I`m not using Pi-Hole anymore as adguard has a much better way of solving this. So per client I can specify the upstream dns servers, block specific services, and enable/enforce other stuff. This works out great to separate rules for our kids and us as parents!

image

@markdumay
Copy link

Thanks! AdGuard Home looks promising indeed, especially when looking at this comparison. It seems Home Assistant provides integration support too. Out of curiosity, are you running a containerized version of AdGuard yourself?

@xirixiz
Copy link
Author

xirixiz commented Sep 22, 2020

Yes, I run everyting in Docker (when possible :))

@rsessa
Copy link

rsessa commented Oct 9, 2020

Hi! Thank you very much for your work. Let's see if you can help me, this error gives me when I try to create the network in docker:
Error response from daemon: Pool overlaps with other one on this address space.
I have followed these steps:

#iPhone NAS 192.168.10.251

ip link add macvlan0 link ovs_eth0 type macvlan mode bridge
ip addr add 192.168.10.100/28 dev macvlan0
ip link set macvlan0 up
Ifconfig
2DDB8123-71E4-41C6-AAA9-A53F58012AD5

ip router show
1B1EB603-C435-4FE1-9F24-D85BCF356005

docker network create --driver=macvlan --gateway=192.168.10.1 --subnet=192.168.10.0/24 -o parent=ovs_eth0 macvlan
F3ABF986-518F-4D3B-8E2E-DA98D1265CDD

@xirixiz
Copy link
Author

xirixiz commented Oct 9, 2020

Might be easy, try service docker restart first. Most of the times this solves it for these kind of errors

Or:

docker-compose down
docker network prune

Also check the existing Docker network config:
docker network ls

Maybe there's a duplicate network specified.

@rsessa
Copy link

rsessa commented Oct 9, 2020

Solved. Thank you!

@lazynomad
Copy link

Thanks for this.
I see you cleanup macvlan at the end of the script. I suppose it's just to show how to delete it, if needed. However, we shouldn't do that if piHole must be running right ?

@xirixiz
Copy link
Author

xirixiz commented Jan 21, 2021

Correct! It's indeed only some info if you need to remove macvlan. However, as you're mentioning Pi-Hole, I swtiched to Adguard. Adguard has the ability to specify rules and upstream DNS servers per subnet. So I now have a single instance of Adguard running, serving multiple subnets, all with their own configuration so f.e. the subnet of the kids has many limitiations. Pi-Hole is not going to add such a feature unfortunatly and I don't wan't to manage multiple Pi-Hole instances.

@titanbird
Copy link

Hello everyone!

Really like it! :-)

I feel that I am almost finished with a running pihole in docker on a synology nas. But the DNS is not working.
If I use it, the browser outputs an DNS ERROR:

DNS_PROBE_FINISHED_BAD_CONFIG

My router has the ip 192.168.0.1.
The nas has 192.168.0.100
DHCP (running on the nas) ip range: 192.168.0.100 - 192.168.0.199

All the commands that I uses are:

ip link add macvlanpihole link eth0 type macvlan mode bridge
ip addr add 192.168.0.200/28 dev macvlanpihole
ip link set macvlanpihole up
ifconfig

Output:

...
macvlanpi Link encap:Ethernet  HWaddr 82:08:AC:9D:26:77  
          inet addr:192.168.0.200  Bcast:0.0.0.0  Mask:255.255.255.240
          inet6 addr: fe80::8008:acff:fe9d:2677/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:28208 errors:0 dropped:0 overruns:0 frame:0
          TX packets:1301 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1 
          RX bytes:3215747 (3.0 MiB)  TX bytes:173829 (169.7 KiB)

docker network create --driver=macvlan --gateway=192.168.0.1 --subnet=192.168.0.0/24 --ip-range=192.168.0.200/28 -o parent=eth0 macvlan

docker run --net=macvlan -dit --name nginx-test-01 --ip=192.168.0.215 nginx:alpine nginx-debug -g 'daemon off;'

192.168.0.215 is shown in the router as connected client.
browsing to 192.168.0.215 shows nginx page.
ssh into the container, executing ping google.de is successful.

docker rm nginx-test-01 --force

NAME=pihole-macvlanpihole
IMAGE=pihole/pihole
docker run --detach \
           --name ${NAME} \
           --restart always \
           --cap-add NET_ADMIN \
           --dns=127.0.0.1 \
           --dns=1.1.1.1 \
           --env "DNS1=1.1.1.1" \
           --env "DNS2=1.0.0.1" \
           --env "ServerIP=192.168.0.201" \
           --env "DNSMASQ_LISTENING=all" \
           --env "DNSMASQ_USER=root" \
           --env "WEBPASSWORD=cVJNKQyYrJ7e" \
           --env "TZ=Europe/Amsterdam" \
           --network macvlan \
           --ip "192.168.0.201" \
           --mac-address "02:42:c0:a8:01:d7" \
           ${IMAGE}

I can browse to the pihole gui by visiting 192.168.0.201 with my browser.
The query log is showing all the request. So they are there, that's fine.
But browsing to google.com with my browser leads to DNS_PROBE_FINISHED_BAD_CONFIG.

I noticed the following: Ssh into the container, executing ping google.com leads to:
ping: google.com: Temporary failure in name resolution
I think that's a problem?
So my container does "not have" an internet connection?

@xirixiz
Copy link
Author

xirixiz commented Feb 14, 2022

Hi, nice work! A couple of questions:

  • Is the DHCP server set to use 192.168.0.201 as a DNS server for all clients?
  • I believe the config of pihole might have changes slightly, can you try it like this:
NAME=pihole-macvlanpihole
IMAGE=pihole/pihole
docker run --detach \
           --name ${NAME} \
           --restart always \
           --cap-add NET_ADMIN \
           --dns=1.1.1.1 \
           --env "DNSMASQ_USER=root" \
           --env "WEBPASSWORD=cVJNKQyYrJ7e" \
           --env "TZ=Europe/Amsterdam" \
           --network macvlan \
           --ip "192.168.0.201" \
           --mac-address "02:42:c0:a8:01:d7" \
           ${IMAGE}

@titanbird
Copy link

titanbird commented Feb 14, 2022

Hi xirixiz.

Is the DHCP server set to use 192.168.0.201 as a DNS server for all clients?

Nope. For my tests, I change my desktop computer's connection settings to static and I set there 192.168.0.201 as the dns value.

I believe the config of pihole might have changes slightly, can you try it like this:

Oh okay. I will give it a try tomorrow. Which of the parameters do you think could lead now to success?

Best
titanbird

@titanbird
Copy link

Hi again :-)

No success yet. I deleted the pihole container and created a new one with

NAME=pihole-macvlanpihole
IMAGE=pihole/pihole
docker run --detach \
           --name ${NAME} \
           --restart always \
           --cap-add NET_ADMIN \
           --dns=1.1.1.1 \
           --env "DNSMASQ_USER=root" \
           --env "WEBPASSWORD=cVJNKQyYrJ7e" \
           --env "TZ=Europe/Amsterdam" \
           --network macvlan \
           --ip "192.168.0.201" \
           --mac-address "02:42:c0:a8:01:d7" \
           ${IMAGE}

But the same error happens (DNS_PROBE_FINISHED_BAD_CONFIG or DNS_PROBE_STARTED).
From inside the new container, ping google.com still leads to the same error as yesterday.

Very confusing because I can see all the queries within the pihole management gui:

grafik

grafik

Cheers and thanks
titanbird

@xirixiz
Copy link
Author

xirixiz commented Feb 15, 2022

I switched to AdGuard a while ago as AdGuard has an intergrated solution to handle different upstream DNS servers per subnet/client.
However, can you validate the upstream DNS servers in PiHole. Maybe set those to a basic non-tls upstream just to validate whether it's working.

https://discourse.pi-hole.net/t/how-do-i-choose-an-upstream-dns-server/258/10

Could also be the settings on the listening part of the interfaces (allowed list). Please validate that as well. Must be something like that.

image

@titanbird
Copy link

titanbird commented Feb 15, 2022

Thanks for the helpful stuff.

The interface listening behavior was already fine and the dns settings were also fine.
No success.

I retried everything from scretch. Now it is working... Somehow?! :-))

Here is all I had to do in short (please note the little ip changes, maybe there is the key to success in my case):

docker network create --driver=macvlan --gateway=192.168.0.1 --subnet=192.168.0.100/24 --ip-range=192.168.0.200/28 -o parent=eth0 macvlan0

ip link add macvlan-shim link eth0 type macvlan mode bridge

ip addr add 192.168.0.205/28 dev macvlan-shim

ip link set macvlan-shim up

I thought maybe i have to set this one for eth0:
sudo ip link set eth0 promisc on
But in the end, it's not needed.
So I restored it to off in my case:
sudo ip link set eth0 promisc off

ifconfig macvlan-shim
Output:

macvlan-s Link encap:Ethernet  HWaddr A2:CA:BE:14:B5:3B
          inet addr:192.168.0.205  Bcast:0.0.0.0  Mask:255.255.255.240
          inet6 addr: fe80::a0ca:beff:fe14:b53b/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:21169 errors:0 dropped:0 overruns:0 frame:0
          TX packets:987 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1
          RX bytes:2407069 (2.2 MiB)  TX bytes:133060 (129.9 KiB)
NAME=pihole-vlan
IMAGE=pihole/pihole
docker run --detach \
           --name ${NAME} \
           --restart always \
           --cap-add NET_ADMIN \
           --dns=1.1.1.1 \
           --env "DNSMASQ_USER=root" \
           --env "WEBPASSWORD=cVJNKQyYrJ7e" \
           --env "TZ=Europe/Amsterdam" \
           --network macvlan0 \
           --ip "192.168.0.200" \
           --mac-address "02:42:c0:a8:01:d7" \
           ${IMAGE}

I tried a ping command within the freshly created docker container to test if the gateway is accessible:
docker exec -ti pihole-vlan ping -c 4 192.168.0.1
Output:

PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=1.28 ms
64 bytes from 192.168.0.1: icmp_seq=2 ttl=64 time=0.989 ms
64 bytes from 192.168.0.1: icmp_seq=3 ttl=64 time=1.12 ms
64 bytes from 192.168.0.1: icmp_seq=4 ttl=64 time=1.75 ms

--- 192.168.0.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 6ms
rtt min/avg/max/mdev = 0.989/1.281/1.745/0.288 ms

I tested it on my notebook right beside me.
Static DNS setting:
dns=192.168.0.200
It works.

grafik
grafik

I did not make any settings in the pihole gui itself, it worked out of the box this way.
Off course, you need a system startup script to execute the ip-commands above once a boot. 👍

@xirixiz
Copy link
Author

xirixiz commented Feb 15, 2022

Great! Good you managed to fix it! 🚀

@donluca87
Copy link

What about:
route add -net 192.168.0.212 netmask 255.255.255.254 dev macvlan0 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment