CoreDNS
Container restart loop — “zone is not a valid domain name”
Two distinct causes produce this identical error message.
Cause A: template plugin without an explicit zone
When template IN A { has no positional zone, CoreDNS falls back to the server-block key (e.g. clinic.local:53). The :53 suffix fails domain-name validation; CoreDNS strips the port before printing, leaving an empty trailing colon.
Fix — pass the zone explicitly:
clinic.local:53 {
template IN A clinic.local { # ← zone here
answer "{{ .Name }} 60 IN A ${HOST_IP}"
}
}
Cause B: space between . and :53
CoreDNS parses . and :53 as two separate tokens.
Fix — no space:
.:53 { # correct
forward . 8.8.8.8 1.1.1.1
}
Quick diagnostic:
docker compose -f infra/docker-compose.yml logs coredns --tail 20
grep -n '^\.' infra/coredns/Corefile # should print ".:53 {" exactly
Corefile is a directory, not a file
Cause: Docker’s bind-mount auto-creation. If the host path doesn’t exist when the container starts, Docker creates it as a directory (because the mount target has no trailing /). CoreDNS mounts an empty folder as its config and crashes.
docker compose -f infra/docker-compose.yml down
rm -rf infra/coredns/Corefile
./scripts/install.sh # regenerates Corefile from template
Garbled parser errors / correct-looking values still rejected
Cause: CRLF line endings in Corefile.template (from Windows editors). After envsubst, the trailing \r becomes part of the zone name.
# Check for CRLF
file infra/coredns/Corefile # should say "ASCII text" not "CRLF"
od -c infra/coredns/Corefile | head # \n only, no \r\n
# Strip CR and regenerate
set -a; source infra/.env; set +a
envsubst < infra/coredns/Corefile.template | tr -d '\r' > infra/coredns/Corefile
docker compose -f infra/docker-compose.yml restart coredns
Verifying DNS works
# From the host
dig @127.0.0.1 console.clinic.local +short
# should print the HOST_IP
# From another LAN device (after pointing DNS at host)
nslookup console.clinic.local
install.sh
Postgres refuses connections after re-running install
Cause: the installer generates a new POSTGRES_PASSWORD on every run. The postgres-data volume preserves the original password baked into the data directory. Postgres ignores the new env var — auth mismatch causes connection failures.
Fix options:
# Option 1 — throw away data (correct in dev/staging)
docker compose -f infra/docker-compose.yml down -v
./scripts/install.sh
# Option 2 — preserve data
# Keep infra/.env intact, don't re-run install
docker compose -f infra/docker-compose.yml up -d
install.sh exits with parse errors on Linux
Cause: CRLF line endings from Windows editing.
sed -i 's/\r$//' scripts/install.sh
# or
dos2unix scripts/install.sh
/etc/hosts already has org.local — skipping on stale entry
Cause: the install script’s check is a coarse grep — any pre-existing line matching the org name blocks the new entry.
sudo sed -i "/${ORG_NAME}\.local/d" /etc/hosts
./scripts/install.sh
Docker / Compose
pull access denied for nublestation/console followed by a successful build
Not fatal. Compose tried to pull the named image, fell back to building locally. The pull_policy: never directive in infra/docker-compose.yml eliminates the noise — this is already set.
Console build fails with "/repo/apps/console/public": not found
Cause: apps/console/public/ is empty — git doesn’t track empty directories, so it doesn’t exist on a fresh clone. The Dockerfile’s COPY ... public fails.
Fix: apps/console/public/.gitkeep must be committed. Check that it exists:
ls apps/console/public/.gitkeep
If missing, create and commit it:
touch apps/console/public/.gitkeep
git add apps/console/public/.gitkeep
git commit -m "fix: track empty public directory"
WSL2 (development only)
⚠ Caution
WSL2 is fine for editing and running tests but is not reliable for LAN behavior testing.
hostname -Ireturns the WSL VM’s NAT’d IP (172.x), not the Windows host’s LAN IP. The installer works around this by calling PowerShell, but WSL is not on your LAN.- Port 53/UDP through Docker Desktop on Windows is unreliable — other LAN devices often can’t reach CoreDNS even with the correct IP configured.
/etc/hostsedits inside WSL only affect the WSL VM. To reachconsole.clinic.localfrom a Windows browser, also editC:\Windows\System32\drivers\etc\hosts.
For real LAN testing: use a native Linux host — a VM with bridged networking or bare metal. The staging Ubuntu VM is the correct environment.
General diagnostic commands
# Status of all containers
docker compose -f infra/docker-compose.yml ps
# Logs for a specific service
docker compose -f infra/docker-compose.yml logs <service> --follow
# Restart a single service
docker compose -f infra/docker-compose.yml restart <service>
# Full restart (preserves volumes)
docker compose -f infra/docker-compose.yml down && docker compose -f infra/docker-compose.yml up -d
# Nuclear restart (deletes all data)
docker compose -f infra/docker-compose.yml down -v && docker compose -f infra/docker-compose.yml up -d