Skip to content

The router

The mx router is a single Traefik container per workstation. Every mx project attaches to the same Docker network and declares a Host() rule in its compose labels; Traefik reads those labels off the Docker socket and routes by hostname. Application services publish no HTTP host port at all, so two ecosystems can both run a web app on container port 3000 and never collide.

MechCrate ecosystem topologyA browser reaches hostnames such as api.localhost and mechcrate.localhost. One workstation-wide Traefik router, installed by mx router install, reads Host rules from container labels on the shared devmesh-traefik Docker network and forwards each hostname to the running container that claims it. Several mx projects attach to that same network, and only the subset of services you started is up.

devmesh-traefik — shared external Docker network

mx router — one per workstation

project: mech-crate/site

project: acme

Host match

Host match

Host match

project: legacy-shop

every service
not started

Browser
api.localhost · admin.localhost · mechcrate.localhost

Traefik
:80 · Host() rules read from container labels

api
Host: api.localhost

admin
Host: admin.localhost

worker
not started

site
Host: mechcrate.localhost

db · redis
not started

MechCrate ecosystem topologyA browser reaches hostnames such as api.localhost and mechcrate.localhost. One workstation-wide Traefik router, installed by mx router install, reads Host rules from container labels on the shared devmesh-traefik Docker network and forwards each hostname to the running container that claims it. Several mx projects attach to that same network, and only the subset of services you started is up.

devmesh-traefik — shared external Docker network

mx router — one per workstation

project: mech-crate/site

project: acme

Host match

Host match

Host match

project: legacy-shop

every service
not started

Browser
api.localhost · admin.localhost · mechcrate.localhost

Traefik
:80 · Host() rules read from container labels

api
Host: api.localhost

admin
Host: admin.localhost

worker
not started

site
Host: mechcrate.localhost

db · redis
not started

One router per workstation. Every project attaches to the same devmesh-traefik network, Traefik reads Host() rules off container labels, and only the services you started are up.
Terminal window
mx router install # copies the Traefik config to ~/.mech-crate/router, creates devmesh-traefik
mx router up # start (or re-apply config to) the router
mx router status # installed / running / network / dashboard URL

mx router status prints something like:

Router Status
────────────────────────────────────────
Installed: installed
Status: running
Network: devmesh-traefik
Dashboard: http://localhost:7680
Location: /Users/you/.mech-crate/router

The rest of the verbs: down, restart, logs, inspect, network, uninstall. Aliases exist for most (start/up, stop/down, ps/status, info/inspect, remove/uninstall).

mx add --domain <host> writes these labels for you. This is what they look like, and what you would write by hand for a service mx did not scaffold:

services:
myapp:
networks:
- default # internal: db, redis, siblings
- devmesh-traefik # external: how Traefik reaches you
labels:
- traefik.enable=true
- traefik.http.routers.${COMPOSE_PROJECT_NAME}-myapp.rule=Host(`${MYAPP_ROUTER_HOST:-myapp.localhost}`)
- traefik.http.routers.${COMPOSE_PROJECT_NAME}-myapp.entrypoints=web
- traefik.http.services.${COMPOSE_PROJECT_NAME}-myapp.loadbalancer.server.port=3000
- traefik.docker.network=devmesh-traefik
networks:
devmesh-traefik:
external: true

Four things matter and are easy to get wrong:

  1. Both networks. devmesh-traefik is how the router reaches the container; default is how the container reaches its database. Only the service that receives external traffic needs the first one. Postgres, Redis and workers stay on default only.
  2. traefik.docker.network. A container on two networks has two addresses. Without this label Traefik may pick the wrong one and you get a 502.
  3. loadbalancer.server.port is the container’s port, not a host port. There is no host port.
  4. The router, service and middleware names carry the compose project. Traefik keeps one table of each per machine, so a bare myapp is a name every mx project on the workstation shares. ${COMPOSE_PROJECT_NAME}-myapp is resolved by Compose from the project name it already knows, so it needs nothing exported and works under a hand-rolled docker compose -f … too.

The routing rule is a Traefik matcher, so the usual composition works: Host(...) || Host(...), Host(...) && PathPrefix(...), HostRegexp(...).

This is the point of the whole arrangement. Project A’s api.localhost and project B’s admin.localhost are two containers on one shared network behind one Traefik. Switching between them is cd: no teardown, no port bookkeeping, no lost container state. Start the subset you need:

Terminal window
make dev s=api # in project A
make dev s=admin # in project B — both reachable, simultaneously
make dev s="api web" # or a named subset, quotes required

Container names no longer stand in the way of this either. No shipped compose file pins container_name, so each project’s containers are named <COMPOSE_PROJECT_NAME>-<service>-<index> and two projects that both declare a db service coexist. See container names if you have a script that addressed one by a fixed name.

Two copies of the same shape coexist as well, and that took two separate repairs. Dev overrides no longer pin host ports, so the second stack’s Postgres does not find 5432 taken. Traefik router, service and middleware names now carry the compose project, so two stacks that each scaffold a service called api write two entries into Traefik’s one router table instead of colliding in it. See host ports for the port classes and their override names.

VariableDefaultEffect
MX_ROUTER_HOME~/.mech-crate/routerState directory
MX_ROUTER_NETWORKdevmesh-traefikDocker network name
MX_ROUTER_DASHBOARD_PORTautoPin the dashboard port
MX_ROUTER_DASHBOARD_RANGE7680-7799Range the dashboard is allocated from

Work down this list; it is ordered by how often each one is the cause.

  1. mx router status. Is the router even running?
  2. docker ps. Is the service running, and is it healthy?
  3. Is the service on devmesh-traefik? A container that is not on the network is invisible to Traefik no matter how good its labels are.
  4. Open the dashboard (mx router status prints the URL) and look at Routers. A missing router means the labels did not parse; a router with no service behind it usually means traefik.docker.network is wrong or absent.
  5. A 502 rather than a 404 means Traefik found the container and could not reach the port. Check loadbalancer.server.port against what the app binds.

The full guide covers HTTPS with mkcert and Let’s Encrypt, the dynamic file provider, middlewares, custom routes, routing to non-Docker host services, load balancing, and the complete troubleshooting matrix. It is published in the corpus:

→ MechCrate Router (full guide)