# Duckeighty Integrate

> Generate a docker-compose.local.yaml override that wires an existing Docker Compose project into duckeighty's shared local HTTPS ingress, so each exposed service becomes reachable at https://<project>-<service>.duckeighty.test with a locally trusted certificate. TRIGGER: the project already has a compose.yaml / docker-compose.yml and the user asks to expose it over duckeighty, add a local override, route via traefik / shared_ingress, or set up *.duckeighty.test URLs. Also fires when the user says "duckeighty" and refers to a Compose app in the current working directory. SKIP: projects without Docker Compose, or projects whose existing docker-compose.local.yaml already attaches the target service(s) to shared_ingress with correct traefik labels.

- Skill: `mackee/duckeighty-integrate` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add mackee/duckeighty-integrate`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mackee/duckeighty-integrate/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-integrate

---


# duckeighty: add a docker-compose.local.yaml override

Goal: produce a `docker-compose.local.yaml` at the project root that, when
combined with the project's existing `compose.yaml`, routes selected services
through the duckeighty ingress stack.

## Reference files (bundled with this skill)

Read these from the skill's `references/` directory before generating
output:

- [references/single-service.template.yaml](references/single-service.template.yaml) — canonical single-service pattern
- [references/multi-service.example.yaml](references/multi-service.example.yaml) — pattern for multiple exposed services
- [references/conventions.md](references/conventions.md) — hostname rules, env var contract, normalization

## Hard rules (wrong rule → Traefik 404)

1. **Labels must be in list form** (`- "key=value"`), not a YAML mapping.
   Compose does not expand `${VAR}` inside mapping *keys*, so the
   `${DUCKEIGHTY_PROJECT_NAME}` token in the router/service name would stay
   literal and Traefik ends up with no matching router.
2. Include `traefik.docker.network=shared_ingress` so Traefik picks the
   correct network when the container is on multiple networks.
3. Router and service names must include the project token
   (`${DUCKEIGHTY_PROJECT_NAME}-<svc>`) so multiple worktrees do not collide
   at Traefik's global router registry.
4. Attach the service to both its own `default` network and `shared_ingress`.
5. Declare `shared_ingress` at the bottom as
   `external: true, name: shared_ingress`.
6. Do not publish host ports on the exposed services. Traefik handles
   ingress; host-published ports conflict between worktrees.

## Workflow

1. Read the existing `compose.yaml` / `docker-compose.yml` to identify the
   service(s) under `services:` the user wants to expose. Ask if ambiguous
   (e.g. "expose only `web`, or also `admin`?").
2. For each exposed service, determine the in-container listen port.
   The duckeighty default is `8080`. If the app listens on a different port,
   ask and set `loadbalancer.server.port` accordingly.
3. Read the single- or multi-service reference template depending on count.
4. Write `docker-compose.local.yaml` at the project root. One labeled block
   per exposed service, following all hard rules above.
5. Keep the override out of the target repo's tracked set. Default to the
   **non-invasive** path: add the filename to `.git/info/exclude` so it
   stays out of `git status` without modifying any tracked file:

   ```
   f=".git/info/exclude"
   test -f "$f" && ! grep -qxF docker-compose.local.yaml "$f" \
     && printf '\n# duckeighty (local, per-developer)\ndocker-compose.local.yaml\n' >> "$f"
   ```

   If the user signals they want the whole team on duckeighty, offer to
   append the line to the target's `.gitignore` instead (that change gets
   committed). Do not pick the `.gitignore` path by default.
6. Print the follow-up run command for the user (see
   [references/conventions.md](references/conventions.md)).

## Prerequisites to surface to the user

Before the override is useful, the duckeighty ingress stack must be running
on the host. The ingress is host-wide: one install per developer, shared by
every app repo.

- If the user has **never set up duckeighty on this host**, point them to
  the `duckeighty-init` skill (sibling of this one) — it clones duckeighty,
  installs the mkcert CA, the macOS resolver entry, and brings up traefik +
  dnsmasq.
- If the user **might have it set up but is unsure** (URL 404s, TLS error,
  NXDOMAIN), point them to the `duckeighty-doctor` skill first. It only
  diagnoses.
- If the user wants to do it manually, from a duckeighty checkout:

  ```
  ./duckeighty.sh setup-ca        # once: mkcert local CA
  ./duckeighty.sh setup-resolver  # once: /etc/resolver entry (macOS, sudo)
  ./duckeighty.sh up              # starts traefik + dnsmasq
  ```

Do not run any of those commands from inside the application repo itself.

## Do / Don't

- Do name the override `docker-compose.local.yaml`. Compose does not pick it
  up automatically; the user invokes
  `docker compose -f compose.yaml -f docker-compose.local.yaml up -d`.
- Do leave the existing `compose.yaml` untouched.
- Do not add TLS / certificate configuration in the override — Traefik
  terminates TLS at the ingress layer.
- Do not guess service names from the `compose.yaml` file name; use the keys
  under `services:`.
- If any exposed service also needs to **call another duckeighty hostname
  from inside the container** (OIDC discovery, webhooks, internal HTTPS),
  add the `extra_hosts` + mkcert CA bind-mount pattern to the caller. See
  [references/conventions.md](references/conventions.md) §
  "Container-to-container calls over `*.duckeighty.test`".
- If the service is a dev server (Vite / Next dev / webpack-dev-server),
  whitelist the duckeighty hostname in its allowed-hosts config. See
  [references/conventions.md](references/conventions.md) §
  "Dev-server allowed-hosts gotcha".

## devcontainer-based projects

If the target project is started through the `devcontainer` CLI (VS Code
Dev Containers) rather than plain `docker compose up`, two extra bits
need attention because the CLI owns the `docker compose` invocation.

### 1. Make the override visible to devcontainer CLI

The CLI only loads compose files listed in `dockerComposeFile` in
`.devcontainer/devcontainer.json`. The override has to be listed
explicitly:

```json
"dockerComposeFile": [
  "./docker-compose.yml",
  "./docker-compose.local.yaml"
]
```

`devcontainer.json` is usually tracked, so editing it pulls duckeighty
into the repo's pushed files. If the team doesn't all use duckeighty, or
the author wants zero duckeighty trace in tracked code, use the CLI's
`--override-config` flag against a **separate** gitignored
`devcontainer.local.json` instead of editing `devcontainer.json`:

```
devcontainer up --workspace-folder . \
  --override-config .devcontainer/devcontainer.local.json
```

`devcontainer.local.json` is a full devcontainer config (not merged —
`--override-config` replaces). Copy the tracked one and add the
`dockerComposeFile` array in the copy. Add `devcontainer.local.json`
and `docker-compose.local.yaml` to `.git/info/exclude` or `.gitignore`.

### 2. Call `update-ca-certificates` at start-up for CA trust

If the service inside the container calls other `*.duckeighty.test`
URLs (OIDC discovery, webhooks, etc. — see §"Container-to-container
calls over `*.duckeighty.test`" in
[references/conventions.md](references/conventions.md)), the mkcert CA
has to be registered at start-up. devcontainer's `postStartCommand` is
the right hook:

```json
"postStartCommand": "sudo update-ca-certificates && <existing command>"
```

Microsoft's `mcr.microsoft.com/devcontainers/base` images ship `sudo`
and `ca-certificates` preinstalled. Custom base images may need either
to be installed via a feature or to run the command without `sudo`.

