Installing modules
There is no catalog, and there is no marketplace. A module is installed by naming the URL of a release’s install manifest.
That is a design decision rather than an unfinished feature: a catalog would make core’s release cadence decide which modules exist, and the whole point of the module system is that it does not.
What a release publishes
Section titled “What a release publishes”Two artifacts:
<id>-<version>.tar.gz— the bundle:module.json, the server half, the prebuilt client chunk, and the SQL fragments.- An install manifest — small JSON carrying the bundle’s URL and its
sha256.
The manifest URL is the thing an operator pastes. The bundle is downloaded, verified
against the sha256, and unpacked into modules/<id>/ on the mounted volume.
Nothing is compiled at any point in that sequence.
The two surfaces
Section titled “The two surfaces”Both write the same installed_modules row, and neither needs a build step.
The admin panel
Section titled “The admin panel”Paste the manifest URL, press Install, then restart — a button on the same screen, not an instruction to go and restart the container. It runs the lifecycle shutdown and exits, and the supervisor declared in the shipped Compose file brings the process back.
That is why restart: unless-stopped is called out as load-bearing on Install the
site. Without a supervisor, that button takes the
site down and leaves it down.
The MODULES environment variable
Section titled “The MODULES environment variable”For hosts managed by Compose rather than by clicking. Each entry is:
<id>@<version>=<install manifest URL>Resolution runs inside the server process, before the volume is scanned — which is what lets it write the same provenance columns a panel install writes. A module already unpacked at the declared version is a no-op that makes no network call at all.
By hand
Section titled “By hand”./modules is a bind mount, deliberately rather than a named volume, so placing a module
directory there yourself is a supported install. A named volume would have routed that
through docker cp.
The image’s own copy of modules/ is excluded by .dockerignore, so a module sitting in a
builder’s working tree can never ship inside an image.
Which surface wins
Section titled “Which surface wins”They govern different things, and the split is worth memorising:
- The declaration owns what is on the volume.
- The row owns whether a module runs.
So uninstalling a declared module from the admin panel returns its files at the next
start and leaves it disabled. The files come back because MODULES still declares them;
it stays off because the row says so. That is the intended outcome, not a bug — but it
surprises people who expect the panel to be the last word.
Upgrades
Section titled “Upgrades”Paste the new release’s manifest URL and install over the top. The bundle is verified, unpacked over the old one, and takes effect at the restart.
An upgrade deliberately leaves the state alone — upgrading an enabled module must not silently switch it off, and re-installing a disabled one must not silently switch it on.
Removal
Section titled “Removal”Covered in full on Managing modules; the shape matters here because it constrains what you ship.
Uninstall is non-destructive: the row goes to disabled, the directory is removed, and
the module’s tables and data are retained.
Purge is separate, explicit, and destructive — it runs your purge.sql. It is offered
in two places, and both are while the file is still on disk: as a standalone action on an
installed module, and as an opt-in checkbox in the uninstall dialog.
That second placement exists because of a real ordering trap: purge.sql lives inside the
directory uninstall deletes, so “purge afterwards” was never actually possible — it would
have left a disabled row whose Purge button had nothing to run.
The consequence, accepted and stated: an operator who uninstalls without ticking the box
keeps the tables, and getting rid of them later means reinstalling the module first. Write
purge.sql on the assumption it may be run long after anyone remembers what it drops.