Skip to content

Managing modules

Installing your first module is Getting started. This is what the screen means afterwards.

The modules screen, showing the Ultima Online module running with the routes it mounts, and the list of hosts modules may be installed from.
A module is installed from a release URL and runs inside the server, so only listed hosts are allowed to serve one.

installed → enabled → started, with disabled and startup_failed as recoverable states.

State Means
Installed Files are on the volume; it mounts at the next restart
Enabled Allowed to run, and about to be loaded. Every boot resets each non-disabled module to this, then records the outcome
Started Running: routes mounted, schema applied
Disabled An operator switched it off. Its routes answer not found
Startup failed It tried and could not. The site came up without it

A module that fails to load never takes the site down. Failure is caught across the whole lifecycle — require, schema, routes, registration, boot hook — and the site starts with that module’s routes and navigation absent, and the reason recorded on this screen.

Two consequences of how boots work:

  • A failed module is retried on every restart. Fix the underlying cause and restart; you do not need to touch the panel. A deterministically broken module re-records its failure each boot, which is the honest thing for it to do.
  • Disabled is the only state a boot leaves alone. Disabling is an operator’s decision rather than an outcome, so it survives restarts untouched.

Paste the new release’s install-manifest URL and press Install. The bundle is verified against its sha256, 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.

Three different actions, in increasing order of destruction.

Disable flips the row and dispatches that module’s shutdown hook, so it actually stops — releases its sockets, closes its streams — rather than merely becoming unreachable. Enable is deliberately not the mirror image: there is no boot hook re-dispatch, so enabling offers a restart.

Uninstall is non-destructive by default: the row goes to disabled, the directory is removed, and the module’s tables and data are retained.

Purge runs the module’s own purge.sql and destroys its data. It is never implied by an uninstall, and it is offered in two places — as a standalone action on an installed module, and as an opt-in checkbox in the uninstall dialog.

The allowlist at the bottom of the screen. Installing a module runs its code inside your server, so only listed hosts are permitted, over HTTPS, re-checked on every redirect. An empty list forbids every install.

MODULE_SOURCE_HOSTS seeds this list on a site’s first boot and is ignored afterwards — the setting is authoritative, so a redeploy cannot silently undo your choice.

MODULES in .env declares the set this deployment runs, resolved at every container start, each entry <id>@<version>=<install manifest URL>.

The division of ownership is the thing to remember: the variable owns what is on the volume; the panel owns whether a module runs. Uninstall a declared module from the panel and its files come back at the next start — disabled.

A module already unpacked at the declared version is a no-op that makes no network call at all, so a restart with no route to the internet comes up unchanged. A version that cannot be fetched is logged, shown on this screen, and never stops the site starting.

Unpacking a module tarball into ./modules/<id>/ and restarting is a supported install — it is why that path is a bind mount rather than a named volume. The row it produces has no provenance columns, because nothing downloaded it.