A lightweight, JSON-configurable DNS server for internal networks
Project description
NanoDNS
A lightweight, zero-dependency DNS server for internal networks — configured with a single JSON file.
Features
- 🚀 Zero dependencies — pure Python standard library only
- 📝 JSON config — human-readable, hot-reloadable configuration
- 🔄 Upstream forwarding — forwards unknown queries to public DNS
- 💾 LRU Cache — configurable in-memory response cache with TTL
- 🌐 Record types — A, AAAA, CNAME, MX, TXT, PTR, NS, SOA
- 🃏 Wildcard records —
*.example.internalsupport - 🚫 Rewrites / blocking — NXDOMAIN any domain instantly
- ♻️ Hot reload — config changes applied without restart
- ⚡ Async — built on Python asyncio
- 🐳 Docker — multi-platform OCI image (
amd64+arm64) - 🔏 Signed — cosign keyless signatures via Sigstore
Installation
pip
pip install nanodns
Docker
# Docker Hub
docker pull iyuangang/nanodns:latest
# GitHub Container Registry
docker pull ghcr.io/iyuangang/nanodns:latest
Quick Start
pip
# 1. Generate an example config
nanodns init
# 2. Edit nanodns.json to your needs
# 3. Validate config
nanodns check nanodns.json
# 4. Start (high port — no admin rights needed)
nanodns start --config nanodns.json --port 5353
# 5. Start on port 53 (requires root / Administrator)
sudo nanodns start --config nanodns.json
Docker
# Generate a config first
nanodns init nanodns.json
# Run with Docker
docker run -d \
--name nanodns \
-p 53:53/udp \
-v $(pwd)/nanodns.json:/etc/nanodns/nanodns.json:ro \
--cap-add NET_BIND_SERVICE \
iyuangang/nanodns:latest
# Run with Docker Compose
docker compose up -d
Verify it works
# Linux / macOS
dig @127.0.0.1 -p 5353 web.internal.lan A
# Windows
nslookup web.internal.lan 127.0.0.1
# PowerShell
Resolve-DnsName -Name web.internal.lan -Server 127.0.0.1 -Type A
Configuration
{
"server": {
"host": "0.0.0.0",
"port": 53,
"upstream": ["8.8.8.8", "1.1.1.1"],
"upstream_timeout": 3,
"upstream_port": 53,
"cache_enabled": true,
"cache_ttl": 300,
"cache_size": 1000,
"log_level": "INFO",
"log_queries": true,
"hot_reload": true
},
"zones": {
"internal.lan": {
"soa": {
"mname": "ns1.internal.lan",
"rname": "admin.internal.lan",
"serial": 2024010101,
"refresh": 3600,
"retry": 900,
"expire": 604800,
"minimum": 300
},
"ns": ["ns1.internal.lan"]
}
},
"records": [
{ "name": "ns1.internal.lan", "type": "A", "value": "192.168.1.10", "ttl": 3600 },
{ "name": "web.internal.lan", "type": "A", "value": "192.168.1.100", "ttl": 300 },
{ "name": "db.internal.lan", "type": "A", "value": "192.168.1.101", "ttl": 300 },
{ "name": "api.internal.lan", "type": "CNAME", "value": "web.internal.lan" },
{ "name": "ipv6.internal.lan", "type": "AAAA", "value": "fd00::1", "ttl": 300 },
{ "name": "internal.lan", "type": "MX", "value": "mail.internal.lan", "priority": 10 },
{ "name": "internal.lan", "type": "TXT", "value": "v=spf1 ip4:192.168.1.0/24 ~all" },
{
"name": "app.internal.lan",
"type": "A",
"value": "192.168.1.200",
"wildcard": true,
"comment": "Matches *.app.internal.lan"
}
],
"rewrites": [
{ "match": "ads.doubleclick.net", "action": "nxdomain" },
{ "match": "*.tracker.example", "action": "nxdomain" }
]
}
See USAGE.md for the full configuration reference.
CLI Reference
nanodns start --config FILE [--host HOST] [--port PORT] [--log-level LEVEL] [--no-cache]
nanodns init [OUTPUT] Generate an example config file
nanodns check CONFIG Validate a config file and print a summary
nanodns --version
Record Types
| Type | value |
Extra fields |
|---|---|---|
| A | IPv4 address | — |
| AAAA | IPv6 address | — |
| CNAME | Target hostname | — |
| MX | Mail server hostname | priority (int) |
| TXT | Text string | — |
| PTR | Pointer hostname | — |
| NS | Nameserver hostname | — |
All records support: ttl (default 300), wildcard (bool), comment (string).
Docker
Images
| Registry | Image |
|---|---|
| Docker Hub | iyuangang/nanodns |
| GHCR | ghcr.io/iyuangang/nanodns |
Tags
| Tag | Description |
|---|---|
latest |
Latest stable release |
1.2.3 |
Exact version |
1.2 |
Minor version |
1 |
Major version |
sha-a1b2c3 |
Specific commit |
Platforms
linux/amd64 · linux/arm64 (Raspberry Pi, Apple Silicon)
docker-compose.yml
services:
nanodns:
image: iyuangang/nanodns:latest
container_name: nanodns
restart: unless-stopped
ports:
- "53:53/udp"
volumes:
- ./nanodns.json:/etc/nanodns/nanodns.json:ro
cap_add:
- NET_BIND_SERVICE
read_only: true
OCI Compliance
Images follow the OCI Image Spec with standard annotations:
# Inspect OCI annotations
docker inspect iyuangang/nanodns:latest \
--format '{{json .Config.Labels}}' | python3 -m json.tool
# Verify cosign signature (keyless / Sigstore)
cosign verify \
--certificate-identity-regexp="https://github.com/iyuangang/nanodns/*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
iyuangang/nanodns:latest
Deployment
Linux — systemd
# /etc/systemd/system/nanodns.service
[Unit]
Description=NanoDNS Server
After=network.target
[Service]
ExecStart=/usr/local/bin/nanodns start --config /etc/nanodns/nanodns.json
Restart=on-failure
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now nanodns
Windows — NSSM
nssm install NanoDNS "C:\Python\Scripts\nanodns.exe"
nssm set NanoDNS AppParameters "start --config C:\dns\nanodns.json"
nssm start NanoDNS
High Port (No Root Required)
nanodns start --config nanodns.json --port 5353
# Redirect port 53 → 5353 with iptables
sudo iptables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT --to-port 5353
CI / CD
| Workflow | Trigger | Description |
|---|---|---|
release.yml |
v* tag |
Run tests → build wheel → GitHub Release → publish PyPI |
docker.yml |
v* tag / main |
Build multi-platform OCI image → push GHCR + Docker Hub → cosign sign |
Release a new version
# 1. Bump version in pyproject.toml
# 2. Commit and tag
git add pyproject.toml
git commit -m "chore: bump version to 0.2.0"
git tag v0.2.0
git push origin main --tags
Both PyPI and Docker Hub are published automatically.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nanodns-0.3.3.tar.gz.
File metadata
- Download URL: nanodns-0.3.3.tar.gz
- Upload date:
- Size: 31.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
90b81bc010977197198a65133260351978cc56ea78de70d4103384af32cb9756
|
|
| MD5 |
56801c9bf5087d2a692542ac2dd3b021
|
|
| BLAKE2b-256 |
ab35fcfeea4208c536c42d00daa234ef45d1e6bfa368ed85cc9b2616a2da5850
|
Provenance
The following attestation bundles were made for nanodns-0.3.3.tar.gz:
Publisher:
release.yml on iyuangang/nanodns
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nanodns-0.3.3.tar.gz -
Subject digest:
90b81bc010977197198a65133260351978cc56ea78de70d4103384af32cb9756 - Sigstore transparency entry: 1026933352
- Sigstore integration time:
-
Permalink:
iyuangang/nanodns@5543702b052e2818d286aa7234429ea0b4a9b306 -
Branch / Tag:
refs/tags/v0.3.3 - Owner: https://github.com/iyuangang
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5543702b052e2818d286aa7234429ea0b4a9b306 -
Trigger Event:
push
-
Statement type:
File details
Details for the file nanodns-0.3.3-py3-none-any.whl.
File metadata
- Download URL: nanodns-0.3.3-py3-none-any.whl
- Upload date:
- Size: 32.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
820528ee09e0956755ae611d946912b87e4acdd1eb345edb1ea1d45bada0a472
|
|
| MD5 |
58929708c0e85f0f0e990558068b17af
|
|
| BLAKE2b-256 |
8594cae53b209201e537d9f598bbcd44619ddb59b1d68314b183402c0807fd86
|
Provenance
The following attestation bundles were made for nanodns-0.3.3-py3-none-any.whl:
Publisher:
release.yml on iyuangang/nanodns
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nanodns-0.3.3-py3-none-any.whl -
Subject digest:
820528ee09e0956755ae611d946912b87e4acdd1eb345edb1ea1d45bada0a472 - Sigstore transparency entry: 1026933458
- Sigstore integration time:
-
Permalink:
iyuangang/nanodns@5543702b052e2818d286aa7234429ea0b4a9b306 -
Branch / Tag:
refs/tags/v0.3.3 - Owner: https://github.com/iyuangang
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5543702b052e2818d286aa7234429ea0b4a9b306 -
Trigger Event:
push
-
Statement type: