Zudoku
Features

Plugins

Plugins let you attach small helper programs to an instance and reach each one over a direct, authenticated HTTP endpoint. A plugin runs inside the instance next to your main app, loads from its own image, and answers requests that the Unikraft Cloud API forwards to it.

Unikraft Cloud built plugins to provide a native sandbox experience. In the sandbox case, a plugin runs a small HTTP server that accepts commands to run inside the instance, exposes filesystem services, and more. The same mechanism fits any helper you want to reach over a per-instance, authenticated endpoint.

Official plugins

Sandbox plugin

The sandbox plugin lets you run commands and manipulate the filesystem inside an instance, over the plugin endpoint:

  • Commands: start a shell command inside the instance, inspect it, read its output, feed its standard input, wait for it, and signal it.
  • Filesystem: create directories, and read, write, and upload files.

Load the sandbox plugin with --plugin when you create, run, or edit an instance:

TerminalCode
unikraft instance run \ --metro fra \ --image official/alpine:latest \ --volume :/data:size=1GiB \ --plugin 'name=sandbox,image=plugins/sandbox:latest,config={"persist_path":"/data"}'

An instance with the sandbox plugin loaded accepts five unikraft commands:

CommandDescription
unikraft instance execRuns a command on the instance. --dir sets the working directory, --env sets the environment, and the command's exit status becomes the CLI's.
unikraft instance shellOpens an interactive shell. The shell runs on your machine and sends each command to the instance, so paths resolve against the instance.
unikraft instance copyCopies a single file in either direction. Write a path on an instance as <instance>:<path>, or <metro>/<instance>:<path> to pick the metro. The alias is cp.
unikraft instance writeUploads a single local file to the instance. --parents creates missing directories, and --append adds to the remote file instead of replacing it.
unikraft instance readDownloads a single file from the instance. The local path is optional and defaults to the remote file's base name.
TerminalCode
unikraft instance exec my-instance -- ls -la /var/log unikraft instance shell my-instance unikraft instance copy ./config.json my-instance:/etc/app.json unikraft instance write my-instance ./data.bin /var/lib/app.bin --parents unikraft instance read my-instance /var/log/app.log

The commands use the plugin named sandbox by default. Pass --plugin <name> to use a sandbox plugin that you loaded under a different name.

A few limitations apply:

  • The instance must run on the base-compat runtime. Use an image that ships the standard Linux utilities, such as official/alpine:latest or official/debian-slim:latest, so that more commands work in the sandbox.
  • The shell is experimental and has no pseudo-terminal, so full-screen and interactive programs such as vim, top, less, or a password prompt don't work. It has no job control either, so ctrl-z, bg, and fg don't work.
  • copy, read, and write carry one regular file at a time, and don't carry file permissions across.
  • exec forwards standard input only when you redirect it from a file or a pipe.

How it works

Each plugin loads from its own ROM image. When the instance boots, the platform mounts every plugin at /uk/plugins/<plugin_name> and starts its init program. The platform hands each plugin a socket to accept connections on, so every plugin has its own private channel.

You reach a plugin through the instance's API endpoint:

Code
https://api.<metro>.unikraft.cloud/v1/instances/<uuid>/plugins/<plugin_name>/<path>

The platform forwards everything after <plugin_name>/ to the plugin as the request path. A request to .../plugins/my-plugin/files/list reaches the plugin with the path /files/list.

This design has a few notable properties:

  • Authorized before it reaches the plugin. By default the platform checks the request against your account and confirms your access to the target instance. A plugin can carry a token of its own instead, which lets callers without a platform account in. Either method settles the request before the plugin sees it, which sets plugins apart from running an HTTP server as a regular service.
  • A direct line to one instance. You talk to a single instance, so no load balancing or autoscale sits in the path, and the instance needs no service group.
  • Works with scale-to-zero. When scale-to-zero has put the instance to sleep, the platform wakes it to serve the request and keeps it up for the duration, the same way a normal request does.

The plugin image

A plugin ships as a standard ROM image with an executable named init in its root. The platform loads the image, mounts it at /uk/plugins/<plugin_name>, and runs init when the plugin starts.

init receives two things from the platform:

  • Configuration on standard input. Whatever you pass in the plugin's config field arrives on init's STDIN as JSON. A plain value such as "config": "my-string" counts as valid JSON, the same as "config": 232 or "config": { ... }.
  • A socket file descriptor. The platform passes init an --api_fd <n> argument that holds the file descriptor the plugin accepts connections on. The plugin serves API traffic on that socket.

Assigning ports

The controller assigns each plugin's socket a port from a configurable range. It walks the range from the start and gives each plugin the next free port, skipping any port that the guest already uses. Operators set the range with the node-level --vmm-plugin-port-start (default 20000) and --vmm-plugin-port-end (default 29999) flags.

Adding plugins when you create an instance

Pass --plugin to unikraft instance create or unikraft instance run, once for each plugin:

TerminalCode
unikraft instance create \ --name my-instance \ --metro fra \ --image my-app:latest \ --plugin 'name=my-plugin,image=user/myplugin:latest,config={"workdir":"/tmp"}'

The flag takes name, image, and an optional config, which must be valid JSON. The CLI sends these in the plugins field of a POST /instances request.

In the API, each entry in the plugins array accepts these fields. The --plugin column shows which of them the CLI flag can set:

