Skip to content

Open your app securely on any device

You built an app in a workbench, and now you want to actually open it: in the browser on your own machine, and on your phone. The app runs inside a container, so you can’t reach it directly. Each way takes one command: clean URLs on your machine, and real HTTPS you can open on any device, your phone included. That HTTPS is what a login or an installable app needs. This guide follows one workbench, called acme, from an empty project to an app you reach both ways.

The example app is a small progressive web app, because it makes the reason for the HTTPS step obvious: without it, a PWA won’t install. A PWA is an ordinary website that can install to the home screen and run offline, powered by a service worker the browser runs in the background. That service worker is the reason the last step of this guide exists. But nothing here is PWA-only: the same URLs and the same HTTPS are what you want for an app you just click through, or one you log into, on any device.

This part happens entirely on your own computer. You build the app and get it reachable in your own browser.

The app is a Java API with a web frontend, and you want Claude Code in the workbench to build it. Postgres to store things. So that is what you start with:

Terminal window
monoceros init acme --with-languages=node,java --with-features=claude --with-services=postgres

Notice there is no port here. That is on purpose. You do not yet know which ports the app will actually serve on, and you add them later, once the app exists and tells you. Build the workbench:

Terminal window
monoceros apply acme

Hand Claude the task and let it work (see Add components to a workbench for the ways to reach the agent). It scaffolds the app into a directory under projects/, shop here, with the API and the web frontend inside. That directory name is the app’s name: it is what you pass to commands like monoceros start and monoceros share later. Because both servers are long-running and have to survive the session that started them, the agent writes their start commands into the app’s own launch config, at projects/shop/.monoceros/launch.json:

projects/shop/.monoceros/launch.json
{
"targets": [
{ "name": "api", "command": "./mvnw spring-boot:run", "port": 8080, "default": true },
{ "name": "web", "command": "npm run dev", "port": 5173, "default": true }
]
}

Each target names the port its server listens on. That whole mechanism, and how to hand-edit the file, is in Long-running app servers.

But the app is still not reachable from your machine. A workbench is a sealed container, closed off from the host, so a server running inside it stays hidden until its port is opened to the outside. Opening a port is a change on the host, which the agent cannot make from inside the container. So it stops and asks you to open the ports. That is the next step.

The workbench needs to know which two ports should be reachable from outside the container. That is what add-port is for:

Terminal window
monoceros add-port acme 5173 8080

This takes effect at once. The proxy picks up the routes on the spot, with no apply and no rebuild, so the ports are reachable the moment the command returns. The change is also written to the yml:

acme.yml
routing:
ports:
- 5173
- 8080

So the routes are part of the workbench’s reproducible config, not a one-off: they come back on the next monoceros apply acme, on this machine or anyone else’s.

Each port gets its own hostname, <workbench_name>-<port>.localhost. In this example that is acme-<port>.localhost, and the first port you add also answers on the bare acme.localhost as the default. That is why 5173 goes first: the web frontend is what you open in a browser, so it gets the short acme.localhost, while the API keeps acme-8080.localhost. These names need no hosts-file editing: any *.localhost name resolves to 127.0.0.1 on its own (RFC 6761), and a single machine-wide proxy maps each name to the right container port. Because the hostname carries the workbench name, two different workbenches can each run a server on 5173 at the same time without fighting over the host, one at acme-5173.localhost and the other at its own name. The full picture is in The Monoceros proxy and tunnels.

The launch config is in place, so the app can be brought up. The agent already started it from inside the container, but you drive it from the host. Start the whole app:

Terminal window
monoceros start acme shop

That is monoceros start <workbench_name> <app>: the workbench name first, then the app, which is the projects/ directory from the last step. It brings up the default set in order, waiting for each server’s port to listen before starting the next, so the API is up before the frontend that calls it. To start or stop just one part, add --target with its name:

Terminal window
monoceros start acme shop --target api

And to bring it down again, the same shape with stop:

Terminal window
monoceros stop acme shop

stop also takes --target to stop a single server instead of the whole app. There is more on the runner, readiness, and how servers come back after a rebuild in Long-running app servers.

The servers are up and the routes are live, so list what you can open:

Terminal window
monoceros port acme
5173 → http://acme.localhost (default)
5173 → http://acme-5173.localhost
8080 → http://acme-8080.localhost

Open http://acme.localhost and the app loads. Everything works, the service worker included, so you can even install the PWA right here. A .localhost address counts as a trusted page, which is the quiet reason all of it is fine on your machine. It looks finished.

Now take the same app off your computer and onto your phone, or any other device on the same network. Doing that turns out to need HTTPS, for a reason that only shows up once you leave your machine.

How the app is reached from your machine and from other devices The app's servers run inside a sealed workbench container. The browser on your machine reaches them through the Monoceros proxy over HTTP at acme.localhost. A phone or other device reaches them through monoceros share over HTTPS, backed by a local certificate authority. http://acme.localhost https://<ip>:5173 Your machine browser Your phone or other device Monoceros proxy HTTP · *.localhost monoceros share HTTPS · local CA Workbench container acme · sealed from your machine web port 5173 api port 8080
Two ways in: your machine through the proxy over .localhost, other devices through share over HTTPS.

Now you open the same app on your phone, which is on the same wifi network. This time acme.localhost does not help, because a .localhost name always refers to the machine you are using: on the phone, it points at the phone, not at your computer. The phone therefore has to reach your computer by its network address instead, and it does so over plain HTTP. That is where things break, and the cause is not in your code:

  • The service worker refuses to register, so the PWA will not install and offline is gone.
  • A login does not complete. Keycloak, and other identity providers, run a bit of cryptography in the browser to keep the sign-in safe, and the browser only allows that on a trusted page. Over plain HTTP on a network address, the page is not trusted, so the login fails before it really starts.

The common cause is one rule: the browser grants these things only to a secure context, which in practice means HTTPS. Your .localhost address gets that for free; a phone on your network does not. This is not really about PWAs. Anything that needs a secure context runs into the same wall the moment you leave your own machine.

That is what share is for. It serves the app over HTTPS on your local network, so the phone gets a trusted page and both problems disappear:

Terminal window
monoceros share acme shop

The shape is the same as start: monoceros share <workbench_name> <app>, the workbench name and then the app. It reads that app’s launch config, so every target with a port comes along, and it prints an address per server plus the one thing you do once:

Sharing acme/shop on the local network:
api
https://192.168.1.42:8080
https://your-mac.local:8080
web
https://192.168.1.42:5173
https://your-mac.local:5173
Trust the local CA once (first device) for warning-free HTTPS:
~/.monoceros/ca/rootCA.pem
Press Ctrl+C to stop sharing.

The padlock needs a certificate behind it, so Monoceros issues one from a small certificate authority that lives only on your machine. A device believes that certificate once you tell it to trust the authority, and that is the single manual step: you trust rootCA.pem once on each device you want to open the shared app from (a phone, a tablet, another computer, even this host), and from then on shared apps just load there. Trusting the local certificate has the exact steps for every device, phones, tablets, and computers alike. With the CA trusted, the login goes through and the PWA installs to the home screen.