Zudoku
Cloud Platform

Images

Unikraft Cloud uses a registry to store images used to instantiate apps. At a high level, you use the CLI to build and push an image to the registry, and then ask the controller to start an app from it.

This process utilizes two types of registries: central and local. Built images default to the central registry (index.unikraft.io). The platform pulls from this registry automatically upon starting an instance for the first time.

You can choose to push an image directly to a node's local registry instead. This bypasses the central registry entirely. Skipping this extra network round trip speeds up deployments. It also reduces bandwidth consumption. Read more about this in the registries documentation.

Limited Access

The local registry is only available in BYOC or on-prem Unikraft Cloud installations.

Dockerfile, Kraftfile and runtime

On Unikraft Cloud, a Dockerfile guides the process of building images, and a Kraftfile guides the process of deploying the resulting image.

Use the Python running example:

TerminalCode
git clone https://github.com/unikraft-cloud/examples cd examples/httpserver-python3.12

The directory contains the following Kraftfile:

YAMLKraftfile
spec: v0.7 runtime: base-compat:latest targets: - kraftcloud/x86_64 rootfs: source: ./Dockerfile format: erofs cmd: ["/usr/bin/python3", "/src/server.py"]

The file is simple. It defines the start command, instructs the CLI to build the root filesystem, specifies the base-compat:latest runtime, and targets kraftcloud on x86_64.

On Unikraft Cloud, a runtime provides a base image containing the minimal code your app needs to execute. The CLI combines your app files with this base image during the build step. It uses the Dockerfile as the source to generate the directory structure. When packaging these files into a root filesystem, Unikraft offers two main options: CPIO and EROFS. The file specifies the EROFS format to create a read-only disk image, which is the preferred format for Unikraft Cloud. For a more in depth explanation of the Kraftfile you can check the reference.

The Dockerfile itself for the app looks as follows:

Dockerfile
FROM python:3.12 AS build RUN set -xe; \ /usr/sbin/ldconfig /usr/local/lib # copy the python image COPY ./server.py /src/server.py

If you're familiar with Dockerfiles there is nothing unusual here, other than that Unikraft Cloud prefers usage of FROM scratch to keep images lean.

This multi-stage build creates a minimal image. First, the process loads a standard Python 3.12 image to configure system dependencies. Next, it starts an empty filesystem called scratch. The builder extracts the compiled Python binary and essential shared system libraries from the first stage. It moves these exact files into the empty environment. Finally, the builder copies your app script into the container. To add your app's code to the build, change the COPY commands as needed.

All guides on Unikraft Cloud, and the examples they rely on underneath come with Kraftfiles and Dockerfiles for you to get started.

You can also try a standard base image (for example, FROM python:alpine). This is a good start for porting existing Dockerfiles to Unikraft Cloud. Be aware that this choice may increase image size, memory use, and boot time.

Root filesystem from an existing image

If the image you want to run already exists, take its root filesystem straight from the registry instead of rebuilding it from a Dockerfile. Set the rootfs source type to oci:

YAMLKraftfile
spec: v0.7 runtime: base-compat:latest targets: - kraftcloud/x86_64 rootfs: source: type: oci path: docker.io/library/nginx:latest format: erofs

The build flattens the layers of a container image into the requested format. For a Unikraft image, it reuses the existing root filesystem unchanged. The image's own command, environment, and labels carry over, and anything you set in the Kraftfile applies on top.

Note the following:

  • Set type: oci explicitly. The CLI never guesses it from the path.
  • The path has to be a registry reference, and the image has to provide the platform you build for.
  • A container image still needs a BuildKit builder to flatten its layers.

See the Kraftfile reference for the other source types.

Platform kernel

Limited Access

Kernel-less images are available as part of enterprise plans. To try out this feature, reach out to the Unikraft Cloud Discord or send an email to support@unikraft.com.

An image doesn't have to carry its own kernel. An image that ships only a rootfs boots on the kernel installed on the node, which your metro operator maintains as a separate package. Images that do ship a kernel keep working exactly as before.

This decouples the guest kernel from your app. A kernel update, including a security fix, becomes available to workloads that cold-boot after the operator rolls out the new kernel package. You no longer rebuild and re-push every image to pick one up. Images also get smaller, which shortens image pull times.

