This Docker image provides a DNS Over HTTP (DOH) service, designed to enhance privacy and security by encrypting DNS queries. It supports custom upstream DNS servers and execution of custom scripts. The image is compatible with various architectures including linux/amd64, linux/arm64, and linux/arm/v7. It offers both Alpine and Ubuntu based images for flexibility.
The image is a thin wrapper around the upstream project
m13253/dns-over-https. The upstream
release that is compiled into the image is pinned in the DOH_VERSION build
argument and defaults to v2.3.10.
- DNS Over HTTP support (RFC 8484 wireformat and the Google JSON API)
- Custom upstream DNS server option, multiple servers are supported
- Support for custom script execution (/app-config)
- Support for Docker / Swarm secrets
- Support for a custom configuration file
- Compatible with below architectures:
- Alpine: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6,linux/ppc64le,linux/s390x,linux/386
- Ubuntu: linux/amd64,linux/arm/v7
- Alpine based tiny images; Ubuntu based image also available
- Comprehensive DOH Server setup example using Docker Compose
Using DNS Over HTTP offers several advantages:
- Protects against ISP monitoring
- Avoids reliance on DOH providers, preserving privacy
- Learn more: Pros & Cons of DNS Over HTTPS
- Additional resource: DNS over HTTPS - Wikipedia
docker run -itd --name doh-server \
-p 8053:8053 \
-e UPSTREAM_DNS_SERVER=udp:208.67.222.222:53 \
satishweb/doh-serverThe container is configured entirely through environment variables. They are
substituted into /server/doh-server.sample.conf to generate
/server/doh-server.conf at startup.
| Variable | Default | Description |
|---|---|---|
UPSTREAM_DNS_SERVER |
udp:208.67.222.222:53,udp:208.67.220.220:53 |
Upstream resolver(s), comma separated. Prefix is udp, tcp or tcp-tls. A random server is picked for every attempt. |
DOH_HTTP_PREFIX |
/getnsrecord |
Primary HTTP path of the resolve endpoint. |
DOH_HTTP_PREFIXES |
/dns-query |
Additional HTTP paths the resolve endpoint is served on, comma separated. |
DOH_SERVER_LISTEN |
:8053 |
Listen address of the HTTP server. |
DOH_SERVER_TIMEOUT |
10 |
Upstream timeout in seconds. |
DOH_SERVER_TRIES |
3 |
Number of attempts per query. |
DOH_SERVER_VERBOSE |
false |
Enable query logging. |
DOH_SERVER_LOG_GUESSED_CLIENT_IP |
false |
Log the client IP taken from X-Forwarded-For / X-Real-IP. |
DOH_SERVER_ECS_ALLOW_NON_GLOBAL_IP |
false |
Allow EDNS Client Subnet for non global (private) client IPs. |
DOH_SERVER_ECS_USE_PRECISE_IP |
false |
Use the full client IP for EDNS Client Subnet instead of a /24 or /56 mask. |
DOH_SERVER_LOCAL_ADDR |
empty | Local address and port that is used for the queries to the upstream resolver. Empty lets the server choose one. |
DOH_SERVER_CERT |
empty | Path to the TLS certificate of the server. Has to be set together with DOH_SERVER_KEY. |
DOH_SERVER_KEY |
empty | Path to the TLS private key of the server. Has to be set together with DOH_SERVER_CERT. |
DOH_SERVER_TLS_CLIENT_AUTH |
false |
Require the clients to present a certificate. |
DOH_SERVER_TLS_CLIENT_AUTH_CA |
empty | Path to the certificate authority that signs the certificates of the clients. Required when DOH_SERVER_TLS_CLIENT_AUTH is enabled. |
DEBUG |
0 |
Set to 1 to trace the entrypoint script. |
DOH_CONFIG_FILE |
/server/doh-server.conf |
Path of the configuration file. It is generated from the template when it does not exist. |
Empty values are replaced by the defaults above, so an unset variable never makes the container fail to start.
The server can serve HTTPS itself, but it is recommended to put a reverse proxy in front of it, because the server does not do OCSP stapling and does not reload a certificate that changed on disk.
docker run -itd --name doh-server \
-p 8053:8053 \
-v /path/to/certs:/certs:ro \
-e DOH_SERVER_CERT=/certs/server.crt \
-e DOH_SERVER_KEY=/certs/server.key \
satishweb/doh-serverThe certificate and the key have to be set together, the container refuses to start when only one of them is given. The health check switches to https on its own when the server runs with TLS.
The files have to be readable for the unprivileged user of the container, so give them the mode 0644 and the directory the mode 0755.
The clients can be required to present a certificate as well:
-e DOH_SERVER_TLS_CLIENT_AUTH=true \
-e DOH_SERVER_TLS_CLIENT_AUTH_CA=/certs/ca.crt \The resolve endpoint is served on two paths by default:
| Path | Purpose |
|---|---|
/getnsrecord |
The historical path of this image. |
/dns-query |
The RFC 8484 standard path. Chrome, Firefox, cloudflared and most other clients use it by default. |
Both paths answer the exact same queries, so no client configuration is needed
for browsers and operating systems. Use DOH_HTTP_PREFIX to change the primary
path and DOH_HTTP_PREFIXES to change the additional ones:
docker run -itd --name doh-server \
-p 8053:8053 \
-e DOH_HTTP_PREFIX=/dns-query \
-e DOH_HTTP_PREFIXES="" \
satishweb/doh-serverServing more than one path requires a small patch to the upstream server, see
patches/multiple-paths.patch. The patch is
applied during the build and the build fails when it does not apply.
Mount your own configuration to replace the generated one completely. The file is used as is, no variable substitution is applied to it:
volumes:
- ./doh-server.conf:/server/doh-server.confThe path of the configuration file can be changed with DOH_CONFIG_FILE. That
way the custom configuration can live in a directory of its own and is not
replaced by the generated one:
docker run -itd --name doh-server \
-p 8053:8053 \
-v ./doh-server.conf:/server-custom/doh-server.conf:ro \
-e DOH_CONFIG_FILE=/server-custom/doh-server.conf \
satishweb/doh-serverWhen the file does not exist it is generated from the bundled template, so the variable works without a custom configuration as well.
The supported options are defined by the upstream project, see
doh-server/config.go.
The upstream server refuses to start when it encounters an unknown option.
Any variable that is referenced through a <NAME>_FILE environment variable is
read from that file and exported. This works with Docker Compose secrets: and
with Docker Swarm secrets:
services:
doh-server:
image: satishweb/doh-server
environment:
UPSTREAM_DNS_SERVER_FILE: /run/secrets/UPSTREAM_DNS_SERVER
secrets:
- UPSTREAM_DNS_SERVERSecret values are read verbatim, values containing whitespace or shell
metacharacters are preserved. The resolved values are written to
/tmp/doh-secrets.env inside the container for troubleshooting.
services:
doh-server:
image: satishweb/doh-server
hostname: doh-server
networks:
- default
environment:
DEBUG: "0"
# Upstream DNS server: proto:host:port
# We are using OpenDNS DNS servers as default,
# Here is the list of addresses: https://use.opendns.com/
UPSTREAM_DNS_SERVER: "udp:208.67.222.222:53"
DOH_HTTP_PREFIX: "/getnsrecord"
DOH_SERVER_LISTEN: ":8053"
DOH_SERVER_TIMEOUT: "10"
DOH_SERVER_TRIES: "3"
DOH_SERVER_VERBOSE: "true"
# You can add more variables here or as docker secret and entrypoint
# script will replace them inside doh-server.conf file
volumes:
# - ./doh-server.conf:/server/doh-server.conf
# Mount app-config script with your customizations
# - ./app-config:/app-config
deploy:
replicas: 1
# placement:
# constraints:
# - node.labels.type == workerbrew install colima
colima start --cpu 8 --memory 16 --disk 150
docker context use colimabrew install colima
colima start --arch x86_64 --cpu 8 --memory 16 --disk 150 -p buildx
docker context use colima-buildx- Install Docker CLI + Containerd
- Install docker-compose
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
docker buildx create --use
docker buildx inspect --bootstrapdocker build . --no-cache -t satishweb/doh-server -f Dockerfile.alpineTo build a different upstream release:
docker build . --build-arg DOH_VERSION=v2.3.10 -t satishweb/doh-server -f Dockerfile.alpineThe build fails if the version of the downloaded source does not match the requested one, so a wrong or truncated download can not slip through.
docker pull satishweb/doh-server| Tag | Description |
|---|---|
latest |
The alpine image, this is the primary image. |
alpine |
The alpine image, pinned to the latest release. |
ubuntu |
The ubuntu image, pinned to the latest release. |
v<version>-alpine |
The alpine image of a specific release, e.g. v2.3.10-1-alpine. |
v<version>-ubuntu |
The ubuntu image of a specific release, e.g. v2.3.10-1-ubuntu. |
The alpine image is the first choice and the ubuntu image is the second one,
so the latest tag always points to the alpine image. The version in the tag
is the version of the upstream
dns-over-https release that is
built into the image. When the same upstream release is rebuilt, the tag gets
a -1, -2, ... suffix.
Follow these steps to set up DOH Server on Linux, Mac, or Raspberry Pi in minutes using Docker Compose.
- Raspberry Pi/Linux/Mac with Docker preinstalled (Required)
- Docker Compose v2 (
docker compose) (Required) - DNS Server Setup on AWS R53 (Other providers supported)
- AWS Access Key, Secret key, and R53 DNS Hosted Zone ID (for LetsEncrypt based auto installation of SSL Certs) (Optional)
- Download the latest release from GitHub to your server:
wget https://github.com/satishweb/docker-doh/archive/v2.3.10-1-alpine.zip
unzip v2.3.10-1-alpine.zip
cp -rf docker-doh-2.3.10-1-alpine/examples/docker-compose-doh-server doh-server
rm -rf v2.3.10-1-alpine.zip docker-doh-2.3.10-1-alpine
cd doh-server- Copy
env.sample.conftoenv.confand update environment variables:
EMAIL=user@example.com
DOMAIN=example.com
SUBDOMAIN=dns
AWS_ACCESS_KEY_ID=AKIKJ_CHANGE_ME_FKGAFVA
AWS_SECRET_ACCESS_KEY=Nx3yKjujG8kjj_CHANGE_ME_Z/FnMjhfJHFvEMRY3
AWS_REGION=us-east-1
AWS_HOSTED_ZONE_ID=Z268_CHANGE_ME_IQT2CE6- Launch services:
./launch.sh- Add your custom hosts to override DNS records if needed:
mkdir -p data/unbound/custom
vi data/unbound/custom/custom.hosts- Determine your DOH address:
https://dns.example.com/getnsrecord- Test the DOH Server:
curl -w '\n' 'https://dns.example.com/getnsrecord?name=google.com&type=A'The endpoint is also available on the RFC 8484 standard path:
curl -w '\n' 'https://dns.example.com/dns-query?name=google.com&type=A'-
If a proxy is still running with a self-signed certificate:
- Check
data/proxy/certs/acme.jsoncontents. - Enable debug mode for the proxy by editing the proxy service in
docker-compose.yml. - Check proxy container logs for errors.
- Check
-
If unable to bind port 53 for unbound service:
- Stop
systemd-resolvedservice:sudo service systemd-resolved stop; sudo apt-get -y purge systemd-resolved - Retry.
- Stop
-
If unable to bind ports 80 and 443 for proxy service:
- Another program on the Docker host or one of the Docker containers may be using the same ports.
- Stop those programs or change the proxy service ports to unused ports.
-
If the container exits right away with a message about the upstream:
- The upstream resolver list is invalid. Every entry needs a
udp,tcportcp-tlsprefix, e.g.udp:1.1.1.1:53.
- The upstream resolver list is invalid. Every entry needs a
The image ships with a health check that queries the local server. The check
passes when the server answers with 200 or 503, so a broken upstream
resolver does not make the container look unhealthy (and does not cause a
restart loop in an orchestrator).
The upstream server answers with Access-Control-Allow-Origin: *, which is
required by the browser based DoH clients but also means that any website can
use your DoH server as a DNS proxy. When the DoH server points at a private
resolver this turns it into an open relay into your network.
Always run the DoH server behind a reverse proxy that applies rate limiting. The provided compose example does this with the Traefik rate limit middleware.
Set IPV6=true in env.conf before running ./launch.sh. The launch script
then adds docker-compose-ipv6.yml to the compose invocation, which enables
IPv6 on the docker network. The docker daemon needs to be started with
ipv6 = true in /etc/docker/daemon.json for dual stack networks.
Configure your router's DHCP settings to point to your DOH server's IP address.
Install Cloudflared for Linux, Mac, or Windows. Set your DOH server as upstream for Cloudflared as follows:
- Linux:
/usr/local/etc/cloudflared/config.yml - Mac:
/usr/local/etc/cloudflared/config.yaml - Windows: Location varies
proxy-dns: trueA Helm chart is available in the helm directory:
helm install doh-server ./helm/doh-server --set env.UPSTREAM_DNS_SERVER="udp:8.8.8.8:53"