Several installations on one machine¶
A machine can run more than one installation, and always could. Every path is
keyed by the product name — /etc/demo, /var/lib/demo, /run/demo,
/opt/demo — the deployment lock lives under the product's own manager
directory, and the five systemd units are named per product. Two installations
never contend for anything the manager owns.
What they do share is the host: one Docker daemon, one port space, one filesystem, one set of CPUs. morzer does not pretend otherwise. This page is about making that sharing legible rather than preventing it.
The daemon's image store is shared too. Two installations of one product, or
two products built on one base image, hold the same images under the same
names. release prune in one installation removes a pruned release's images
only when no release left in any installation names them, so a prune here
never costs another installation a rollback target. See
release prune.
What is on this machine¶
morzer ls
PRODUCT RELEASE MODE UNITS PATH
demo 1.4.0 5 /etc/demo
sandbox 1.5.0-rc1 dev 0 /etc/sandbox
Read from the state files alone: no Docker call, no lock, no network. That is deliberate — the moment somebody most needs to know what a machine is running is the moment the daemon is wedged, and a listing that needed the daemon would be unavailable exactly then.
morzer installation list is the same command under its long name.
| Column | What it is |
|---|---|
PRODUCT |
The name every path, unit and Compose project derives from |
RELEASE |
The version the state records as current, or none |
MODE |
dev for a sandbox; blank for a production installation |
UNITS |
How many systemd units this installation manages; 0 after init --install-units=false, and unknown when the supervisor could not be read |
PATH |
Where its configuration lives |
morzer ls --json adds schema_version per row. It is absent from the table on
purpose: it matters on one day in a hundred, and a terminal's width is scarcer.
units is null there when the count could not be read — zero is a real answer
and a supervisor nobody could ask is not one.
An installation it cannot read¶
A row whose state will not load says unreadable and the reason is printed
underneath:
PRODUCT RELEASE MODE UNITS PATH
demo 1.4.0 5 /etc/demo
legacy unreadable 5 /etc/legacy
✗ legacy
installation state at /var/lib/legacy/manager/installation.json is invalid:
installation was written by a newer manager (schema 9, this manager reads 5)
A directory in /etc that this process cannot open at all is a different case
again. It is listed as not counted, with a warning rather than a failure:
credstore not counted unknown /etc/credstore
! credstore
cannot be read by this process, so it is not counted as an installation;
re-run as root if it is one
/etc/<product> is 0750 and owned by root, and so are several of the host's
own directories — /etc/credstore, /etc/wireguard. An unprivileged process
cannot tell one from the other, so morzer does neither of the two obvious wrong
things: it does not refuse to run (which would make every non-root invocation
fail on a real machine), and it does not count them (which would tell an
operator with one deployment that they have four). It says what it could not
look at, and morzer ls is where that belongs — an empty-looking listing that
does not mention them is the misleading answer.
An /etc that cannot be read at all is an error, because that is the
manager's own root rather than one directory inside it.
An installation whose state will not load is listed rather than skipped. The
moment an installation's state breaks is the moment it must not look absent — and
the row carries nothing this manager could not read, because a mode or a
release reported out of a state file it does not understand would be a guess
presented as a fact.
What is running¶
morzer ls --status
Adds a SERVICES column of running-out-of-total, at the cost of one runtime
query per installation. Each query is bounded at five seconds on its own, so one
unresponsive daemon costs that row and no other:
PRODUCT RELEASE MODE UNITS SERVICES PATH
demo 1.4.0 5 3/3 /etc/demo
sandbox 1.5.0-rc1 dev 0 unknown /etc/sandbox
! sandbox
services: timed out after 5s
The counts are read without taking the deployment lock. A listing that blocked behind a running update would be useless exactly when somebody is watching one; the price is that a row can be a moment out of date, and the output says so.
Saying which installation you mean¶
With one installation, nothing has to be said: the manager finds it. With two or more, a command that acts on an installation refuses rather than guessing.
$ morzer status
error: this machine has 2 installations, so --product is required
hint: demo, sandbox — pass `--product demo`, or `--config /etc/demo/installation.yaml`; `morzer ls` lists them
Guessing is the one thing it must not do. An operator who typed morzer update
on the wrong deployment finds out during the downtime.
Three ways to say it, in precedence order:
| Source | Rank | Notes |
|---|---|---|
--config <path>/installation.yaml |
1 | Selects the layout the file sits in. This is what the generated systemd units pass. |
--product <name> |
1 | The ordinary spelling. |
MORZER_PRODUCT |
2 | For a shell session pinned to one installation. |
| Discovery | 3 | Used only when exactly one installation exists. |
--config and --product share rank 1, and passing both is refused rather
than resolved:
$ morzer --config /etc/demo/installation.yaml --product sandbox status
error: --product sandbox and --config /etc/demo/installation.yaml name different installations
hint: pass one or the other
The exception is agreement: if both name the same installation, the command proceeds. Somebody who spelled one deployment two ways has said one thing twice.
MORZER_PRODUCT is not in that exclusion — a flag overrides it, which is what
makes it usable in the case it exists for:
export MORZER_PRODUCT=sandbox
morzer status # sandbox
morzer --product demo status # demo, just this once
It does not reach your timers
The generated systemd units pass --config, which outranks the variable. An
operator who exports MORZER_PRODUCT globally has not redirected their
backup or update timers, and should not expect to have.
There is deliberately no morzer use <product> that writes a selection to disk.
That is kubectl's context, and its failure mode is why people wrap kubectl in
scripts that print the context in the prompt: a mutable global that decides which
deployment a destructive command hits. A variable dies with the shell that set
it.
The commands that answer anyway¶
Not everything needs an installation. These run on a machine with three of them and no selection, because they are about the host, a file, or an installation they name themselves:
morzer ls · morzer version · morzer init · morzer doctor ·
morzer installation import · morzer release new · morzer release verify ·
morzer release pack · morzer release build · morzer release archive
morzer release show ./bundle joins them, because the argument decides: a
directory is inspected without an installation, while release show and
release show 1.4.0 read this installation's store and are refused like
anything else.
doctor is the interesting one: it runs its machine-scope checks, reports them,
and refuses the rest by name. Taking the diagnostic away at the moment the
diagnosis is "you have two installations" would be exactly the wrong time.
What doctor says about the neighbours¶
Two checks, both read-only, both warnings and never failures — a machine with
several installations is a supported arrangement, and a doctor that failed on a
supported arrangement would teach everyone to ignore doctor.
machine.installations counts them, and warns about the one arrangement
nothing else reports: an installation whose state will not load while units may
be installed. systemd starts it on every boot and the manager cannot tell it
anything. A unit count that could not be read counts as "may have them" — a
failed read is not evidence that there are none.
machine.ports warns when two installations declare the same host port:
! no two installations publish the same port
two installations want the same host port: 18080 (demo, sandbox)
they cannot both run; change one release's port parameter with
`morzer --product <name> config set`, or keep only one started
They cannot both run. Without this check the second one's apply fails inside
Compose with a message about a port already in use — true, and silent about the
neighbour holding it, which sends an operator looking for a stray process with
ss -tlnp.
It reads what each release declares it needs, resolved against that installation's own parameters, not what is bound right now. The collision matters before either is started, which is exactly when nothing is listening and a probe would report all clear.
The usual fix is a parameter:
morzer --product sandbox config set http_port=18081
morzer --product sandbox apply
Taking one off the machine¶
morzer --product demo installation remove --force --confirm <installation-id>
This removes one installation and nothing of another. Every path, unit and lock
it touches is derived from demo. The images it removes are only those that no
release of any installation names, the same rule release prune follows. A
glob typed by hand, such as /etc/systemd/system/demo*, also matches
demo-admin's units.
By default the data, and the keys that read it, stay on the machine. See
installation remove
for what goes and what stays.
What is deliberately not here¶
No --all. There is no morzer update --all, and there will not be. Three
installations have three releases, three compatibility gates and three windows of
downtime; a command that updated all of them would be a loop with one exit code
and no way to say which one failed. That loop belongs in a shell script, where
its failure handling is yours.
No isolation between installations. They share a daemon, a port space and a
disk. Making them not share those is a container-per-installation design, which
is a different product. What ships instead is the machine.ports check above,
and the fact that everything the manager owns is already keyed by product.
No renaming. The product name is the paths, the Compose project, the unit
names and the comment on every secret recipient. Changing it is a migration, not
a flag: export, init under the new name, import, restore.
No machine-level registry file. Which installations exist is read off the
filesystem: morzer ls lists each /etc/*/ that holds an installation.yaml.
A second index would be one more thing to keep in
sync — wrong precisely when a machine had been rebuilt by hand, which is when
somebody is reading it.
Related¶
morzer ls— the command surface- Recovering a lost machine — rebuilding one installation
installation remove— taking one off the machine- Changing configuration —
config set, which is how a port moves