Skip to content

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.

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:

acme.yml
routing:
ports:
- 5173
- 8080
- 6006

That config gives you four URLs:

URLRoutes to
http://acme.localhostport 5173 (the first, the default)
http://acme-5173.localhostport 5173
http://acme-8080.localhostport 8080
http://acme-6006.localhostport 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:

Terminal window
monoceros port acme

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):

Terminal window
monoceros add-port acme 8080 6006 # expose two more ports
monoceros add-port acme 3000 --default # expose 3000 and make it the default host
monoceros 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:

Terminal window
monoceros apply acme

A ports-only apply just rewrites the proxy config and Traefik picks it up - it does not rebuild the container.

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.

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-proxy whose container is already gone. This is common with a native dockerd under 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:

monoceros-config.yml
schemaVersion: 1
routing:
hostPort: 8080

The URLs then carry that port (e.g. http://acme.localhost:8080).

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:

Terminal window
monoceros tunnel acme postgres

Then point your client at the local end:

Terminal window
psql -h localhost -p 5432