A new kernel on the node applies to the next instance that starts. Running instances keep the kernel they booted with until they fully shut down, and an instance resuming from a snapshot always boots the kernel that snapshot captured.

Build from a Dockerfile

A kernel-less image doesn't need a Kraftfile. Pass a Dockerfile to unikraft build, and it builds an image with only the rootfs that the Dockerfile describes:

TerminalCode
unikraft build ./Dockerfile --arch x86_64 --output <my-org>/my-app:latest

The CLI identifies a Dockerfile by its filename, so names such as Dockerfile.prod also work. A directory without a Kraftfile builds from its Dockerfile. If both files are present, the Kraftfile takes precedence.

A Dockerfile has no targets to take an architecture from, so you must pass --arch. The image runs only in metros that have a platform kernel installed.

Architectures

The Unikraft Cloud platform runs on both x86_64 and arm64 hosts, and the hosted platform runs on x86_64. The architecture of the guest images must match the host architecture.

Limited Access

arm64 hosts are available as part of enterprise plans. To deploy to them, reach out to the Unikraft Cloud Discord or send an email to support@unikraft.com.

List the architectures you want under targets in the Kraftfile:

YAMLKraftfile
spec: v0.7 runtime: base-compat:latest targets: - kraftcloud/x86_64 - kraftcloud/arm64 rootfs: source: ./Dockerfile format: erofs cmd: ["/usr/bin/python3", "/src/server.py"]

A single unikraft build invocation then produces an image for every architecture in the list. The --arch flag names architectures on the command line instead, which is handy for narrowing a multi-architecture Kraftfile down to one architecture.

How the build resolves architectures depends on what the Kraftfile declares:

KraftfileArchitectures used
runtime with no targets or --archx86_64 only, even when the runtime provides more.
runtime with targets or --archOnly the architectures listed in both targets and --arch, which must be ones the runtime provides.
roms with no runtimeOnly the architectures listed in both targets and --arch, and the build fails when you supply neither.

Multi-platform builds need a correctly configured BuildKit builder on the host. Learn more here

Local outputs

--output names an image reference unless it looks like a path. A local path starts with ./, ../, or /, and the build saves the image there as an OCI archive or an OCI layout instead of pushing it:

TerminalCode
unikraft build . --arch x86_64 -o ./app.tar
│ saved image output=oci-archive://./app.tar

The build prints where the image went. Add the oci-archive:// or oci-layout:// prefix to make the choice explicit. The same rule applies to image get, image delete, and image copy.

An output such as app.tar or dist/ is an image reference, so the build pushes it to the registry instead of writing it locally. Add ./ to keep writing to a local path.

Example workflows

This guide uses a Python app as an example to show three workflows:

  1. How to create an image and launch an instance from it.
  2. How to create an image and launch many instances from it.
  3. How to launch instances from an existing image.

Create an image and an instance from it

Start with the simplest workflow: create an image from a Python app and start an instance from it:

TerminalCode
unikraft build . --output <my-org>/httpserver-python312:latest unikraft run --metro fra \ -p 443:8080/http+tls \ -m 512M \ --image <my-org>/httpserver-python312:latest

The output should look like:

TerminalCode
metro: fra name: httpserver-python312-ma2i9 uuid: e7389eee-9808-4152-b2ec-1f3c0541fd05 state: running image: <my-org>/httpserver-python312 resources: memory: 512MiB vcpus: 1 service: uuid: 51a41f63-7e88-c443-b9bf-83cd7c04d975 name: young-night-5fpf0jj8 domains: - fqdn: young-night-5fpf0jj8.fra.unikraft.app networks: - uuid: 53da3490-c6f5-3718-25f1-219a65163c73 private-ip: 10.0.3.3 mac: 12:b0:18:0c:cb:aa timestamps: created: just now

This command builds an image named httpserver-python312@sha256:278cb8b1... using the Kraftfile and Dockerfile. It then packages it, pushes it to the registry, and starts an instance named httpserver-python312-ma2i9 from it. The platform fetches the image from the registry to start the instance.

