A smarter hosts file for modern development.
Override DNS, simulate failures, and test real-world conditions, without touching host files.
Now you can make it DNS.
It's Always DNS helps you test scenarios that are difficult or awkward to reproduce with static host files:
- Mock external APIs without changing application code
- Test mobile devices against a local backend
- Route real domains to local services
- Simulate slow or degraded DNS responses
- Reproduce intermittent
SERVFAIL,NXDOMAIN, or timeout behaviour - Apply rules to a single client IP or CIDR without affecting others
- Update behaviour in real time via the API
-
Copy
.env.exampleto.env -
Update the values in
.envas needed -
Start the application:
-
Using Docker:
docker compose up
-
Or run locally:
python3 app/main.py
-
-
Create a rule:
curl -X POST http://[HostIP/localhost]:8080/rules \ -H 'Content-Type: application/json' \ -d '{"domain":"api.example.com","rule":{"default":{"config":{"ttl":10,"ip":"127.0.0.1"},"enabled":true}}}'
-
Test DNS:
dig @127.0.0.1 -p 53 api.example.com
If the response is
127.0.0.1, it's working. -
Update your hosts or servers to use iadns as the primary DNS server, then start testing immediately.
{
"github.com": {
"clients": {
"192.168.1.50": {
"config": {
"failure_rate": 1.0,
"failure_response": "nxdomain"
}
}
}
}
}{
"api.stripe.com": {
"default": {
"enabled": true,
"config": {
"ip": "192.168.1.100",
"ttl": 10
}
}
}
}
Rules are grouped by domain. Each domain can have a default rule and optional per-client overrides:
{
"api.example.com": {
"default": {
"enabled": true,
"config": {
"delay_ms": 500,
"jitter_ms": 100,
"ttl": 10
}
},
"clients": {
"192.168.1.50": {
"enabled": true,
"config": {
"ip": "192.168.1.50",
"failure_rate": 0.3,
"failure_response": "servfail",
"ttl": 10
}
}
}
}
}Client rules can use exact IPv4 addresses or CIDR ranges. Wildcard domains such as *.example.com are supported.
Matching prefers specificity:
- Exact client IP on exact domain
- Client CIDR on exact domain
- Exact client IP on wildcard domain
- Client CIDR on wildcard domain
- Default on exact domain
- Default on wildcard domain
- Upstream DNS fallback
Config effects run in this order:
- Failure chance
- Delay and jitter
- IP override or upstream resolution
- TTL rewrite
Rules are stored in memory by default.
To save rules to local JSON, set RULES_FILE.
RULES_FILE=./rules.json python3 app/main.pyUncomment the volume configuration in docker-compose.yml:
volumes:
- itsalwaysdns-data:/data
volumes:
itsalwaysdns-data:
Also in your .env file:
RULES_FILE=/data/rules.json
Environment variables:
DNS_PORT, default53HTTP_PORT, default8080UPSTREAM_DNS, optional overrideUPSTREAM_PORT, default53DEFAULT_TTL, default10RULES_FILE, optional path to a JSON rules file
When UPSTREAM_DNS is unset, iadns reads the first nameserver from /etc/resolv.conf. In Docker, that normally means it uses Docker's injected resolver, so container DNS behavior stays aligned with the host/container environment. If no system resolver can be detected, it falls back to 1.1.1.1.
Using port 53 is convenient when you want the app to act like a normal DNS server, but some local environments require elevated privileges for ports below 1024. If you want a no-privilege local setup, use a high port:
DNS_PORT=8053 python3 app/main.py
dig @127.0.0.1 -p 8053 api.example.comcurl http://[HostIP/localhost]:8080/healthThe test suite uses the Python standard library:
python3 -m unittest discover- Swagger UI: http://[HostIP/localhost]:8080/docs
- OpenAPI: http://[HostIP/localhost]:8080/openapi.json
IP overrides are intentionally IPv4 A-record focused. Other query types, including AAAA, are forwarded upstream unless a failure or delay rule applies.
That keeps the tool small and predictable for local development. A future version could add explicit AAAA/IPv6 overrides if that becomes important.

