| name | traefik-integrate-project |
| description | Use when a developer asks to wire a dockerized project into a local Traefik reverse-proxy hub, route a service at a friendly *.localhost hostname, add a project to the proxy/Traefik dev hub, or set up Traefik for a docker-compose app. Symptoms include the project having a docker-compose.yml without traefik.mk or docker-compose.traefik.yml, and the developer wanting URLs like http://myapp.localhost instead of localhost:3000. |
Wire a project into the local Traefik hub
Overview
Operator-mode integration: detect the project's compose layout, run the
hub's installer, patch the override to the actual service, verify
preconditions, and bring it up. Designed for the hub at
github.com/mechemsi/traefik_local
(HTTP-only — no TLS, no cert setup).
REQUIRED BACKGROUND: Read _shared/SNIPPET-CONTRACT.md in this
skill's parent directory before starting. It documents the
container-name trap, the variable contract, and the v3 label gotchas
the workflow below assumes you understand.
When to use
Triggers (any of):
- "wire this project into Traefik / the hub / the proxy"
- "make this routable at
<name>.localhost"
- "add this app to the local Traefik"
- Project has a
docker-compose.yml but no traefik.mk and the
developer wants a friendly hostname.
When NOT to use:
- Production / non-local Traefik. This skill targets a local HTTP-only
dev hub for
*.localhost.
- Modifying the hub itself (adding middleware, debugging routes,
resetting dashboard auth) → use
traefik-hub-maintain instead.
The workflow
1. Pre-flight (locate hub, read base compose, detect values)
2. Confirm plan with developer (one prompt, coarse)
3. Install snippets (run installer)
4. Patch override (rename service, set hostname strategy)
5. Verify preconditions (proxy network, hub state)
6. make up + verify routing
1. Pre-flight detection
Locate the hub repo. Default ~/traefik; if absent, ask the developer
for the path. Confirm by checking for snippets/traefik.mk,
snippets/docker-compose.traefik.yml, and
snippets/docker-compose.fallback.yml.
Read the consumer's base docker-compose.yml. Extract:
- Service name — the top-level key under
services:. If multiple
services, ask which one is the web-facing target. (Multi-service is a
separate flow — see _shared/SNIPPET-CONTRACT.md "Multi-service
consumers" and write the overlays by hand.)
- Container port — what the service listens on inside the
container. Inferred from the right side of
ports: ["X:Y"] (Y), the
service's EXPOSE if no ports:, or asked if ambiguous. This is
APP_PORT.
- Host port — the left side of
ports: ["X:Y"] (X). This is
APP_HOST_PORT. Set explicitly only when it differs from APP_PORT.
- Has
ports: block? — if yes, it must be removed from the base
compose in step 4 (port publishing moves to
docker-compose.fallback.yml). Capture host:container first so
APP_HOST_PORT/APP_PORT are right; then delete.
container_name: set? — determines hostname strategy below.
Compute:
APP_NAME = the project directory's basename ($(basename $PWD)).
Never the compose service name unless the developer explicitly
asks. Service names are often generic (web, app, api); the
router/service name in Traefik labels should identify the project.
APP_HOST = ${APP_NAME}.localhost.
2. Confirm plan
Print one consolidated plan and ask once before mutating files:
About to:
- run <hub>/scripts/install.sh <pwd> with APP_NAME=<x> APP_PORT=<y>
- set APP_HOST_PORT=<n> in the Makefile (only if host port ≠ container port)
- remove `ports:` from base docker-compose.yml (only if present)
- rename `app:` → `<service>` in docker-compose.traefik.yml
- rename `app:` → `<service>` in docker-compose.fallback.yml
- set Host() rule on the routed overlay (because container_name not set in base)
- ensure docker network `proxy` exists
- run `make up` and verify http://<host> responds
Proceed?
Don't ask per-step. Coarse confirmation is the design.
Surface two items in the plan whenever step 1 detected them — they're
the silent-failure modes:
- The
ports: removal from base compose (destructive edit on the
developer's file; they should see it before you do it).
APP_HOST_PORT whenever the host port differs from the container
port — the variable most likely to be wrong silently in fallback
mode.
3. Install snippets
Prefer the local clone (idempotent + offline-safe):
HUB=/path/to/traefik_local
APP_NAME=<name> APP_PORT=<container-port> "$HUB/scripts/install.sh" "$PWD"
The installer copies traefik.mk, docker-compose.traefik.yml, and
docker-compose.fallback.yml, creates or appends to Makefile with
the include block, and prompts before overwriting existing snippet
files. It auto-detects local vs remote source.
If the consumer has an existing Makefile, the installer detects an
existing include traefik.mk and skips the Makefile change. If
variables (APP_NAME, APP_HOST_PORT) need to be set, append them
above the include line yourself — installer doesn't manage them.
The installer is intentionally dumb about your service name. It
copies both overlays verbatim from the template, each with app: as
the service key. It does not know what your real service is called
and will not rename it. That's step 4 below — your job, not the
installer's.
4. Patch base compose + both overlays
a. Remove ports: from base docker-compose.yml. If the
web-facing service has a ports: block, delete it. Port publishing
now lives in docker-compose.fallback.yml. Skipping this leaves a
host-port publish in both modes (compose appends override ports:
to base, doesn't replace) — defeats the routed/fallback split and
risks port collisions when the hub is up. See
_shared/SNIPPET-CONTRACT.md "Ports policy" for the rationale.
b. Edit docker-compose.traefik.yml (routed overlay):
- Rename
app: → actual service name from the base compose.
This is the #1 silent failure: if you forget, compose merges
app: as a new (label-less) service and your real service stays
unrouted. Verify after with docker compose config — the merged
output should show your service with all the Traefik labels
attached.
- Hostname strategy (see
_shared/SNIPPET-CONTRACT.md for the
full container-name trap):
c. Edit docker-compose.fallback.yml (fallback overlay):
- Rename
app: → actual service name — same rename as the
routed overlay, same silent-failure mode if forgotten (a phantom
app: service gets the published port and your real service
stays unpublished).
d. Set APP_HOST_PORT in the consumer Makefile if it differs from
APP_PORT. Otherwise omit — defaults to APP_PORT.
5. Verify preconditions
docker network inspect proxy >/dev/null 2>&1 \
|| (cd "$HUB" && make network)
docker inspect -f '{{.State.Running}}' traefik 2>/dev/null
If the hub is stopped and the developer wants routed mode, point them
at cd "$HUB" && make up. Don't run make up in the hub repo without
asking — it's their hub.
6. Bring it up + verify
make up
make traefik-info
Expected traefik-info output in routed mode:
Traefik: running
Mode: routed via Traefik
Access: http://<APP_HOST>
Verify routing reaches the app:
curl -sS -o /dev/null -w '%{http_code}\n' http://${APP_HOST}
If the developer wants to sanity-check fallback mode (or the hub is
down), the URL is http://localhost:${APP_HOST_PORT} after make up.
A Connection refused there usually means the app: rename in
docker-compose.fallback.yml was forgotten — docker compose config
will show two services, only one with ports:.
Common mistakes
| Mistake | Symptom | Fix |
|---|
Forgot to rename app: in routed overlay | 404 at http://<host> after make up (hub running) | docker compose config shows two services, only one labeled. Edit docker-compose.traefik.yml. |
Forgot to rename app: in fallback overlay | Connection refused at http://localhost:<port> (hub down) | docker compose config shows two services, only one with ports:. Edit docker-compose.fallback.yml. |
Left ports: in base compose | Host port published in routed mode too; possible bind: address already in use if another consumer is also up | Delete ports: from base. Compose appends override ports: to base — there's no override-time removal. |
APP_PORT set to host port | 502 Bad Gateway from Traefik | APP_PORT is the in-container port. Swap to the right side of ports: ["X:Y"] (the original base mapping you captured in step 1). |
APP_NAME = compose service name (web, app) | Router collisions across projects | Use directory basename. Multiple consumers all routing app.localhost is the failure. |
Used defaultRule without container_name: | Routes at <project>-<service>-1.localhost | Add the explicit Host() rule (preferred) or set container_name: on base service. |
proxy network missing | network proxy not found at compose up (routed mode) | make network in hub repo, or docker network create proxy. |
Hub not running, expected http://<host> to work | Connection refused on <host>.localhost | Fallback URL is http://localhost:${APP_HOST_PORT}. Either start the hub or use the fallback URL. |
Ran docker compose up directly (no make) | App container runs but unreachable from host | Neither overlay applied. Use make up, or docker compose -f docker-compose.yml -f docker-compose.fallback.yml up. |
Browser auto-upgrades to https://<host> | ERR_SSL_PROTOCOL_ERROR or "connection refused" on :443 | Chrome/Edge cache HSTS for hosts they previously served over HTTPS. Clear at chrome://net-internals/#hsts ("Delete domain security policies"). |
| Multi-service project, used the single-service template | Some services unrouted, label conflicts | Skip traefik.mk's APP_NAME plumbing. Hand-write the overlays using map-form labels + YAML merge-keys (see _shared/SNIPPET-CONTRACT.md). |
| Edited labels on running container, expected hot-reload | Old routing persists | Compose doesn't push label updates. Run docker compose up -d to recreate the service. |
Quick reference
| Step | Command |
|---|
| Detect base compose | cat docker-compose.yml then read services + ports |
| Install | APP_NAME=<x> APP_PORT=<y> "$HUB/scripts/install.sh" "$PWD" |
| Verify merge (routed) | docker compose -f docker-compose.yml -f docker-compose.traefik.yml config |
| Verify merge (fallback) | docker compose -f docker-compose.yml -f docker-compose.fallback.yml config |
| Bring up | make up && make traefik-info |
| Verify routing (hub up) | curl http://${APP_HOST} |
| Verify routing (hub down) | curl http://localhost:${APP_HOST_PORT} |
| Network bootstrap | cd "$HUB" && make network |