FieldRequired--pluginDescription
nameYesYesThe plugin name. It becomes the <plugin_name> segment in the plugin endpoint. See Plugin names for the allowed format.
imageYesReference string onlyThe plugin's ROM image, given as an image reference string such as user/myplugin:latest, or as an image object with a url and optional headers and pull_policy, the same as elsewhere in the API. The deprecated rom field still works in its place.
configNoYesArbitrary JSON that the platform passes to the plugin's init on STDIN. Any JSON value works, including a string, a number, or an object.
authorizationNoNoHow the platform authorizes requests to this plugin, as described under authorization.

You can attach up to 8 plugins to an instance.

Authorization

Every request to a plugin endpoint carries a token in an Authorization: Bearer <token> header. The platform authorizes the request before it forwards anything, and strips that header on the way, so the plugin never sees the token.

In the API, a plugin entry picks how the platform checks the token with an optional authorization object. The --plugin flag can't set this object, so a plugin that you load with the CLI uses the userdb method.

FieldRequiredDescription
typeYesEither userdb or bearer.
tokenFor bearerThe plugin's own token, at most 256 characters, which only a bearer plugin may carry.

Platform users

A plugin without an authorization object uses the userdb method. The token has to belong to a platform user with access to the instance, so the token you use for the rest of the API reaches the plugin as well.

A token of the plugin's own

With "type": "bearer" the platform compares the supplied token against the plugin's own token and consults no user account. Send the request with unikraft api, because the --plugin flag can't set authorization:

TerminalCode
unikraft api /v1/instances \ '{ "name": "my-instance", "plugins": [ { "name": "my-plugin", "image": "user/myplugin:latest", "authorization": { "type": "bearer", "token": "s3cr3t" } } ] }'

A caller then reaches the plugin with that token alone:

TerminalCode
curl -H "Authorization: Bearer s3cr3t" \ https://api.fra.unikraft.cloud/v1/instances/<uuid>/plugins/my-plugin/files/list

This suits callers with no platform account and no business holding one: a sidecar, your own control plane, or a webhook from a third-party service. The token opens that one plugin on that one instance, and nothing else of the API.

The instance status in the API echoes a non-default type back in the plugin's authorization object, and never echoes the token. A plugin on the default method reports no authorization object at all. unikraft instance get doesn't show authorization, so read it from the API with unikraft api /v1/instances/<uuid>.

Two consequences follow from bearer consulting no user account:

  • Your platform token stops working on that plugin, so the plugin's token becomes the only way in.
  • Changing the token takes the same PATCH path as other plugin changes, so it lands the next time the instance stops. Send it with unikraft api, because the CLI flags can't set authorization.

Plugin names

A plugin name has a maximum length of 63 characters and contains only these characters:

  • Lowercase and uppercase letters (a–z, A–Z)
  • Digits (0–9)
  • Hyphen (-) and underscore (_)

Changing plugins on an existing instance

Use unikraft instance edit to change the plugins of an existing instance. --plugin replaces the instance's plugin list, --add plugins=... appends to it, and --del plugins=<name> removes one plugin:

TerminalCode
# Replace the plugin list unikraft instance edit my-instance \ --plugin name=my-plugin,image=user/myplugin:latest # Append to the plugin list unikraft instance edit my-instance \ --add plugins=name=my-other-plugin,image=user/myotherplugin:latest # Remove a plugin by name unikraft instance edit my-instance \ --del plugins=my-other-plugin

Each flag sends a PATCH /instances request with prop set to plugins. --plugin sends the set op, --add sends add, and --del sends del. The platform handles these requests the same way whether they come from the CLI or the API. A plugin that a set leaves out of the new list unloads, the same as with del. The platform applies the change while the instance sits in the stopped state. On a running instance, the platform queues the change and applies it the next time the instance stops. A restart and a scale-to-zero both count as a stop. Until then, the instance keeps its current plugins, and a removed plugin still answers requests.

The --plugin flag doesn't carry authorization, so unikraft instance edit --plugin turns every bearer plugin on the instance back into a userdb plugin. --add leaves the existing plugins as they are. To replace the list and keep a bearer plugin, send the set request with unikraft api.

Reloading across the instance lifecycle

The platform treats plugins like ROMs across state changes. It reloads them whenever the instance goes through a scale-to-zero, suspend, or restart cycle, so a plugin returns with the instance each time it comes back up.

Inherited when you clone an instance

Plugins belong to the instance definition, so an instance that comes from another one keeps the same plugins. This applies when you use branching, restore a checkpoint, fork an instance, or start from an on-demand template.

Use cases

The first use case for plugins is a native sandbox: a plugin runs a small HTTP server that accepts commands to run inside the instance and exposes filesystem services, all reachable over the authenticated plugin endpoint.

Plugins fit other patterns too. A few ideas:

  • Admin and debug endpoints that stay separate from your app's public services and stay reachable only through the authenticated API.
  • Health and inspection probes that read state from inside the instance on demand.
  • Sidecar utilities such as a metrics collector, a log tailer, or a configuration reloader.
  • Per-instance tooling that you attach to one instance without a change to its base image.

Limitations

  • On BYOC and on-prem installations, plugins require a license that includes the feature.
  • You can attach at most 8 plugins to an instance.
  • A plugin name has a maximum length of 63 characters and uses only letters, digits, -, and _.
  • A bearer token has a maximum length of 256 characters.

Learn more

  • Plugin SDK: build a plugin in Go without hand-rolling the platform contract.
  • Sandboxes: run untrusted code in a hardware-isolated microVM.
  • ROMs: the image format that plugins build on.
  • Instances: create and manage the instances that host plugins.
  • Scale-to-zero: how an idle instance wakes to serve a plugin request.
  • Services: the regular way to expose an HTTP server, for contrast with the authenticated plugin endpoint.
  • Unikraft Cloud's REST API reference, in particular the create instance endpoint.
Last modified on