You can see your images by running the following command:

TerminalCode
unikraft images list

You should see output like:

TerminalCode
REF DIGEST <my-org>/httpserver-python312 sha256:278cb8b14f9faf9c2702dddd8bfb6124912d82c11b4a2c6590b6e32fc4049472

And you can remove an image from the central registry with the following command:

TerminalCode
unikraft images rm <my-org>/httpserver-python312:latest

Use the full <my-org>/<name> reference: a bare name such as httpserver-python312 resolves to the official/ namespace, not to your images.

There may be a delay of a few minutes between removing an image from the registry and the image list reflecting the change.

Create an image and many instances from it

For the next workflow, start many instances:

TerminalCode
unikraft build . --output <my-org>/httpserver-python312:latest unikraft run --metro fra \ -p 443:8080/http+tls \ -m 512M \ --replicas 2 \ --image <my-org>/httpserver-python312:latest
TerminalCode
metro: fra name: httpserver-python312-lscmn uuid: 1121225d-a678-45a2-a8ce-335a9a6a2683 state: running image: <my-org>/httpserver-python312 resources: memory: 512MiB vcpus: 1 service: name: restless-orangutan-1lklj6z5 uuid: 16f4b86d-df8f-452c-b189-41ec34250e58 domains: - fqdn: restless-orangutan-1lklj6z5.fra.unikraft.app networks: - uuid: da9445a3-c7b7-4327-8499-3bd7001cd829 private-ip: 10.0.6.37 mac: 12:b0:0a:00:06:25 timestamps: created: just now metro: fra name: httpserver-python312-xfgvc uuid: d021379d-9e03-4d0b-92a6-6fdf038e076a state: running image: <my-org>/httpserver-python312 resources: memory: 512MiB vcpus: 1 service: name: icy-violet-hce1qwm5 uuid: 4cd63083-d538-44df-92b9-74bd6eb54ec0 domains: - fqdn: icy-violet-hce1qwm5.fra.unikraft.app networks: - uuid: 51c147cc-8f8d-4347-b628-d2e0f4f9b3be private-ip: 10.0.0.121 mac: 12:b0:0a:00:00:79 timestamps: created: just now metro: fra name: httpserver-python312-7lha2 uuid: 0706c648-0c56-4c74-8dda-5a1aeff7d8b8 state: running image: <my-org>/httpserver-python312 resources: memory: 512MiB vcpus: 1 service: name: hidden-silence-l3g520dw uuid: 38703973-ccbd-47ae-b4c0-f9f59c1751e7 domains: - fqdn: hidden-silence-l3g520dw.fra.unikraft.app networks: - uuid: 6c20f9b2-dc39-41d1-a4bd-250a52a75b19 private-ip: 10.0.9.185 mac: 12:b0:0a:00:09:b9 timestamps: created: just now

Check that it worked by listing all instances with:

TerminalCode
unikraft instances list
TerminalCode
METRO NAME STATE IMAGE ARGS MEMORY VCPUS FQDN CREATED fra httpserver-python312-7lha2 running <my-org>/httpserver-python312 512MiB 1 hidden-silence-l3g520dw.fra.unikraft.app just now fra httpserver-python312-xfgvc running <my-org>/httpserver-python312 512MiB 1 icy-violet-hce1qwm5.fra.unikraft.app just now fra httpserver-python312-lscmn running <my-org>/httpserver-python312 512MiB 1 restless-orangutan-1lklj6z5.fra.unikraft.app just now

Three instances run—the original plus two replicas.

Create instances from an existing image

In this final workflow, take the existing image and start new instances from it:

TerminalCode
unikraft run --metro fra \ -p 443:8080/http+tls \ -m 512M \ --image <my-org>/httpserver-python312@sha256:1b815914eb568a06ca4bbfdfb7d6cf484a9e9a0947ba8e0e0f1664d972a25bca

Pinning the digest is optional. Providing the exact hash guarantees you deploy the exact same immutable image every time. This prevents unexpected behavior if a developer overwrites a standard tag like latest. If you omit the digest, the CLI automatically pulls the newest version of your image.

You now have a new instance created from the existing image.

Learn more

Last modified on