URLs and origins
Every service with a port has two addresses:
http://localhost:<port>, where stackctl reaches it. Itshealthcheckis probed here.- Its public origin, where a browser reaches it. With a reverse proxy, that is its hostname there.
The public origin is what stackctl injects as PUBLIC_ORIGIN, what a path in urls is based on, and what stackctl show-config reports as publicOrigin.
Which origin a service gets
| The service | Its public origin |
|---|---|
| has no reverse proxy, or is in none of its routes | http://localhost:<port> |
is named by a route's service | <scheme>://<subdomain>.<domain>[:port], from the first such route |
is reached only through '*' or paths | none |
| is an infra service in no route | none |
sets publicOrigin | what that gives |
A service reached only through '*' or paths has no host of its own, so stackctl doesn't guess one: it gets no PUBLIC_ORIGIN, and a path in its urls is an error. Give it a publicOrigin, or a service route of its own.
PUBLIC_ORIGIN
Each service's process gets its public origin as PUBLIC_ORIGIN, next to PORT. Use it wherever an app needs its own address: allowed hosts, CORS origins, OAuth callback URLs.
const origin = process.env.PUBLIC_ORIGIN; // https://api.myproject.teststackctl exec and stackctl run set it too. Only a PUBLIC_ORIGIN in the service's own .env file overrides it.
Links in the footer
urls puts links on the terminal UI's links row. stackctl adds none of its own.
services: {
web: {
port: 3000,
urls: [
'', // the origin itself, shown as "web ↗"
{ label: 'admin', url: '/admin' }, // a path on the origin, with a label
{ label: 'storybook', url: 'http://localhost:6006' }, // any URL, with a label
({ port }) => `http://localhost:${port}/debug`, // a callback
],
},
},- A path is based on the service's public origin, so the same config is right with and without a proxy. It needs a
port. - A link shows as
<label> ↗. Without a label of its own, it takes the service's name. urlscan itself be a callback returning the list.- An infra service takes the same
urls.
The top-level urls option holds workspace-wide links that belong to no service, such as a dashboard. A callback there gets { envs, tokens, buildAddress }.
export default defineConfig({
urls: [{ label: 'board', url: 'https://tracker.example.test/board' }],
services: {/* … */},
});publicOrigin
- Type:
{ scheme?, subdomain?, domain?, port? } | (ctx) => …
Sets a service's public origin part by part. Each part given replaces that part of the origin it would otherwise have. Available on application and infra services.
reverseProxy: {
port: 4000,
publicAddress: { domain: 'example.test', scheme: 'https' },
routes: [{ subdomain: '*', paths: { '/api/**': 'api', '/**': 'web' } }],
},
services: {
web: { port: 3000, publicOrigin: { subdomain: 'app' }, urls: [''] }, // https://app.example.test
api: { port: 3001, publicOrigin: { subdomain: null } }, // https://example.test
},subdomain: null is the bare domain. A port that is the scheme's default is left out.
WARNING
publicOrigin only shapes URLs. It never changes routing: for the proxy to serve web at app.example.test, a route has to send that host to it.
buildAddress
urls and healthcheck callbacks, and infra callbacks, get buildAddress. It builds a URL starting from the service's public origin, replacing the parts you pass:
web: {
port: 3000, // routed at web.<domain>
urls: ({ buildAddress }) => [
buildAddress(), // https://web.<domain>
buildAddress({ subdomain: 'admin' }), // https://admin.<domain>
buildAddress({ subdomain: null, path: '/api' }), // https://<domain>/api
],
},It takes { scheme?, subdomain?, domain?, port?, path? }. For a service with no public origin, and for the top-level urls, it starts from the proxy's public address, or http://localhost without a proxy.
To build several links, make urls itself the callback, as above. A callback returned from a .map() into the urls array gets no types from the config.