DNS
Configure local DNS for your development services
Stackpanel can configure local DNS so that your development services are reachable at readable hostnames instead of localhost:<port>. Instead of remembering that your API is on port 4201, you can access it at api.myapp.local.
Overview
Local DNS works in conjunction with the Caddy reverse proxy and optionally Step CA certificates. The flow looks like this:
Browser → api.myapp.local
→ DNS resolves to 127.0.0.1
→ Caddy receives the request
→ Caddy proxies to localhost:4201 (your API's deterministic port)This gives you production-like URLs during development, which is especially useful when working with:
- CORS — matching origins between frontend and backend
- Cookies — domain-scoped cookies that work across services
- OAuth callbacks — redirect URLs that look like real domains
- Multi-service architectures — clear, memorable addresses for each service
Configuration
DNS settings are configured under stackpanel.dns:
stackpanel.dns = {
enable = true;
domain = "myapp.local";
entries = {
web = {
host = "myapp.local";
port = config.stackpanel.ports.computed.web;
};
api = {
host = "api.myapp.local";
port = config.stackpanel.ports.computed.api;
};
minio = {
host = "s3.myapp.local";
port = config.stackpanel.ports.computed.minio;
};
};
};Each entry maps a hostname to a local port. Stackpanel configures Caddy to proxy these hostnames to the correct ports.
How DNS Resolution Works
For local DNS to work, your machine needs to resolve *.myapp.local to 127.0.0.1. There are a few ways to set this up:
/etc/hosts (Simplest)
Add entries manually to your hosts file:
127.0.0.1 myapp.local
127.0.0.1 api.myapp.local
127.0.0.1 s3.myapp.localThis is straightforward but requires updating the file every time you add a new subdomain.
dnsmasq (Wildcard Support)
For wildcard resolution (so that any subdomain of myapp.local resolves locally), use dnsmasq:
# macOS with Homebrew
brew install dnsmasq
# Add a rule for your local domain
echo "address=/myapp.local/127.0.0.1" >> /opt/homebrew/etc/dnsmasq.conf
# Restart dnsmasq
sudo brew services restart dnsmasq
# Tell macOS to use dnsmasq for .local domains
sudo mkdir -p /etc/resolver
echo "nameserver 127.0.0.1" | sudo tee /etc/resolver/localWith this setup, anything.myapp.local resolves to 127.0.0.1 automatically—no hosts file edits needed when you add new services.
On Linux, you can achieve similar wildcard resolution with systemd-resolved or NetworkManager dnsmasq integration. The exact setup varies by distribution.
Combining with Caddy
DNS entries work hand-in-hand with Stackpanel's Caddy integration. When both are configured, Caddy automatically gets reverse proxy rules for each DNS entry:
{
stackpanel.dns = {
enable = true;
entries.api = {
host = "api.myapp.local";
port = config.stackpanel.ports.computed.api;
};
};
# Caddy picks up DNS entries and creates proxy rules
stackpanel.caddy.enable = true;
}Caddy will proxy api.myapp.local → localhost:<api-port> without any additional Caddy configuration.
HTTPS with Local DNS
For HTTPS on local domains, combine DNS with Step CA certificates:
{
stackpanel.dns.enable = true;
stackpanel.caddy.enable = true;
stackpanel.step-ca.enable = true;
}With all three enabled, you get:
- DNS resolves
api.myapp.local→127.0.0.1 - Step CA issues a TLS certificate for
api.myapp.local - Caddy terminates TLS and proxies to your app
The result is https://api.myapp.local working in your browser with a trusted certificate—identical to production.
Per-App DNS
When you define apps in stackpanel.apps, DNS entries can be generated automatically:
stackpanel = {
dns.enable = true;
dns.domain = "myapp.local";
apps = {
web = {
port = 0; # auto-assigned
# Automatically gets web.myapp.local (or just myapp.local for the first app)
};
api = {
port = 1;
# Automatically gets api.myapp.local
};
};
};This keeps your DNS configuration in sync with your app definitions—no need to maintain both separately.
Environment Variables
When DNS is enabled, Stackpanel exports URL environment variables that use the configured hostnames instead of localhost:
| Variable | Without DNS | With DNS |
|---|---|---|
STACKPANEL_WEB_URL | http://localhost:4200 | http://myapp.local |
STACKPANEL_API_URL | http://localhost:4201 | http://api.myapp.local |
Your application code can reference these variables to construct URLs that match the actual hostname—useful for generating OAuth redirect URLs, API base URLs, and CORS allow-lists.
Troubleshooting
Hostname not resolving
Verify DNS resolution is working:
# Should show 127.0.0.1
dig +short myapp.local
# Or with nslookup
nslookup myapp.localIf it doesn't resolve, check your /etc/hosts or dnsmasq configuration.
"Connection refused" after DNS resolves
DNS is resolving correctly, but nothing is listening. Make sure Caddy is running:
stackpanel caddy statusAnd that your target service is up:
stackpanel services status.local domain conflicts on macOS
macOS uses mDNS (Bonjour) for .local domains by default, which can conflict with custom DNS. If you experience slow resolution or intermittent failures, either:
- Use a different TLD (e.g.,
.test,.internal) - Configure dnsmasq to handle
.localresolution before mDNS
The .local TLD is reserved for mDNS by RFC 6762. If you hit issues, .test is a safe alternative—it's reserved by IANA for testing purposes and will never conflict with real domains.
Reference
- Options Reference → DNS for all DNS configuration options
- Caddy for reverse proxy setup
- Certificates for HTTPS with local domains
- Deterministic Ports for how port assignments work