Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ohemilyy/universe/llms.txt

Use this file to discover all available pages before exploring further.

Extensions are self-contained JARs that Universe loads at startup from the ./extensions/ directory. They allow you to add new container runtimes, remote template storage backends, and custom template variables without touching the core orchestrator. Two production-ready extensions ship alongside Universe: runtime-docker and storage-s3.

How extensions load

On startup, Universe’s extension loader scans every .jar file inside ./extensions/. Each JAR must contain exactly one class that implements the Extension interface. Universe instantiates that class through Guice, injects its dependencies, and calls onLoad().
Extensions discovered at startup are loaded in arbitrary order. Do not rely on load ordering between extensions.

Extension lifecycle

Every extension implements three lifecycle hooks defined in the Extension interface:
interface Extension {
    fun id(): String
    fun version(): String

    fun onLoad()
    fun onUnload()
    fun onReload()
}
HookWhen it is called
onLoad()Once at startup, after Guice injection is complete. Register providers here.
onUnload()On graceful shutdown, or when extension reload is issued before re-loading.
onReload()When the extension reload console command is run. Typically calls onUnload() then onLoad().
You can trigger a reload at any time from the console or REST API:
extension list    # show all loaded extensions and versions
extension reload  # call onReload() on every extension

Built-in extensions

Universe ships two extensions that cover the most common deployment needs:

Docker Runtime

Run instances as isolated Docker containers with port bindings, volume mounts, and resource limits.

S3 Storage

Store and retrieve template zips from AWS S3 or any S3-compatible object store.

Building Extensions

Implement your own runtime, storage backend, or template variable provider.

Extension isolation rule

Extensions must only depend on :api and :extensions:extension-api. Depending on :app is prohibited — the app module contains orchestrator internals that are not part of the stable extension surface.
Any extension that imports classes from the :app module will break across Universe upgrades. Restrict your build.gradle.kts dependencies to compileOnly(projects.api) and compileOnly(projects.extensions.extensionApi).
Use runtimeDownload for any external libraries your extension needs (AWS SDK, Docker Java, etc.) so that Universe can resolve them at runtime without bundling them into the fat JAR.

Key registries

Extensions interact with three Guice-injected registries. Inject whichever registries your extension needs and call register() inside onLoad().

RuntimeRegistry

Holds all available RuntimeProvider implementations keyed by a technology string (e.g., "docker", "screen", "tmux"). When a new instance is deployed, the wrapper looks up the provider whose key matches the "runtime" field in the instance’s configuration file.
interface RuntimeRegistry {
    fun register(key: String, provider: RuntimeProvider)
    fun unregister(key: String)
    fun get(key: String): RuntimeProvider?
    fun getAll(): Map<String, RuntimeProvider>
}

TemplateStorageRegistry

Holds all available TemplateStorageProvider implementations keyed by a storage string (e.g., "s3", "local"). When a template reference specifies "storage": "s3", the template manager delegates download and upload to the matching provider.
interface TemplateStorageRegistry {
    fun register(provider: TemplateStorageProvider)
    fun get(key: String): TemplateStorageProvider?
    fun unregister(key: String)
    fun getAll(): Map<String, TemplateStorageProvider>
}

TemplateVariableRegistry

Aggregates custom placeholder-to-value mappings contributed by extensions. During instance deployment, collectVariables() is called across all registered providers and the results are merged with the built-in variables (%PORT%, %INSTANCE_ID%, %MASTER_IP%, etc.).
interface TemplateVariableRegistry {
    fun register(provider: TemplateVariableProvider)
    fun collectVariables(): Map<String, String>
}

Build docs developers (and LLMs) love