Zudoku
Cloud Platform

Volumes

A volume is a persistent storage device that keeps data across restarts and even redeployments.

Drive volumes from your own code

The SDKs wrap the volume API, including attaching and detaching volumes:

Code
await ukc.volumes.get(name="data").attach(to="web", at="/data")

See the JavaScript, Python and Go SDKs.

Typical workflow

Creating a persistent volume

You can create an instance with attached volumes in a single API operation. This ties the volumes' lifetimes to the instance - when you delete the instance, the volumes disappear. This use case is still valid, because an instance with no volumes attached will write all its files into RAM and eventually exhaust its memory.

To start, create the volume with the CLI, with size 100 MiB and name my-volume:

TerminalCode
unikraft volumes create --metro fra \ --name my-volume \ --size 100M
TerminalCode
metro: fra name: my-volume uuid: 4d5576f7-315a-4eab-98b8-6c18fec076e0 state: available size: 100MiB filesystem: ext4 quota-policy: static persistent: true access-mode: rwo timestamps: created: just now

You can list the volume with:

TerminalCode
unikraft volumes list
TerminalCode
METRO NAME STATE SIZE CREATED fra my-volume available 100MiB just now

The volume is in available state because it isn't attached to any instance yet. It's also marked as persistent because it will persist even after instance deletion.

Importing local data

To populate an empty volume with local data, use the CLI import subcommand. For example, assuming the data you want to import is in your my-data directory, you would run:

TerminalCode
unikraft volume import \ my-volume \ --source ./data

You should see output like:

TerminalCode
│ importing data into volume size=16.47KiB volume=my-volume │ import complete free=89.52MiB total=89.69MiB volume=my-volume

Creating an instance

Start from this guide to create a simple Flask web server:

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

Replace the contents of server.py with:

server.py
from flask import Flask, send_from_directory from datetime import datetime import os app = Flask(__name__) file_path = "/mnt/log.txt" def initialize_file(): if not os.path.exists(file_path): with open(file_path, 'w') as log_file: log_file.write("Log file created.\n") @app.route('/') def write_to_file(): initialize_file() current_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S") # Write to file with open(file_path, 'a') as log_file: log_file.write(f"{current_datetime}\n") # Read contents of the file with open(file_path, 'r') as log_file: file_contents = log_file.read() return file_contents if __name__ == '__main__': app.run(host='0.0.0.0', port=8080)

On every request, this simple server will write a timestamp to a file on the mounted persistent volume and print out the current contents of the file.

Build and deploy the Flask web server and mount the my-volume volume at /mnt:

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

You should see output like:

TerminalCode
metro: fra name: httpserver-python312-flask30-bxwxm uuid: 3ff1ebad-2639-4214-bab4-ed35c4c32fa4 state: starting image: <my-org>/httpserver-python312-flask30 resources: memory: 512MiB vcpus: 1 service: uuid: 2021ca1e-5e47-a35c-bde2-7190f3815c07 name: damp-sunset-azd6dtyt domains: - fqdn: damp-sunset-azd6dtyt.fra.unikraft.app volumes: - name: my-volume uuid: 4d5576f7-315a-4eab-98b8-6c18fec076e0 at: /mnt networks: - uuid: 4dc3b272-4fa2-ddb4-7ce0-5e66e8112a61 private-ip: 10.0.6.5 mac: 12:b0:66:99:b3:3f timestamps: created: just now

To confirm that the platform attached the volume, run:

TerminalCode
unikraft volumes get my-volume

You should see output like:

TerminalCode
METRO NAME STATE SIZE CREATED fra my-volume mounted 100MiB 2 minutes ago

A volume can't mount at /uk or at any path below it, which the platform reserves in the guest for plugins. Such a mount point fails the request with The mountpoint must not be '/uk' or a path below it, whether you set it when you create the instance or when you attach the volume.

Testing

The Flask server writes the time and date to /mnt/log.txt for each request. Test it by running curl several times. Example output:

Code
$ curl https://holy-bonobo-px345skl.fra.unikraft.app Log file created. 2024-02-24 08:15:32 $ curl https://holy-bonobo-px345skl.fra.unikraft.app Log file created. 2024-02-24 08:15:32 2024-02-24 08:15:34 $ curl https://holy-bonobo-px345skl.fra.unikraft.app Log file created. 2024-02-24 08:15:32 2024-02-24 08:15:34 2024-02-24 08:15:35

To test data persistence, you can stop the instance, then detach the volume from the instance:

