--- name: traefik-expose description: Make a service on the tachi host reachable at https://NAME.i.grosinger.net through Traefik, or remove it. Use when asked to expose, register, route, or publish a dev server, host process, or docker container on an internal hostname, or to unexpose/deregister one. --- > [!info] This skill only applies to the host `voyager` > This skill may be synced to other hosts, but it only applies to `voyager`. If you are running on a different host and need to expose a service to an internal or external hostname, stop and ask for guidance from the user. # traefik-expose — put a service on NAME.i.grosinger.net Traefik on this host (`/tachi/docker`) serves `*.i.grosinger.net` on its `internal` entrypoint. That entrypoint is the default and already carries the wildcard TLS certificate and the home-only IP allowlist, and Blocky resolves every `*.i.grosinger.net` name to this host. So registering a service is only a routing rule and a target; there is no DNS, certificate, or middleware step. Pick the branch by where the service runs. ## Host process (not in a container) Register with the script in the docker repo: ```sh /tachi/docker/traefik/expose NAME PORT # https://NAME.i.grosinger.net -> host:PORT /tachi/docker/traefik/expose -l # list /tachi/docker/traefik/expose -d NAME # remove ``` It writes one file to `traefik/dynamic.d/`, which Traefik watches, so the route is live within seconds and needs no restart. The file is gitignored. The process must listen on `0.0.0.0` or on `172.19.0.1`. Traefik connects from inside its container network and cannot reach a listener bound to `127.0.0.1`, which most dev servers default to; pass the tool's host flag (`--host 0.0.0.0` or equivalent). ## Docker container Join the container to the `docker_traefik` network and add labels. From a compose file outside `/tachi/docker`, declare the network as external: ```yaml services: myapp: networks: [traefik] labels: - "traefik.enable=true" - "traefik.http.routers.myapp.rule=Host(`myapp.i.grosinger.net`)" - "traefik.http.services.myapp.loadbalancer.server.port=3000" networks: traefik: external: true name: docker_traefik ``` Inside `/tachi/docker/docker-compose.yaml` the network is simply `traefik`. Traefik ignores containers without `traefik.enable=true`. Always set the port label: it is the port the app listens on inside the container, not a published one, and no `ports:` mapping is needed. Removing the labels or the container removes the route. ## Verify ```sh curl -sk -o /dev/null -w '%{http_code}\n' https://NAME.i.grosinger.net/ ``` - **200-ish**: done. - **502**: Traefik has the route but cannot reach the target. Host process: check the bind address with `ss -ltnp | grep PORT`. Container: check the port label matches what the app listens on and the container is on `docker_traefik`. - **404**: Traefik has no router for that host. Ask the dashboard API, which needs no docker socket: ```sh curl -sk https://traefik.i.grosinger.net/api/http/routers/NAME@file # host process curl -sk https://traefik.i.grosinger.net/api/http/routers/NAME@docker # container ``` ## Public exposure is a different task Everything above is reachable only from the LAN and Tailscale. Making a service internet-facing means a real `grosinger.net` name, an explicit `traefik.http.routers.NAME.entrypoints=websecure` label, and a deliberate decision by the user. Do not add `websecure` when asked for an internal hostname.