Local macOS Dev Setup: dnsmasq + caddy-snake for python projects
When working on a single web project, running flask run on a fixed port is
usually more than sufficient. However, as soon as you start developing
multiple services in parallel, this approach quickly becomes cumbersome:
ports collide, you have to remember which service runs on which port, and you
end up constantly starting, stopping, and restarting individual development
servers by hand.
Using a wildcard local domain (*.lan) throuhg dnsmask and a vhost proxy with
proper WSGI services solves these problems cleanly. Each project gets a stable,
memorable local subdomain instead of a port number, services can run side by
side without collisions, and process management becomes centralized and
predictable. The result is a local development setup with less friction.
dnsmasq on macOS Sonoma (Local DNS with .lan)
This is a concise summary of how to install and configure dnsmasq on macOS
Sonoma to resolve local development domains using a .lan wildcard (e.g.
*.lan → 127.0.0.1).
1. Install dnsmasq
Using Homebrew:
brew install dnsmasq
Homebrew (on Apple Silicon) installs dnsmasq and places the default config in:
/opt/homebrew/etc/dnsmasq.conf
2. Configure dnsmasq
Edit the configuration file:
sudo vim /opt/homebrew/etc/dnsmasq.conf
Add the following:
# Listen only on localhost
listen-address=127.0.0.1
bind-interfaces
# DNS port
port=53
# Wildcard domain for local development
address=/.lan/127.0.0.1
This maps any *.lan hostname to 127.0.0.1.
It's recommended to not use .dev as this is real Google owned TLD and browsers
have baked in to use HTTPS only. Also don't use .local as this is reserved for
mDNS (Bonjour).
3. Tell macOS to use dnsmasq
macOS ignores /etc/resolv.conf, so DNS must be configured per network interface.
Option A: System Settings (GUI)
- System Settings → Network
- Select your active interface (Wi-Fi / Ethernet)
- Details → DNS
- Add:
127.0.0.1
- Move it to the top of the DNS server list
Option B: Command line
networksetup -setdnsservers Wi-Fi 127.0.0.1
(Replace Wi-Fi with the correct interface name if needed.)
4. Start dnsmasq
Run it as a background service:
sudo brew services start dnsmasq
Or run it manually for debugging:
sudo dnsmasq --no-daemon
5. Flush DNS cache
This step is required on Sonoma:
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
6. Test
dig foo.lan
ping foo.lan
Both should resolve to:
127.0.0.1
Result
You now have:
- dnsmasq running on
127.0.0.1 - Wildcard local DNS via
*.lan - Fully compatible behavior with macOS Sonoma
Caddy
Note: My initial plan was to use caddy with caddy-snake to run multiple
vhosts for python wsgi apps with the configuration below. But this did
not work out as expected because caddy-snake does not run multiple python
interpreters for the different projects, but only appends the site packages
from the python projects to sys.path for all projects and runs all of them in
the same python interpreter. This leads to problems with different python
versions or incompatible python requirements installed in the different venv
versions. So the approach below only works if you use the same python version
and your requirements are compatible within the different apps.
Caddyfile: host multiple WSGI services
As we want caddy to run wsgi services we need to build caddy-snake:
The caddyfile now needs to be stored in $(brew --prefix)/etc/Caddyfile
{
auto_https off
}
http://foo.lan {
bind 127.0.0.1
route {
python {
module_wsgi app:app
working_dir /Users/you/dev/foo
venv /Users/you/dev/foo/.venv
}
}
log {
output stdout
format console
}
}
http://bar.lan {
bind 127.0.0.1
route {
python {
module_wsgi app:app
working_dir /Users/you/dev/bar
venv /Users/you/dev/bar/.venv
}
}
}
