# Duckeighty Doctor

> Diagnose whether the duckeighty local HTTPS ingress is correctly set up on this host. Checks the shared_ingress Docker network, traefik and dnsmasq containers, the macOS /etc/resolver entry, local CA trust, DNS resolution for *.duckeighty.test, and HTTPS reachability of traefik. Produces a checklist and the one command needed to fix whatever failed. Does not mutate any state. TRIGGER: the user reports that a *.duckeighty.test URL returns 404, NXDOMAIN, a TLS error, or connection refused; asks to check whether duckeighty is running; asks to debug the ingress; or is about to use the duckeighty-integrate skill and wants to confirm the stack is up first. SKIP: issues unrelated to the ingress path (e.g. the user is debugging application logic, not routing / DNS / TLS).

- Skill: `mackee/duckeighty-doctor` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mackee/duckeighty-doctor`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mackee/duckeighty-doctor/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- License: MIT
- Author: mackee (https://skillmd.com/u/mackee)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mackee/duckeighty-doctor

---


# duckeighty: doctor

Run the checks below in order. For each, run the exact command, judge the
result, and note the single fix command if it fails. At the end, produce
a compact table (check / status / fix) and point the user to the single
next action.

This skill only diagnoses. Do not run `setup-ca`, `setup-resolver`, or
`up` from here — direct the user to the duckeighty-init skill or the manual command.

When a fix command is `./duckeighty.sh …`, the user needs to run it from
their duckeighty checkout. Resolve the path in this order, no guessing:

1. `$DUCKEIGHTY_CHECKOUT` env var if set.
2. If the ingress is running, ask Docker directly:

   ```
   docker compose ls --format json \
     | python3 -c "import json,sys,os; p=next((x for x in json.load(sys.stdin) if x['Name']=='duckeighty'),None); print(os.path.dirname(os.path.dirname(p['ConfigFiles'])) if p else '')"
   ```

   This returns the checkout root (parent of `infra/compose.yaml`) when
   the `duckeighty` project is running.
3. Otherwise ask the user for the path.

If the user has no checkout at all, tell them to run the duckeighty-init skill
first instead of printing a fix command.

## 1. Docker is usable

```
docker version --format '{{.Server.Version}}'
```

Fails → "Start Docker Desktop (or your Docker daemon) and re-run."

## 2. `shared_ingress` network exists

```
docker network inspect shared_ingress >/dev/null 2>&1 && echo ok || echo missing
```

`missing` → "Run `./duckeighty.sh up` from the duckeighty checkout. It
creates the network before `compose up`."

## 3. `duckeighty` compose project is up

```
docker compose ls --format json | python3 -c "import json,sys; print(next((p['Status'] for p in json.load(sys.stdin) if p['Name']=='duckeighty'),'missing'))"
```

Expect something like `running(2)`. `missing` → `./duckeighty.sh up`.

## 4. Both ingress containers exist and are on `shared_ingress`

```
docker ps --filter 'label=com.docker.compose.project=duckeighty' --format '{{.Names}}\t{{.Networks}}\t{{.Status}}'
```

Expect two rows: `duckeighty-traefik-1` and `duckeighty-dnsmasq-1`, both
Up. The traefik row's Networks column must contain `shared_ingress`; if
not, the ingress compose diverged from upstream.

## 5. macOS resolver entry

```
test -f /etc/resolver/duckeighty.test && cat /etc/resolver/duckeighty.test
```

Expect:

```
nameserver 127.0.0.1
port 5354
```

Missing or wrong → "Run `./duckeighty.sh setup-resolver` (sudo required,
one-time)."

## 6. DNS actually answers

```
dig +short @127.0.0.1 -p 5354 probe.duckeighty.test
```

Expect `127.0.0.1`. Empty or timeout → dnsmasq is not listening on
5354/udp on 127.0.0.1. Check `docker logs <dnsmasq container>`; confirm
the published ports in `infra/compose.yaml` are intact.

## 7. Local CA is trusted

```
mkcert -CAROOT
ls "$(mkcert -CAROOT)/rootCA.pem" 2>/dev/null
```

Both must succeed. Missing → `./duckeighty.sh setup-ca`.
If `mkcert` itself is missing, ask the user to install it:
`brew install mkcert nss`.

## 8. HTTPS path reaches Traefik

```
curl -sI https://no-such-app.duckeighty.test/ | head -1
```

A `HTTP/2 404` response from Traefik is the happy path: DNS + TLS both
work, and the hostname just has no matching router yet. Other failures:

- `curl: (6) Could not resolve host` → step 5 or 6 failed
- TLS / certificate errors → step 7 failed, or the user did not restart
  the browser / shell after installing the CA
- `curl: (7) Failed to connect` → step 3 failed, or ports 80/443 are
  occupied by another process

## Report format

```
check                          status    fix
1. docker daemon               OK        —
2. shared_ingress network      OK        —
3. duckeighty compose project  OK        —
4. traefik + dnsmasq on net    OK        —
5. /etc/resolver/…             MISSING   ./duckeighty.sh setup-resolver
6. DNS resolution              FAIL      (after step 5)
7. mkcert local CA             OK        —
8. HTTPS reaches traefik       FAIL      (after step 5)
```

End with the single first command the user should run. Do not dump all
per-step fixes when one upstream fix unblocks the rest.