TerminalCode
unikraft volumes detach my-volume --from httpserver-python312-flask30-bxwxm unikraft instances delete httpserver-python312-flask30-bxwxm

You should see that the volume is now available again.

While the instance is stopped, the volume is still attached to it, but not mounted. Mounting of an attached volume happens automatically when the instance starts. This is different from a snapshotted instance, which is in standby state and has its volumes still mounted.

If you try to detach a volume while the instance is running, the platform will queue the operation. The detach operation will trigger when the instance goes into stopped.

Now start another instance with the same volume:

TerminalCode
unikraft run --metro fra \ -m 512M \ -p 443:8080/http+tls \ -v my-volume:/mnt \ --image <my-org>/httpserver-python312-flask30:latest

This should output something like:

TerminalCode
metro: fra name: httpserver-python312-flask30-0s9c7 uuid: 59d06415-84b8-4bf5-85c7-997960382011 state: starting image: <my-org>/httpserver-python312-flask30 resources: memory: 512MiB vcpus: 1 service: uuid: ca8ae7e9-3767-85f6-3d70-b77bcf894a7c name: winter-field-v6m37jgs domains: - fqdn: winter-field-v6m37jgs.fra.unikraft.app volumes: - name: my-volume uuid: 4d5576f7-315a-4eab-98b8-6c18fec076e0 at: /mnt networks: - uuid: 935ba4ef-39c2-07e7-d2ae-8cd3c9aae07c private-ip: 10.0.6.5 mac: 12:b0:66:99:b3:3f timestamps: created: just now

Mount the volume when you create the instance, as shown above. unikraft volumes attach on a running instance doesn't mount the volume right away. The platform queues the attach and applies it when the instance next stops, so the volume only appears after a restart.

Run one final curl to this new address. You should see the previous contents plus a new entry at the bottom:

Code
$ curl https://winter-field-v6m37jgs.fra.unikraft.app Log file created. 2024-02-24 08:15:32 2024-02-24 08:15:34 2024-02-24 08:15:35 2024-02-24 08:20:58

Cleaning up

To clean up, first detach the volume from all instances and then remove it:

TerminalCode
unikraft instances stop httpserver-python312-flask30-0s9c7 unikraft volumes detach my-volume --from httpserver-python312-flask30-0s9c7 unikraft volumes delete my-volume unikraft instances delete httpserver-python312-flask30-0s9c7 unikraft instances delete httpserver-python312-flask30-bxwxm

If the instance is already in stopped state, the detach operation will trigger immediately. Otherwise, the platform will queue it and trigger when the instance fully stops. Do not confuse a stopped instance with a snapshotted instance, which is in standby state and has its volumes still mounted.

Warm-plug volume operations

The platform supports warm-plug volume operations. This lets you attach volumes in more lifecycle states than before, but with important timing constraints.

What's supported

  • You can attach a volume to an instance created from a snapshot (for example, from a template).
  • You can attach a volume to an instance that's currently running.

Current behavior and limitations

  • Attaching to a running instance isn't applied instantly.
  • The platform schedules the attach operation and completes it on the next scale-to-zero cycle.
  • This is warm-plug support, not full hot-plug support.
  • Detaching a volume still requires the instance to be fully shut down (not only scaled to zero).
  • If you request detach while the instance isn't yet shut down, the platform schedules the update and applies it automatically once shutdown happens.

API usage note

For now, volume attach and detach updates are available through dedicated endpoints:

  • /v1/volumes/attach
  • /v1/volumes/detach

You can invoke these endpoints via the CLI using unikraft volumes attach and unikraft volumes detach.

The instance PATCH endpoint doesn't currently support volume operations.

Volume templates

A volume template is a volume that you can use as a base to create new volumes with the same data. When you create a volume template, the platform converts the source volume to a template.

Once a volume transitions to template state, it's immutable: you can clone it but not write to it or delete it while active clones exist.

Volume templates have their own create, read, update, and delete endpoints at /volumes/templates. They support delete locks and tags.

Volume templates are useful because you can clone them.

You can create volume templates from existing volumes using the CLI as well:

TerminalCode
unikraft volume template create my-volume
TerminalCode
metro: fra name: my-volume uuid: 4d5576f7-315a-4eab-98b8-6c18fec076e0 state: template size: 100MiB filesystem: ext4 persistent: true timestamps: created: just now

You can list all volume templates with:

TerminalCode
unikraft volume template list
TerminalCode
METRO NAME STATE SIZE CREATED fra my-volume template 100MiB 1 minute ago

