Appearance
Domains, DNS and certificates
Three things have to line up before https://my-app.test opens: the name has to resolve, something has to answer on port 443, and the certificate it presents has to be one your browser trusts. dqx arranges all three, and they fail in different ways — so it is worth knowing which is which.
The two families of domain
dqx works with two kinds of local domain, and they resolve by completely different routes.
.test — resolved on your machine
.test is reserved by the IETF for exactly this: it will never be a real top-level domain, so nothing on the internet can ever claim it. That also means no public DNS server knows anything about it, and each machine has to answer for itself.
On macOS that is /etc/resolver/test:
nameserver 127.0.0.1
port 5354macOS reads /etc/resolver/<suffix> and sends every query for that suffix to the address in the file. Port 5354 is dqx's own DNS server — a process listening on 127.0.0.1:5354 that answers 127.0.0.1 for anything under a suffix it owns.
Deliberately not port 53, and deliberately not 5300: Lerd's dnsmasq listens on 5300 and Herd runs its own. Two servers fighting over one port is not a fallback, it is a coin toss.
Only on this machine
A colleague who clones the project and opens https://my-app.test gets nothing until dqx has set up their resolver too. That is not a bug — it is what "reserved for local use" means.
*.example.test under a real domain — resolved by public DNS
The other family is a wildcard on a domain the company actually owns, with a public DNS record pointing at 127.0.0.1:
bash
$ dig +short anything.local.example.eu @8.8.8.8
127.0.0.1That record is real, public, and answered by Google's resolver — for any subdomain, on any machine, with nothing installed. A colleague, a new laptop, a CI runner: they all resolve it, and they all get their own loopback address.
It costs one wildcard record in a domain you own. In exchange, a project's domains work on a machine where dqx has never run, and nobody has to be talked through editing /etc/hosts.
Which to use
.test needs no DNS record and works offline. A wildcard under your own domain needs no resolver on each machine. Most projects here use the wildcard for exactly that reason, and .test where a domain should be visibly local.
What answers on 443
Every project could publish its own ports, and then only one project could run at a time — 80 and 443 have a single owner per machine. So dqx runs one shared reverse proxy (Traefik) that owns both, and every project's web container sits behind it on an internal network, publishing nothing.
The proxy is told about a project through a small file per project, generated by dqx:
yaml
http:
routers:
my_app:
rule: Host(`my-app.test`) || Host(`app.my-app.local.example.eu`)
entryPoints: [websecure]
service: my_app
tls: {}
services:
my_app:
loadBalancer:
servers:
- url: "http://my_app_web:80"Every domain a site declares — primary and aliases — becomes part of that rule. A missing alias is the failure that looks like nothing: Traefik has no route for the host, so it answers its own bare 404 page not found, which mentions neither your project nor a domain.
Behind the proxy, the project's own nginx picks the site by server_name and serves its docroot. That is the second place a domain has to be listed, and dqx generates both from one declaration precisely so they cannot disagree.
The certificate
A local site over https:// needs a certificate your browser trusts. dqx uses mkcert, which creates a local certificate authority, installs it into your system trust store once, and then signs certificates with it.
- One certificate, many names. The shared proxy serves a single certificate whose Subject Alternative Names list every domain every project on the machine declares. On this machine that is 57 names in one certificate.
- It grows, never shrinks. dqx adds a project's domains when it is installed or started; it never removes another project's, because the command that adds yours only sees the directory it was run from. A certificate that shrank would take every other site down with a browser warning, and nothing would say why.
- The container trusts it too. The root certificate is copied into the app container, because a site calling another site over
https://fails inside the container while the same URL works in your browser — the browser was told about the CA, the container never was.
dqx doctor reports a certificate that does not cover a domain, and dqx up reissues it.
When it does not work
| symptom | what it means |
|---|---|
| Browser cannot resolve the host at all | DNS. For .test, the resolver file or dqx's DNS server; for a wildcard domain, the public record |
404 page not found, plain text, no styling | The proxy has no route for that host — usually an alias missing from the declaration |
| The site's own 404 or 500 | You reached the project. It is nginx or the application now |
| "Your connection is not private" | The certificate does not cover this domain, or the CA is not in your trust store |
Bind for 0.0.0.0:443 failed | Something else owns 443 — another backend, or a project stack that predates the shared proxy |
dqx doctor names each of these, and dqx verify asks every declared domain what it actually answers.