Cloudflare DNS over TLS Docker container
DNS caching server connected to Cloudflare 184.108.40.206 DNS over TLS (IPv4 and IPv6) with DNSSEC, DNS rebinding protection, built-in Docker healthcheck and malicious IPs + hostnames blocking
10 Nov 2018: The port binding is back to
53:53/udp and the container runs without root privileges
This is thanks to
setcap 'cap_net_bind_service=+ep' /usr/sbin/unbound allowing Unbound to bind to well known ports
|Image size||RAM usage||CPU usage|
|19.4MB||13.2MB to 70MB||Low|
It is based on:
- Alpine 3.8
- Unbound 1.7.3
- Malicious hostnames list from github.com/qdm12/malicious-hostnames-docker
- Malicious IPs list from github.com/qdm12/malicious-ips-docker
- bind-tools for the healthcheck with
nslookup duckduckgo.com 127.0.0.1
- libcap to set low port bind capabilities to unbound so that the container runs without root
It also uses DNS rebinding protection and DNSSEC Validation:
You can also block additional domains of your choice, amongst other things, see the Extra section
Diagrams are shown for router and client-by-client configurations in the Connect clients to it section.
docker run -it --rm --name cloudflare-dns-tls -p 53:53/udp \ -e VERBOSITY=3 -e VERBOSITY_DETAILS=3 -e BLOCK_MALICIOUS=off \ qmcgaw/cloudflare-dns-server
VERBOSITYenvironment variable goes from 0 (no log) to 5 (full debug log) and defaults to 1
VERBOSITY_DETAILSenvironment variable goes from 0 to 4 and defaults to 0 (higher means more details)
BLOCK_MALICIOUSenvironment variable can be set to 'on' or 'off' and defaults to 'on' (note that it consumes about 50MB of additional RAM). It blocks malicious IP addresses and malicious hostnames from being resolved.
LISTENINGPORTenvironment variable sets the UDP port on which the Unbound DNS server should listen to (internally), and defaults to 53
You can check the verbose output with:
docker logs -f cloudflare-dns-tls
See the Connect clients to it section to finish testing.
Run it as a daemon
docker run -d -p 53:53/udp qmcgaw/cloudflare-dns-server
or use docker-compose.yml with:
docker-compose up -d
Connect clients to it
Option 1: Router (recommended)
Block the UDP 53 outgoing port on your router firewall so that all DNS traffic must go through this container.
All machines connected to your router will use the 220.127.116.11 encrypted DNS by default
Configure your router to use the LAN IP address of your Docker host as its primary DNS address.
- Access your router page, usually at http://192.168.1.1 and login with your credentials
- Change the DNS settings, which are usually located in Connection settings / Advanced / DNS server
- If a secondary fallback DNS address is required, use a dull ip address such as the router's IP 192.168.1.1 to force traffic to only go through this container
Option 2: Client, one by one
You have to configure each machine connected to your router to use the Docker host as their DNS server.
Connect other Docker containers by specifying the DNS to be the host IP address
docker run -it --rm --dns=127.0.0.1 alpine
version: '3' services: test: image: alpine network_mode: bridge dns: - 127.0.0.1
If the containers are in the same virtual network, you can simply set the
dns to the LAN IP address of the DNS container (i.e.
- Open the control panel and follow the instructions shown on the screenshots below.
Enter the IP Address of your Docker host as the Preferred DNS server (
192.168.1.210 in my case)
You can set the Cloudflare DNS server address 18.104.22.168 as an alternate DNS server although you might want to
leave this blank so that no domain name request is in plaintext.
When closing, Windows should try to identify any potential problems.
If everything is fine, you should see the following message:
Follow the instructions at https://support.apple.com/kb/PH25577
You probably know how to do that. Otherwise you can usually modify the first line of /etc/resolv.conf by changing the IP address
of your DNS server.
Block domains of your choice
- Create a file on your host
Write the following to the file to block youtube.com for example:
local-zone: "youtube.com" static
Change the ownership and permissions of
chown 1000:1000 include.conf chmod 400 include.conf
Launch the Docker container with:
docker run -it --rm -p 53:53/udp \ -v $(pwd)/include.conf:/etc/unbound/include.conf \ qmcgaw/cloudflare-dns-server
Build all the images yourself
docker build -t qmcgaw/malicious-ips https://github.com/qdm12/malicious-ips-docker.git docker build -t qmcgaw/malicious-hostnames https://github.com/qdm12/malicious-hostnames-docker.git docker build -t qmcgaw/dns-rootanchor https://github.com/qdm12/dns-rootanchor-docker.git docker build -t qmcgaw/cloudflare-dns-server https://github.com/qdm12/cloudflare-dns-server.git
This container requires the following connections:
- UDP 53 Inbound (only if used externally)
- TCP 853 Outbound to 22.214.171.124 and 126.96.36.199
- [ ] Build Unbound binary at image build stage