The platform converts attached volumes to templates automatically when you convert an instance into an instance template.

Volume cloning

A volume clone is an independent copy of an existing volume. The clone operation is asynchronous: the platform creates the new volume immediately in a pending state, and the data copy completes in the background.

To clone one or more volumes, you can use the POST /volumes/clone endpoint. You can also use the unikraft CLI to clone a volume:

TerminalCode
unikraft volume clone my-volume --name my-volume-clone
TerminalCode
metro: fra name: my-volume-clone uuid: 3f0625b4-cad3-4875-8b61-113aad4b02c7 state: available size: 100MiB filesystem: ext4 quota-policy: static persistent: true access-mode: rwo timestamps: created: just now

The platform clones volumes implicitly when you clone an instance or create one from a template. It clones each attached volume and attaches the clone to the new instance.

You can't delete a source volume while it has active clones.

You can't clone a volume template.

Filesystems

By default, volumes are block devices with ext4 filesystem. There's also a virtiofs filesystem option, which is a shared filesystem that allows more than one instance to read and write the same volume simultaneously. To specify the filesystem, use the filesystem field when creating a volume with POST /volumes:

JSONPOST /volumes
{ "name": "my-volume", "size_mb": 512, "filesystem": "virtiofs" }

You can also use the unikraft CLI:

TerminalCode
unikraft volume create --metro fra \ --name my-volume \ --size 512M \ --filesystem virtiofs

For BYOC or on-prem Unikraft Cloud installations, you can configure custom filesystems that can be backed by other media, such as S3 or network storage. Custom filesystem drivers support lifecycle hooks for format, fscheck, mount, unmount, and delete, so operators can control how storage is prepared, attached, and cleaned up.

This is useful when you want shared storage semantics over non-block backends, for example exposing S3 through a POSIX layer such as JuiceFS while still integrating with the platform's volume lifecycle.

Managed volumes

Limited Access

Managed volumes are available as part of enterprise plans. To enable them for your account, reach out to the Unikraft Cloud Discord or send an email to support@unikraft.com. Quotas aren't yet enforced by the platform for managed volumes, so use them with caution to avoid filling up your host's disk.

A managed volume points to an existing directory on the host rather than a platform-allocated storage file.

Create a managed volume with a host_path field instead of size_mb when calling POST /volumes:

JSONPOST /volumes
{ "name": "my-managed-volume", "host_path": "/srv/data/mydir", "uid": 1000, "gid": 1000 }

Restrictions apply:

  • host_path must be an absolute, normalised path to an existing directory on the host (no ., .., or : components).
  • uid and gid set the ownership used when accessing the volume from the guest. Both default to 0 if not specified.
  • size_mb and template are mutually exclusive with host_path.
  • The platform doesn't create, format, resize, or delete the backing directory for managed volumes.

Access modes

On creation, you can specify an access_mode that controls how instances can access or share the volume:

Access modeDescription
rwoRead-write once. The volume can only be mounted in read-write mode by a single instance at a time. This is the default access mode.
roxRead-only many. The volume can be mounted in read-only mode by multiple instances simultaneously.
rwxRead-write many. The volume can be mounted in read-write mode by multiple instances simultaneously. This access mode is only supported for volumes with virtiofs filesystem.

You can specify the access mode when creating a volume with the access_mode field:

JSONPOST /volumes
{ "name": "my-volume", "size_mb": 512, "access_mode": "rwo" }

You can also use the unikraft CLI:

TerminalCode
unikraft volume create --metro fra \ --name my-volume \ --size 512M \ --access-mode rwo

Read-only many

More than one instance can mount the same rox volume simultaneously as long as all mounts are in read-only mode. Read-only sharing is always permitted regardless of how many other instances have the volume mounted.

Specify readonly: true when attaching a volume at instance creation or via the attach endpoint:

JSONPOST /instances
{ ... "volumes": [ { "name": "my-shared-volume", "at": "/data", "readonly": true } ], ... }

Read-write many

Read-write many (rwx) volumes are only supported for volumes with the virtiofs filesystem. The downside is that virtiofs volumes are slower than block volumes, so only use them for rwx or managed volumes. To create a rwx volume, you can use the POST /volumes endpoint:

JSONPOST /volumes
{ "name": "my-volume", "size_mb": 512, "access_mode": "rwx", "filesystem": "virtiofs" }

You can also use the unikraft CLI:

TerminalCode
unikraft volume create --metro fra \ --name my-volume \ --size 512M \ --filesystem virtiofs \ --access-mode rwx

Learn more

Last modified on