desec-stack is the container stack behind the deSEC DNS service
Backbone of the deSEC Free Secure DNS Hosting Service
At a glance
- What is it?
- desec-stack is a Docker Compose application that assembles the deSEC name service: an eventually authoritative PowerDNS server that holds the DNSSEC keys, a stealth server that receives signed transfers, a Django API, and a set of supporting services. It is MIT licensed, and the interesting part is how the key material is kept away from the server that faces the internet.
- Who is it for?
- desec-stack suits an operator who wants to run a DNSSEC-signing DNS service with the signing keys isolated from the public-facing server, since the nslord and nsmaster split is the whole design and the compose file wires them together with per-service networks and read-only mounts.
- Can I use it commercially?
- Yes. MIT 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 4 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two PowerDNS servers, and why the keys are not on the public one
The stack is described as a docker compose application providing the basic stack for deSEC name services, and the component list is where the design is visible.
There is nslord, an eventually authoritative DNS server running PowerDNS, and the DNSSEC keys are generated here. There is nsmaster, a stealth authoritative DNS server also running PowerDNS, which receives fully signed AXFR zone transfers from nslord. No access to keys.
That split is the point of the whole architecture. nslord holds the private keys, so it is the component that would be catastrophic to compromise, and it is not the component the internet talks to. nsmaster is what resolvers query, and it holds nothing but signed data. An attacker who takes over nsmaster gets a denial of service and nothing else, because the keys needed to sign a forged zone stayed behind.
The names follow the DNS convention. A primary server is the one with the zone data, and a secondary receives transfers, and here the roles are inverted from the usual deployment: nslord is the origin that signs, nsmaster is the public face.
The other services fill in the parts a hosted DNS service needs. The api service is a RESTful API to create deSEC users and domains, with its documentation hosted separately. The www service is an nginx instance serving static website content and proxying to the api. The unbound service is a validating recursive resolver used by the api and celery-delegation to check how domains are delegated and secured through manage.py check-delegation, and it is reachable only from those two, as it is an open resolver by design.
The services that carry the state
The databases are named for the service they back, and there are four of them. dbapi, dblord and dbmaster are Postgres databases for the api and nsmaster, with a MariaDB database for nslord. The split follows the trust boundaries: the database holding the keys sits behind the same boundary as the service that uses it.
The asynchronous work is separated out rather than run in the API process. celery is a shadow instance of the api code for performing asynchronous tasks, with email delivery named as the example, and rabbitmq is celery's queue. celery-delegation is another shadow instance of the api code, running delegation checks off two queues: a bulk one for runs over the whole inventory, and an ad-hoc one for checks asked for on a single user's behalf.
That second queue arrangement is a small design decision with a large effect on a hosted service. A delegation check for one user and a sweep over every domain have completely different cost profiles, and putting them on separate queues means the cheap interactive check does not queue behind a full inventory run.
The remaining services close the loop. memcached is an api-wide in-memory cache, currently used to keep API throttling state, which is a per-request concern that would otherwise need a database round trip on every call. gatekeeper is an arbiter that the api consults about incoming account registrations, applying whatever rules the operator sees fit, and the README points at gatekeeper/README.md for the specifics. It is reachable only from the api and has no access to the rest of the stack, so the service that decides who may register cannot reach the data.
Two more are operational. openvpn-server is an OpenVPN server used to tunnel replication traffic between this stack and frontend DNS secondaries, and prometheus is the Prometheus server for monitoring.
Configuring the stack with environment variables
The configuration model is a single .env file, and the .env.default file in the repository is the template. The variables fall into groups that mirror the services, and the naming makes the mapping obvious.
The global settings are DESECSTACK_DOMAIN, the domain name under which the entire system will be running, with the API reachable at https://desec.$DESECSTACK_DOMAIN/api/, and DESECSTACK_NS, the names of the authoritative name servers, which point to your secondary name servers, with a minimum of 2. For a development setup the README recommends using yourname.dedyn.io.
The network settings are where the sharp edges are. DESECSTACK_IPV4_REAR_PREFIX16 is an IPv4 net of size /16 for assignment of internal container IPv4 addresses, and the README attaches a warning to it directly: if you change this in an existing setup, you need to manually update persisted data structures such as the MySQL grant tables, and better don't do it. DESECSTACK_IPV6_SUBNET is an IPv6 net, ideally /80, and DESECSTACK_IPV6_ADDRESS is the IPv6 address of the frontend container, ideally 0642:ac10:0080 within that subnet. DESECSTACK_PORT_XFR is the port over which XFRs are performed with secondaries.
The DNS-specific settings are also per-service. The nslord group holds the MariaDB password for pdns, the pdns API key, the carbon-server and carbon-ourname settings, and the default TTL including for the default NS records. The nsmaster group holds the pdns API key, which the README says is required so that zone deletions can be executed on nsmaster, which replicates to the secondaries, plus DESECSTACK_NSMASTER_TSIGKEY, the base64-encoded value of the default TSIG key used for talking to external secondaries with algorithm HMAC-SHA256.
That TSIG detail is the one that matters for a second deployment: the primary and its secondaries have to agree on a shared secret, and the tool that produces it is standard OpenVPN tooling, openvpn --genkey --secret ta.key.
The two requirements that are not optional
The Requirements section says that although most configuration is contained in this repository, some external dependencies need to be met before the application can be run, and there are two.
The first is the Docker daemon setting. We run this software with the --userland-proxy=false flag of the dockerd daemon, and recommend you do the same. The reason is in the compose file: the www service is published on 80:80/tcp and 443:443/tcp and 443:443/udp, and it is given a fixed MAC address of 06:42:ac:10:00:80 with static IPv4 and IPv6 addresses on the front network. A fixed MAC and a static address in a container network is a deliberate setup, and the userland proxy, which is Docker's own port-forwarding process, does not work with that arrangement. So this is a host configuration change rather than a stack option.
The second requirement is the certificate setup for openvpn-server, and the steps are spelled out. Get easy-rsa and follow the tutorial, then copy ca.crt, server.crt and server.key to openvpn-server/secrets/. Then create a pre-shared secret using openvpn --genkey --secret ta.key inside openvpn-server/secrets/.
The provisioning note for a secondary is where the two halves meet. For provisioning a secondary, use the same easy-rsa PKI and create a new client.key and client.crt pair, then transfer these securely onto the secondary along with ca.crt and ta.key. The README also notes you can create the key on the secondary and only transfer a certificate signing request and the certificate, which is the better practice when the secondary is in someone else's hands.
The reason the secondaries need a tunnel at all is the stealth design. nsmaster is not supposed to be a zone transfer source that the world can reach, so the replication path is a VPN rather than a public AXFR.
The compose file, and the network partitioning behind it
The docker-compose.yml at the repository root is the reference for how the pieces connect, and the networks section is where the design shows.
Each service declares a build context, an image name, an init flag, volumes, environment, depends_on, a logging driver and a restart policy. The www service, for example, builds from the www context, mounts the certificate directory at /etc/ssl/private as read-only, mounts the static html directory read-only, declares the DESECSTACK_ variables, depends on api and prometheus, and logs through syslog with the tag desec/www.
The certificate mount has a behaviour worth knowing. The DESECSTACK_WWW_CERTS directory is monitored for changes so that nginx can reload when new keys and certificates are provided, and the README warns that the reload is done any time something changes in the directory and that the relevant files are not watched individually. So dropping a new certificate into that directory triggers a reload, and dropping any other file into it does too.
The network names are descriptive of the trust model. There is front, which the www service joins with its static IPv4 and IPv6 addresses, and then rearwww, rearreplication and rearmonitoring_www, each with its own address inside the /16. The name is the documentation: www talks to the front network, and each rear network serves one channel.
The compose file also shows the monitoring pattern, with a www_monitor service running the nginx-prometheus-exporter image at 1.4.1, depending on www, and scraping http://www:8080/stub_status. The prometheus service is a separate service in the stack and www depends on it, so metrics are part of the deployment rather than something bolted on.
For development and testing there are separate files, docker-compose.dev.yml, docker-compose.test-api.yml and docker-compose.test-e2e2.yml, plus a run-api-tests-stack.sh script, and the repository has a .env.dev.template alongside the .env.default.
Where desec-stack fits, and where it stops
The comparison worth making is with running a normal authoritative DNS server, and with the hosted deSEC service itself.
Against a normal authoritative server, this stack is doing a much larger job. A PowerDNS installation answers queries for zones you control. This stack also has an API, an account system with registration gated by a gatekeeper, a web front end, a throttling cache, delegation checking against a real resolver, email delivery, and a full monitoring stack. The DNS is the core function, but it is one component of a hosted service.
Against the hosted deSEC service, the difference is ownership. Running this yourself means the operator controls the accounts, the retention, the logs and the keys, and is responsible for the four databases, the queue, the resolver, the OpenVPN tunnel and the certificate lifecycle. For an organisation with a compliance reason to keep its DNS hosting and its account data in house, that is the whole point. For anyone else, the hosted service removes a large amount of operational work.
The limits are structural. This is a docker compose application, so it assumes a Docker host with the userland proxy disabled and manual certificate provisioning, which is not a turnkey install. The compose file uses static IPs and a fixed MAC address, so the /16 prefix has to be chosen before the first run, and the README is blunt about the cost of changing it later. The OpenVPN tunnel to the secondaries means the replication path depends on that tunnel being up.
There is also a dependency the README does not detail: the `unbound` service is an open resolver by design, and the README says it is reachable only from the api and celery-delegation. That is a configuration statement, and a deployment has to honour it, since an open resolver reachable from the internet is an amplification liability.
For a DNS operator who wants DNSSEC signing with the keys kept off the public server, the design here is the interesting part and is worth reading even if you run something else. The licence is MIT, the last push was on 2026-10-19, the repository is not archived, and the code is Python.
Editorial conclusion
desec-stack suits an operator who wants to run a DNSSEC-signing DNS service with the signing keys isolated from the public-facing server, since the nslord and nsmaster split is the whole design and the compose file wires them together with per-service networks and read-only mounts. It is the wrong tool if you only need a DNS server, because this is a hosted service stack with an API, an account system and a web front end rather than a resolver or an authoritative server, and it is the wrong tool if you cannot run a Docker daemon with the userland proxy disabled. Before deploying it, read the requirements section carefully, because the certificate setup for openvpn-server and the --userland-proxy=false flag are not optional details, and fix DESECSTACK_IPV4_REAR_PREFIX16 before the first run rather than after.
Frequently asked questions
What is the difference between nslord and nsmaster in desec-stack?
nslord is the eventually authoritative PowerDNS server where the DNSSEC keying material is generated, and nsmaster is the stealth authoritative PowerDNS server that receives fully signed AXFR zone transfers from it and has no access to the keys. The split keeps the signing keys off the server the internet queries.
What must be configured before running the desec-stack?
Two things. The Docker daemon has to be run with the --userland-proxy=false flag of the dockerd daemon, which the README recommends, and certificates have to be set up for openvpn-server: copy ca.crt, server.crt and server.key into openvpn-server/secrets/ and create a pre-shared secret with openvpn --genkey --secret ta.key in the same directory.
How is desec-stack configured?
Through environment variables or an .env file, with the .env.default file in the repository as a template. The variables are grouped as global, such as DESECSTACK_DOMAIN and DESECSTACK_NS, and then per service, with separate groups for network, certificates, api, gatekeeper, webapp, nslord, nsmaster and monitoring.
What are the gatekeeper and unbound services for?
gatekeeper is an arbiter that the api consults about incoming account registrations, applying whatever rules the operator sees fit, and it is reachable only from the api with no access to the rest of the stack. unbound is a validating recursive resolver used by the api and celery-delegation to check how domains are delegated and secured, and it is reachable only from those two because it is an open resolver by design.
Why is DESECSTACK_IPV4_REAR_PREFIX16 hard to change later?
The README warns that if you change it in an existing setup you need to manually update persisted data structures such as the MySQL grant tables, and says better don't do it. The /16 provides the static container addresses that the compose file assigns, so it has to be chosen before the first run.
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/desec-io-desec-stack)Was this useful?