dnscrypt-server-docker: a DNSCrypt resolver you run yourself
A Docker image for a non-censoring, non-logging, DNSSEC-capable, DNSCrypt-enabled DNS resolver
At a glance
- What is it?
- dnscrypt-server-docker packages a caching, DNSSEC capable, non-logging DNSCrypt resolver as a container image, with an Anonymized DNS relay mode, Prometheus metrics, TLS forwarding for DoH backends and domain filtering. It suits operators who want to answer DNS from their own address instead of a public resolver.
- Who is it for?
- Adopt dnscrypt-server-docker if you want to run the resolver that your own network and clients point at, on a host where you control the address and the keys, and you are comfortable operating a container on a public port. Do not adopt it if you only need a private resolver inside one network, where a plain caching server is simpler, or if you need to serve public DNS at scale without a second relay in front of it.
- Can I use it commercially?
- Yes. ISC is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly Shell, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A resolver in one docker run
The image described as a Docker image for a non-censoring, non-logging, DNSSEC-capable, DNSCrypt-enabled DNS resolver, and the pitch is speed of setup. The README says that if you are already familiar with Docker, it should not take more than five minutes to get your resolver up and running.
The core of the setup is a single docker run invocation:
docker run --name=dnscrypt-server -p 443:443/udp -p 443:443/tcp \
--restart=unless-stopped \
-v /etc/dnscrypt-server/keys:/opt/encrypted-dns/etc/keys \
jedisct1/dnscrypt-server init -N example.com -E '192.168.1.1:443'The mechanics of that line are the mechanics of the whole design. The two port mappings publish UDP and TCP on 443, the restart policy keeps the resolver up, and the volume maps a host directory onto the container path where the server keeps its keys. The init subcommand takes the provider name with -N and the external address with -E.
Two details in that line are easy to get wrong. The address in -E must be the real external IP that clients will connect to, not the internal Docker one. And the volume mount is what persists your identity across container rebuilds, so the README asks you not to change the container side of the path, and to adjust the host side if you created the directory somewhere else.
There is a macOS specific caveat. The README says not to use -v with a path mapping on macOS, because current versions of macOS and Docker do not seem to work well with shared directories.
The provider name and the DNS stamp
Before the first run there is one decision that has long term consequences, and the README puts it first.
You need to think about a name, because it is going to be part of your DNSCrypt provider name, and if you plan to make the resolver publicly accessible then this name will be public. By convention it has to look like a domain name, such as example.com, but it does not have to be an actual registered domain. That last point matters for a home lab or an internal resolver: you can use a name that you control conceptually without buying a domain.
The init command prints the DNS stamp of the server when it finishes. That stamp is the public identity of your resolver in the DNSCrypt ecosystem, and it is generated as part of creating the keys in the mounted directory. This is the reason the keys directory has to be persistent: the stamp belongs to that key material.
The README also handles the ordinary mistake of a name clash. If Docker complains that the name is already in use, the container can be removed with docker rm --force dnscrypt-server, and docker ps is the way to confirm the container is running at all.
IPv6 is handled too. Addresses have to be enclosed in brackets, for example [2001:0db8::412f]:443, and multiple comma-separated addresses and ports can be given in one -E value, for example '192.168.1.1:443,[2001:0db8::412f]:443'.
Updating the container without losing the identity
The update procedure in the README is longer than a normal docker pull, and the extra steps are all about the keys.
The sequence is: pull the latest image, then verify that the keys directory really contains the key material by listing it. If the directory is empty, the likely cause is that the container was created without the -v mapping, and the recovery is to copy the data out of the running container with docker cp dnscrypt-server:/opt/encrypted-dns/etc/keys ~/keys.
After that the old container is stopped, renamed to dnscrypt-server-old, and a new container is created with the init command again, and only then is the old one removed with docker rm dnscrypt-server-old. The rename step is what makes the operation safe: at every point there is a working container or a recoverable one.
The README also notes that parameters differ from the ones used in the previous container. That is a warning about the habit of reusing an old command line. If you originally did not activate relaying and now want it, you append -A to the command, and if you want metrics you append -M 0.0.0.0:9100 and add the corresponding -p 9100:9100/tcp mapping after the existing TCP mapping.
Anonymized relay, Prometheus metrics and TLS forwarding
Three optional features extend the resolver, and each is a switch on the init command.
The Anonymized DNS relay is enabled by adding -A. The README describes the server as configurable as a relay for the Anonymized DNSCrypt protocol, and says the relay DNS stamp will be printed right after the regular stamp. For a resolver that is meant to be reachable from clients without them having to speak the full protocol, this is the mode that matters.
Prometheus metrics are exposed inside the container at http://127.0.0.1:9100/metrics, and become reachable from outside by adding the -M option with a listening address and port, for example -M 0.0.0.0:9100. The README points out that these can be indexed with Prometheus and that dashboards can be built with Grafana.
TLS forwarding solves a practical conflict. If the DNS server is listening on port 443 but you also want a web or DoH service on that port, you add -T followed by the backend server address and port, for example -T 10.0.0.1:4443, and the server relays the non-DNS traffic to it. The README is explicit that the backend server must support the HTTP/2 protocol.
Filtering by domain list
The server can be used to block domains, and the README gives a concrete example of the feature in use: the sfw.scaleway-fr server uses it to provide a service that blocks websites possibly not suitable for children.
The configuration is a directory and a text file. You create the directory:
mkdir -p /etc/dnscrypt-server/listsand put the domains to block in a file named /etc/dnscrypt-server/lists/blacklist.txt, one domain per line.
That is the whole mechanism, and its simplicity is the point. A filtering resolver built on a general purpose DNS server usually needs a rule engine, a database and a management interface. Here the rule set is a file that can live in the same directory as the keys, be edited with any text editor, be version controlled and be shipped to the container the same way the keys are.
The trade-off is equally obvious. A blacklist is a deny list, so anything not on it resolves normally, and the file has to be maintained by hand or by a script that fetches the lists.
The image compiles Unbound and the server from source
The Dockerfile shows the real engineering behind the image, and it is not a package install.
The base is ubuntu:26.04, with build dependencies installed separately from runtime dependencies so the former can be purged afterwards. Unbound is compiled from the NLnetLabs repository with a pinned git revision, 13b6717f1716810523ffb60f971a6c9c5e8ce127, configured with pthreads, a dedicated _unbound user and libevent, then the build dependencies are purged and the man pages removed to shrink the image.
The Rust toolchain is installed through rustup with a minimal profile and the stable toolchain, with RUSTFLAGS set to strip symbols at link time. That is the DNSCrypt server itself being built inside the image rather than pulled from a distribution.
The rest of the tree is the runtime logic. entrypoint.sh drives the container, encrypted-dns.sh and encrypted-dns.toml.in hold the server configuration and its template, unbound.sh and unbound-check.sh manage the caching resolver, watchdog.sh watches the processes, and opennic.hints, opennic.key and undelegated.txt carry the OpenNIC configuration. There is also a kube/ directory for Kubernetes deployments and a scripts/ directory.
The Compose file shows the minimal deployment, and it is worth reading because it differs from the README example in a useful way:
image: jedisct1/dnscrypt-server:latest
container_name: dnscrypt
restart: always
command: "init -N example.com -E '127.0.0.1:5443'"It uses a named volume for the keys rather than a host path, and it publishes on 5443 rather than 443.
Where this image is the wrong tool
Three cases stand out.
First, if you only need a private resolver for a single network, this is heavier than the job. The container assumes a public-facing posture: a DNSCrypt provider name, a DNS stamp, port 443, and the option to relay for the general internet. A plain caching resolver inside your own LAN has none of those requirements and needs none of this machinery.
Second, the operational profile is a single container with a fixed directory of keys and a couple of switches. If you intend to publish DNS at scale, you are running a resolver whose identity is bound to one key directory, and the README does not describe a high availability arrangement for that. Scaling means thinking about multiple instances and how clients pick between them, and none of that is documented here.
Third, the privacy claim has a boundary worth understanding. The resolver is described as non-logging, which is a property of the server software, and the image keeps the keys on the host, so whoever controls the host controls the identity and can see the queries passing through it.
One practical trap belongs in the same list. The DNSCrypt provider name is baked into the stamp and is public if the resolver is public, and if you later want a different name you are not renaming a container, you are generating a new identity and telling your clients about it. And on macOS the volume mount that makes the keys persistent is documented as unreliable, which means the upgrade path in the README starts with a recovery step that copies the keys back out of the container.
Editorial conclusion
Adopt dnscrypt-server-docker if you want to run the resolver that your own network and clients point at, on a host where you control the address and the keys, and you are comfortable operating a container on a public port. Do not adopt it if you only need a private resolver inside one network, where a plain caching server is simpler, or if you need to serve public DNS at scale without a second relay in front of it. Verify three things first: that the DNSCrypt provider name you pick will be public if the resolver is public, since the name becomes part of the provider identity and the DNS stamp; that the keys directory is genuinely persisted, because a lost key directory means a new identity and a new stamp, and the README has a recovery step for exactly that case; and that your firewall and network allow UDP and TCP on port 443 alongside whatever web service you relay to. The licence is ISC, the last push was on 2026-10-18, and the repository publishes no GitHub releases.
Frequently asked questions
How do I run a DNSCrypt resolver with this Docker image?
Run docker run with the dnscrypt-server name, publish UDP and TCP on port 443, mount a host directory onto /opt/encrypted-dns/etc/keys and pass the init subcommand with a provider name and your external address. The init command prints the DNS stamp of the server.
What do the init options mean?
-N sets the provider name, which by convention looks like a domain name and becomes public if the resolver is public. -E sets the external address and port that clients connect to, with IPv6 addresses in brackets and multiple addresses separated by commas. -A enables the Anonymized DNSCrypt relay, -M exposes the Prometheus metrics endpoint, and -T forwards TLS and DoH traffic to a backend that must support HTTP/2.
How do I update the container without losing my keys?
Pull the new image, check that the keys directory still contains the key material, and if it is empty copy it back with docker cp dnscrypt-server:/opt/encrypted-dns/etc/keys ~/keys. Then stop the container, rename it to dnscrypt-server-old, create a new one with init, and remove the old one. Remember that any new options have to be added again to the command.
How does the blacklist filtering work?
Create the directory /etc/dnscrypt-server/lists and put one blocked domain per line in a file named blacklist.txt inside it. The README describes this as the mechanism a server such as sfw.scaleway-fr uses to block websites possibly not suitable for children.
Is there a Compose or Kubernetes setup?
Yes. The repository includes a docker-compose.yml using the jedisct1/dnscrypt-server:latest image with a named volume for the keys and a 5443 port mapping, and a kube/ directory for Kubernetes deployments.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/dnscrypt-dnscrypt-server-docker)Was this useful?