This document captures the practical steps needed to spin up the full stack locally, manage certificates, and configure browsers or operating-system DNS clients during development.
- Docker & Docker Compose
- Make
- mkcert (or an equivalent CA generation tool)
- dnsmasq (or another DNS forwarder that supports wildcard overrides)
- Access to
/etc/hostsand/etc/resolv.conf(Linux/macOS) or the Windows hosts file
-
Generate or trust the provided certificates (see the next section).
-
From the repository root, run:
make up
This builds containers, seeds databases, and exposes the web UI via Nginx at
https://ivpndns.com. -
Use
make logsto tail container output, andmake downto stop everything when you are done hacking.
- Create or reuse a local Certificate Authority (CA).
- Trust that CA in your OS (e.g.,
/usr/local/share/ca-certificates/on Ubuntu, Keychain Access on macOS). - Generate a wildcard certificate and sign it with your CA.
- Convert the resulting
.crt+.keyinto.pemfiles and place them incerts/.
./mkcert ivpndns.com "*.ivpndns.com" localhost 127.0.0.1 ::1mkcert automatically installs its root CA into the system trust store, so browsers accept https://ivpndns.com when the dev proxy serves it locally.
/etc/hosts cannot express wildcard records, so we rely on dnsmasq:
sudo systemctl disable systemd-resolved # disable Ubuntu's stub resolver
sudo systemctl enable dnsmasq.service
sudo systemctl start dnsmasq.service/etc/dnsmasq.conf snippet:
# Map every *.ivpndns.com host to localhost for HTTPS and DoT/DoQ tests
address=/ivpndns.com/127.0.0.1
cache-size=1000
Helpful /etc/hosts entries (in addition to dnsmasq):
# DNS check entry for local testing
127.0.0.1 123.test.localdnsleaktest.com
127.0.0.1 ivpndns.com
Tip
docker network inspect bridge reveals the "Gateway" IP. Export that value as API_ALLOW_IP. Set API_ALLOW_IP="*" to bypass IP-based access control while developing.
-
Point your browser or OS DNS setting to the local DoH endpoint:
https://ivpndns.com:443/dns-query/<profile-id> -
Import
certs/ivpndns.com+4.pem(or the certificate generated via mkcert) into the browser's trust store:- Chrome/Edge: Settings → Privacy and Security → Security → Manage certificates → Authorities
- Firefox: Settings → Privacy & Security → Certificates → View Certificates → Authorities
-
If you need DoT/DoQ validation, ensure your DNS client trusts the same certificate.
The API serves announcements by fetching a single Markdown file over HTTP from
ANNOUNCEMENTS_URL (in production this is the raw URL of the announcements
content branch). To exercise the feature locally without that branch, serve the
bundled dev fixture with any static file server.
A ready-made fixture lives at bootstrap/announcements/announcements.md. It
covers every category (news, feature, maintenance, incident, security,
policy) and severity (info, warning, critical), plus one expired and one
future entry to confirm the API hides them.
-
Put the dev URL in
api/.env(with a short reload for fast iteration):ANNOUNCEMENTS_URL=http://announcements-dev/announcements.md ANNOUNCEMENTS_RELOAD=10s[!IMPORTANT] The API reads
ANNOUNCEMENTS_URLfromenv_fileonly at container creation (there is no in-process.envloader). After editingapi/.envyou must recreate thednsapicontainer —make down && make up(ormake restart_dev) — not just restart the process.docker exec dnsapi printenv ANNOUNCEMENTS_URLshows the value the running API actually sees. -
With the stack up, serve the fixture (in its own terminal):
make announcements
This runs a throwaway nginx named
announcements-devon the shareddns_dnsnetwork, so the API reaches it by container DNS name athttp://announcements-dev/announcements.md. Because it lives on the network (not inside the API's namespace) it survivesdnsapirestarts. Editing the.mdafterwards is picked up within the reload interval — no restart needed. If you runmake down, the network is torn down too, so re-runmake announcementsafter the nextmake up. -
Open the web UI and visit
/announcements(reachable logged in or logged out). Verify each category renders with its badge and severity-coloured accent, and that the two(should be hidden)entries do not appear. -
The nav "Announcements" entry shows an unread dot: red when an unread announcement is
critical(the fixture'sdev-incident), brand-coloured otherwise. Opening the page marks everything seen and clears the dot; the last-seen timestamp is persisted under themoddns-storagekey inlocalStorage. Clear that key (DevTools → Application → Local Storage) or use a private window to re-test the dot.
Note
If you run the API on the host instead of in the dnsapi container, skip
make announcements and serve the fixture directly with
cd bootstrap/announcements && python3 -m http.server 8099, using
ANNOUNCEMENTS_URL=http://localhost:8099/announcements.md.
- TLS errors: confirm the CA is trusted and the certificate's SAN includes the host you're testing (
*.ivpndns.com). - Wildcard not resolving: restart dnsmasq after editing the config (
sudo systemctl reload dnsmasq). - API allow list failures: verify
API_ALLOW_IPmatches the docker bridge gateway or set it to*for local-only usage.
Keep this guide close when onboarding new contributors so the local environment stays reproducible.