The Monoceros proxy and tunnels
You reach a workbench from your host two ways: persistent HTTP routing through the Monoceros proxy, and ad-hoc TCP tunnels. The proxy is a single, machine-wide Traefik container that maps a hostname to each declared port; a tunnel forwards a TCP service (like a database) to a local port. This page covers both - plus how to add and remove ports, and why the proxy is shared.
New to Monoceros? Start with Installation.
Hostname routing
Section titled “Hostname routing”You declare ports under routing.ports in the yml. Each port gets its own
<name>-<port>.localhost hostname, and the first port additionally answers
on the bare <name>.localhost:
routing: ports: - 5173 - 8080 - 6006That config gives you four URLs:
| URL | Routes to |
|---|---|
http://acme.localhost | port 5173 (the first, the default) |
http://acme-5173.localhost | port 5173 |
http://acme-8080.localhost | port 8080 |
http://acme-6006.localhost | port 6006 |
*.localhost resolves to 127.0.0.1 on its own (RFC 6761), so there is no
hosts-file editing and no host-port juggling. List a workbench’s live URLs
anytime:
monoceros port acmeAdding and removing ports
Section titled “Adding and removing ports”Two ways, same result - the ports always end up in the yml.
With the command (takes effect immediately: the proxy hot-reloads, no
rebuild and no apply):
monoceros add-port acme 8080 6006 # expose two more portsmonoceros add-port acme 3000 --default # expose 3000 and make it the default hostmonoceros remove-port acme 6006 # stop exposing one--default takes exactly one port; it moves that port to the front so it owns
the bare <name>.localhost.
By hand, by editing routing.ports in the yml and then applying:
monoceros apply acmeA ports-only apply just rewrites the proxy config and Traefik picks it up - it
does not rebuild the container.
One proxy for the whole machine
Section titled “One proxy for the whole machine”There is exactly one monoceros-proxy container, reused by name across all your
workbenches. That avoids host-port collisions when several containers run in
parallel - two Vite apps on 5173 no longer fight over the host, because each is
reached by its own hostname.
When port 80 is taken
Section titled “When port 80 is taken”The proxy listens on host port 80 by default. If something else already holds
it, apply stops with a message that tells you which of two cases you’re in:
-
A real service holds it - another container, a system nginx, IIS on Windows. The message names the container when it can. Stop or re-map it, then re-run.
-
The port is held but nothing publishes it - almost always a leftover
docker-proxywhose container is already gone. This is common with a nativedockerdunder WSL, which gets restarted on every shutdown and strands the forwarder with nothing behind it. Restarting the Docker daemon reaps it:Terminal window sudo systemctl restart docker # native dockerd / WSL; or restart Docker Desktop
Or move Monoceros off 80 entirely - set routing.hostPort in
monoceros-config.yml:
schemaVersion: 1routing: hostPort: 8080The URLs then carry that port (e.g. http://acme.localhost:8080).
TCP services: tunnels
Section titled “TCP services: tunnels”The proxy speaks HTTP only. To reach a TCP service - Postgres, MySQL, Redis - or an ad-hoc port from a host tool, open a tunnel instead:
monoceros tunnel acme postgresThen point your client at the local end:
psql -h localhost -p 5432See also
Section titled “See also”monoceros add-port/monoceros remove-port- add or remove routed ports.monoceros port- list a workbench’s URLs.monoceros tunnel- tunnel options (local port, bind address).