Local dev with multiple branches should not require port math.
dnsvard gives each worktree a stable hostname on normal ports (80, 3306, 5432, etc.) so you can run master + feature branches in parallel without rewriting Compose files or juggling localhost:3xxx.
# before (conflicts)
localhost:3000 localhost:3001 localhost:3307 localhost:5433 ...
# after (clean)
master.myapp.test
feat-auth.myapp.testInstall + bootstrap:
curl -fsSL https://dnsvard.com/install | sh
sudo dnsvard bootstrap
dnsvard doctorIf dnsvard is not found right after install:
export PATH="$HOME/.local/bin:$PATH"Enable shell tab completion (auto-detects your shell):
dnsvard completion installCheck completion status:
dnsvard completion statusFor Bash login shells, if completion is configured in ~/.bashrc, ensure ~/.bash_profile sources it:
if [[ -f ~/.bashrc ]]; then
source ~/.bashrc
fiLinux bootstrap note (when prompted for elevated resolver setup):
sudo dnsvard bootstrap -f
dnsvard bootstrap -fStart your app stack as usual:
docker compose up -dHit branch/worktree hosts directly (same app/db ports, no host-port rewrites):
curl http://master.myproj.test
curl http://feat-auth.myproj.testIf macOS prompts for Local Network access, click Allow.
Local multi-worktree dev breaks down fast:
- branch A uses
localhost:3000, branch B also needs3000 - branch A MySQL is on
3306, branch B wants3306too - branch A Postgres is on
5432, branch B wants5432too - every compose stack needs custom host port rewrites
dnsvard removes that class of problem by routing by hostname/worktree identity, not by fragile host port juggling.
dnsvard is not only a web dev proxy.
- It routes DNS + HTTP + TCP (not only browser traffic)
- It works for databases/caches (
psql, Redis, etc.) - It is local infrastructure plumbing, not a cloud deploy platform
- It is branch/worktree aware from your repo context
- macOS + Linux
- IPv4 only
- Docker-first discovery
- DNS + HTTP + TCP workspace routing
Linux resolver backend status for v0.1.0:
systemd-resolved: supported, primary tested pathdnsmasq: supported, lower field coverage (treat as preview hardening path)
Current priority is Linux hardening and broader distro validation.
Linux targets:
- Debian/Ubuntu family
- Fedora family
- Arch family
- Pop!_OS
Implementation will detect capabilities (resolver stack, service manager, networking tools), not hardcode distro assumptions.
v0.1.x compatibility policy:
- no intentional breaking changes to CLI commands, config keys, or hostname model
Homebrew (macOS):
brew install --cask comment-slayer/tap/dnsvardIf macOS blocks first launch with a message like "dnsvard" Not Opened:
- Open System Settings -> Privacy & Security
- Scroll to the bottom and click Open Anyway for
dnsvard - Run
dnsvardagain
Release installer (checksum verified):
curl -fsSL https://dnsvard.com/install | shVersion-pinned installer:
curl -fsSL https://dnsvard.com/v0.1.0/install | shSecurity notes:
- Installer verifies release checksum
- Installer default source policy allows only
downloads.dnsvard.com - Default install path:
~/.local/bin(INSTALL_DIRoverrides)
dnsvard bootstrap
dnsvard doctor
docker compose up -dLinux note: resolver setup needs root, daemon runs as user. If prompted, run a root pass first, then rerun as normal user.
sudo dnsvard bootstrap -f
dnsvard bootstrap -fCurrent Linux resolver limitation: when backend is systemd-resolved, use dns_listen: 127.0.0.1:53.
dnsmasq supports non-53 forwarding, but has less production mileage in this release line.
DNS behavior inside managed domains:
- Unknown names under configured domains intentionally return loopback answers instead of NXDOMAIN.
- Reason: if an early lookup returns NXDOMAIN while a service is still starting, OS/app resolvers can negatively cache that result, so immediate retries still fail even after routes become ready.
- Outside configured domains, dnsvard does not answer.
Now hit your service by hostname instead of custom localhost ports.
dnsvard heals issues by component and always reports an explicit repair path.
- restart/start safety:
dnsvard daemon restartverifies the daemon is running before reporting success - action-level healing: each heal action tracks failure count, last failure detail, and suppression window (
blocked_until) - targeted recovery: persistent upstream unreachable events trigger targeted HTTP router reset without daemon-wide restart
- deterministic diagnostics: each degraded action reports a stable status code, component, and fix list
Use these commands to inspect reliability state:
dnsvard daemon status --verbose
dnsvard doctor
dnsvard doctor --jsonWhen an issue is present, outputs include:
code: stable machine-readable failure codecomponent: failing subsystemmessage: why this is failing nowfixes: one or more immediate repair steps
Two branches, same app port, no conflicts:
curl http://master.myproj.test
curl http://feat-auth.myproj.testTwo branches, same Postgres port (5432), still isolated:
psql -h master.myproj.test -d app
psql -h feat-auth.myproj.test -d appFast workspace cleanup with zero docker-name hunting:
dnsvard ps
dnsvard rm -f workspace/myproj/feat-auth
dnsvard rm -f workspace/myprojDefault host pattern: service-workspace-project-tld
- workspace host: same pattern without
service - project host:
<project>.<suffix>points to default workspace
Examples:
api.master.comment-slayer.testmaster.comment-slayer.testcomment-slayer.test
If you set workspace-tld, service-specific hosts are disabled (no <service>. hostname).
dnsvard [-c config] bootstrap [--force|-f] [--quick]
dnsvard [-c config] uninstall [--remove|--delete]
dnsvard [-c config] doctor [--flush-cache] [--check-local-network] [--probe-routing] [--json]
dnsvard [-c config] env [--shell]
dnsvard [-c config] config global <set|get|unset|show>
dnsvard [-c config] config local <set|get|unset|show>
dnsvard [-c config] daemon <start|stop|restart|status|logs|loopback-sync>
dnsvard [-c config] ps
dnsvard [-c config] stop <target>
dnsvard [-c config] kill <target>
dnsvard [-c config] rm <target> [--force|-f]
dnsvard [-c config] run [service] [--vite|--next|--nuxt|--astro|--svelte|--webpack|--adapter <name>] -- <cmd...>
dnsvard upgrade [--version <vX.Y.Z|latest>] [--allow-downgrade]
dnsvard version
dnsvard --versiondnsvard run supports explicit adapters and adapter auto-detection.
Recommended zero-config start:
dnsvard run -- bun devdnsvard will attempt to auto-detect framework adapter from command tokens and package.json scripts/dependencies.
If auto-detection is not possible for likely frontend dev commands, dnsvard fails fast with explicit adapter guidance.
Explicit adapter examples:
dnsvard run --vite -- bun dev
dnsvard run --next -- npm run dev
dnsvard run --adapter svelte -- bun devAdapters currently supported: vite, next, nuxt, astro, svelte, webpack.
Port selection for dnsvard run:
- If
DNSVARD_HTTP_PORTis set, dnsvard requires that exact port. - If that port is already in use, command fails with an explicit error.
- If
DNSVARD_HTTP_PORTis not set, dnsvard auto-allocates a free local port.
dnsvard upgrade is for installer-based installs (curl ... | sh) and is disabled for Homebrew installs.
Examples:
dnsvard upgrade
dnsvard upgrade --version v0.1.0
dnsvard upgrade --allow-downgradeSecurity behavior:
dnsvard upgradeenforces default host allowlists for installer and release sources--version latestfails closed if it resolves to an older version than current unless--allow-downgradeis set
If installed via Homebrew, use:
brew upgrade --cask comment-slayer/tap/dnsvardLoad order (low to high precedence):
~/.config/dnsvard/config.yaml./dnsvard.yaml-c/--config <path>DNSVARD_*environment variables
Global config (~/.config/dnsvard/config.yaml):
suffix: test
host_pattern: service-workspace-project-tld
loopback_cidr: 127.90.0.0/16
dns_listen: 127.0.0.1:1053
dns_ttl: 5
http_port: 80
state_dir: ~/.local/state/dnsvard
log_level: info
docker_discovery_mode: requiredRepo-local config (./dnsvard.yaml):
suffix: test
host_pattern: service-workspace-project-tld- Effective
suffixcomes from the highest-precedence config source (global, local,-c, env). - Effective
host_patterncomes from the highest-precedence config source (global, local,-c, env). - If
suffixis unset everywhere, dnsvard defaults totest. suffixesis not supported in config.
Set config with CLI:
dnsvard config global set suffix test
dnsvard config global set host_pattern workspace-project-tld
dnsvard config global set dns_ttl 15
dnsvard config local set host_pattern workspace-tld
dnsvard config global show
dnsvard config local show
dnsvard config global
dnsvard config localMulti-label DNS suffixes are supported (examples: dev.test, foo.bar.test).
dnsvard rejects *.local suffixes because .local is reserved for mDNS/Bonjour and conflicts with unicast resolver routing.
Workload operations targets:
ps,stop,kill:lease/<id>,container/<name-or-id>,workspace[/<project>[/<workspace>[/<container>]]],all(requires--yesforstop/kill)rm:container/<name-or-id>,workspace[/<project>[/<workspace>[/<container>]]],all(requires--yes)
docker_discovery_mode values:
required(default): Docker discovery failures are fatal.optional: keep workspace/runtime-only routes when Docker discovery fails.
Service names are auto-discovered from Compose metadata.
Use labels only when you need aliases or explicit HTTP behavior:
labels:
dnsvard.service_names: "frontend,ui"
dnsvard.http_port: "3000"
dnsvard.default_http: "true"
dnsvard.detect: "manual"Prefer 127.0.0.1 for host-native dev servers (for example dnsvard run -- bun dev).
127.0.0.1keeps the service local to your machine.- It avoids accidentally exposing dev services on your LAN.
- It matches dnsvard's local-first routing model.
Use 0.0.0.0 only when you intentionally need non-local access (for example another device on your network).
Resolver setup is a privileged operation on both macOS and Linux. dnsvard keeps long-running daemon processes as your normal user, and auto-heals drift in the background. Only privileged resolver/root-helper setup needs a root pass:
sudo dnsvard bootstrap -f
dnsvard bootstrap -fThis is intentional. Inside managed suffixes (for example *.test), returning NXDOMAIN can get negatively cached by your OS/app resolver. The race is:
- service is still starting,
- first DNS lookup gets NXDOMAIN,
- resolver cache keeps NXDOMAIN briefly,
- service becomes ready,
- next request still fails until negative cache expires.
By returning loopback answers in managed suffixes, dnsvard avoids that negative-cache race so retries can succeed as soon as routes become ready.
Usually no. Auto-heal should cover normal drift. Use it when you intentionally want a full privileged reconcile.
Ports below 1024 are privileged. dnsvard will guide you through capability/root setup during bootstrap. If you do not need 80, choose an unprivileged port (>=1024).
If dnsvard run -- bun dev cannot auto-detect framework adapter:
dnsvard run --adapter vite -- bun devIf hostnames under *.test do not resolve, check dnsvard DNS directly:
dig +short @127.0.0.1 -p 1053 <name>.testIf DNS record exists in dnsvard but your shell/app still fails, check system resolver cache:
dscacheutil -q host -a name <name>.testIf behavior looks off, give background auto-heal a moment first, then inspect status:
dnsvard doctorIf doctor reports blocked privileged resolver/root-helper setup (macOS or Linux):
sudo dnsvard bootstrap -fIf Docker discovery blocks route build and you want dnsvard to stay usable without Docker:
DNSVARD_DOCKER_DISCOVERY_MODE=optional dnsvard doctorTo persist this behavior, set docker_discovery_mode: optional in ~/.config/dnsvard/config.yaml.
go test ./...Run a development daemon without colliding with your installed dnsvard:
DNSVARD_STATE_DIR="$HOME/.local/state/dnsvard-dev" \
DNSVARD_DNS_LISTEN="127.0.0.1:1153" \
DNSVARD_HTTP_PORT=18080 \
go run ./cmd/dnsvard daemon start --foregroundUse a separate test suffix for dev runs:
DNSVARD_SUFFIX=dev.test go run ./cmd/dnsvard doctorAdapter integration harness (real framework projects):
make test-adaptersRun subset:
DNSVARD_ADAPTERS=vite,next scripts/test-dev-adapters.shLinux bootstrap harness (dry-run by default):
make test-linuxBackend matrix (auto-detect or explicit):
DNSVARD_LINUX_BACKENDS=systemd-resolved,dnsmasq make test-linuxRun full Linux two-pass bootstrap check:
DNSVARD_LINUX_APPLY=1 scripts/test-linux-bootstrap.shReleases are cut from GitHub Actions when pushing a tag matching v*.
git tag v0.1.0
git push origin v0.1.0examples/http-api-postgres/README.mddocs/how-dnsvard-routing-works.mddocs/release-checklist-v0.1.0.md
LICENSE(Apache-2.0)NOTICECONTRIBUTING.mdCODE_OF_CONDUCT.mdSECURITY.mdSUPPORT.md