multiversectl GitHub

Control which services run

Start optional services on demand, keep a published port, and give a service its own hostname.

Let a service declare its own hostname

Put a label on the service in its own compose file. The key names the host, the value is the container port:

services:
  app-dev:
    labels:
      multiverse.host.app: "4200"   # app-u3.<domain> -> port 4200

With no labels at all, a service that exposes exactly one port is already reached at the app's own name. See Labels and variables for entrypoints and aliases.

Keep a service's published port

Universes drop every published host port, so two copies never collide. A service that genuinely needs one asks to keep it:

services:
  db:
    ports: ["5432:5432"]
    labels:
      multiverse.publish: "true"

Or, without touching the repository, list it in the manifest: publish: [db] on the app. Only one universe can hold that host port at a time.

Run optional services on demand

# multiverse.yaml, on the app
optional:
  - name: celery
    services: [worker, beat]
$ multiversectl up 3 api --with celery

The group name is also activated as a compose profile. Comma-separate several: --with celery,mail.

Configure what depends on the universe

A universe's hostnames do not exist until it does, so anything built from them — trusted origins, allowed hosts, CORS, a front-end's API base URL — is asked for with a label on the service that needs it:

services:
  web:
    labels:
      multiverse.env.DJANGO_ALLOWED_HOSTS: hosts
      multiverse.env.DJANGO_CSRF_TRUSTED_ORIGINS: origins
      multiverse.env.API_BASE_URL: url:devapi

The sources are origins (every address as an origin, each with its entrypoint's port), hosts, domain, universe, slug, host:<key> and url:<key>. An unknown one is an error naming the valid ones, never a variable that is quietly empty. They are written into the fragment generated for the app, which is merged last, so they win over the service's own environment: and over any env_file: — which is how a static list in a committed .env stops being what the universe runs with.

It is a label rather than a ${MULTIVERSE_ORIGINS} reference because a compose file is also run by hand: the reference would resolve to the empty string when multiversectl is not driving, while a label is inert metadata until it is. For a repository you cannot change, an app's inline overlay: does the same from the manifest:

apps:
  - name: api
    repo: api
    overlay:
      services:
        web:
          environment:
            DJANGO_CSRF_TRUSTED_ORIGINS: "${MULTIVERSE_ORIGINS}"
            DJANGO_ALLOWED_HOSTS: "${MULTIVERSE_HOSTS}"

Prefer the aggregates to listing hostnames one at a time: they follow the manifest, so adding a host key or an app leaves no settings list behind. There is also <HOST>_HOST and <HOST>_URL per host key, <REPO>_DIR per checkout, and UNIVERSE, UNIVERSE_SLUG, UNIVERSE_DIR, MULTIVERSE_DOMAIN, COMPOSE_PROJECT_NAME, HOST_UID, HOST_GID. multiversectl env <universe> prints the lot.