# Unikraft

> Unikraft CLI (`unikraft`) commands for building and deploying to Unikraft Cloud. Use when working with Kraftfiles, deploying unikernels, or managing Unikraft Cloud instances/services/images. Covers the new `unikraft` CLI that replaces the legacy kraftkit `kraft`.

- Skill: `guillempuche/unikraft` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add guillempuche/unikraft`
- Raw SKILL.md: https://api.skillmd.com/api/skills/guillempuche/unikraft/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- License: MIT
- Author: guillempuche (https://skillmd.com/u/guillempuche)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/guillempuche/unikraft

---


# Unikraft CLI Reference

Build and deploy unikernels with the `unikraft` CLI.

- Documentation: <https://unikraft.com/docs/cli>
- CLI source & releases: <https://github.com/unikraft-cloud/cli>
- Platform: <https://unikraft.cloud>

**Targets the new `unikraft` CLI `0.4.x`** (verified against `0.4.1`) and the Unikraft Cloud REST API `v1`.
Run `unikraft version` to check; if the major or minor differs, re-verify flags/commands against `--help` before following this skill.

## New CLI vs legacy kraftkit — they are different tools

The binary is **`unikraft`** (from `unikraft-cloud/cli`).
It **replaces the legacy kraftkit `kraft`** (`v0.12.x`).
They are separate programs with:

- a **different command tree** (`unikraft run` / `unikraft build` / `unikraft instances …`, not `kraft cloud deploy`),
- a **separate auth store** — a `kraft login` / `UKC_TOKEN` env from kraftkit does **not** authenticate `unikraft`; you must run `unikraft login` (see below).
  The new CLI writes its own profile under `~/.config/unikraft`, kraftkit uses `~/.config/kraftkit`,
- **packaging split from deploy** — `unikraft build --output <ref>` publishes an image, then `unikraft run --image <ref>` deploys it (kraftkit did both in one `kraft cloud deploy`).

If you see `kraft`, `UKC_TOKEN`, `kraft cloud`, or `--rollout` in a config or runbook, it is the legacy CLI — translate it to the new commands here.

## Important: running commands

When working with `unikraft` commands:

1. **Show the command first** — display it in a copy-paste code block before running it.
1. **Authenticated (cloud) commands need a login** — anything hitting Unikraft Cloud (`run`, `instances`, `services`, `images`, `metros`, `api`, a publishing `build`) fails with `profile not setup` until `unikraft login` has run in that environment.
   The AI's shell may not be logged in; if a command returns `profile not setup`, hand it to the developer to run.
1. **Let the developer run it** when a login or a real deploy is involved.

## Installation

- **CI (GitHub Actions):** `unikraft/setup-action@v1` — installs the CLI and logs in headlessly.
  Inputs: `version` (`latest`/`stable`, `dev`/`staging`, or a release tag), `token` (login token), `organization`.
  It downloads the release asset `unikraft-cli_<version>_<os>_<arch>.tar.gz` from `unikraft-cloud/cli`.

  ```yaml
  - uses: unikraft/setup-action@v1
    with:
      version: 0.4.1
      token: ${{ secrets.KRAFTCLOUD_TOKEN }}
      organization: my-org
  ```

- **Local:** `unikraft upgrade` self-updates an installed CLI.
  Otherwise pull the tarball for your platform from <https://github.com/unikraft-cloud/cli/releases> (it holds the `unikraft` binary at its root).

- **Nix:** the CLI is not in nixpkgs (only the legacy `kraft` is), but the Unikraft team maintains a NUR flake at `github:unikraft/nur` exposing `packages.<system>.{unikraft-cli, unikraft-cli-staging, kraftkit}` (+ an overlay).
  Add it as a flake input and take `unikraft-cli` — or `unikraft-cli-staging` while the stable attr is being fixed upstream.

## Authentication

The new CLI reads the token from a **file or stdin**, not an env var, and associates the session with an organization.

```bash
# From a file:
unikraft login --token /path/to/token --organization my-org

# From stdin (no token on disk / no token in argv):
printf '%s' "$UKC_TOKEN" | unikraft login --token - --organization my-org
```

`--organization` is resolved from the control plane if omitted.
A successful login stores a **profile** (named after the organization) under `~/.config/unikraft`.
Manage profiles with:

```bash
unikraft profile list          # profiles + their metros + which is active
unikraft profile use <name>    # switch active profile
```

Global flags / env available on every command: `--profile` (`$UNIKRAFT_PROFILE`), `--config` (`$UNIKRAFT_CONFIG`), `--timeout` (`$UNIKRAFT_TIMEOUT`), `--log-level` (`$UNIKRAFT_LOG_LEVEL`), `--log-type`, `--[no-]telemetry`.

### Metros

Most write operations take `--metro <m>`.
List them with `unikraft metros list`:

| Use (current)                     | Endpoint                             |
| --------------------------------- | ------------------------------------ |
| `fra`, `dal`, `sin`, `was`, `sfo` | `https://api.<metro>.unikraft.cloud` |

`unikraft metros list` also shows **legacy** metros `fra0`, `dal0`, `sin0`, `was1` on `api.<metro>.kraft.cloud` — these are the old kraft.cloud endpoints.
**Use the suffix-less names only**; treat any `<metro>0` as legacy.

> [!IMPORTANT]
> **Only the *create* verbs take `--metro`** — `unikraft run` and `unikraft services create`.
> The read/manage verbs (`instances list`/`get`/`delete`/`logs`, `images list`, `services get`) **reject `--metro`** with `unknown flag --metro`; they take a **bare name** (the logged-in profile spans metros) or a `--filter 'metro==<m>'`.
> This bites in CI: piping a failed `instances delete <name> --metro fra` through `|| true` silently skips the delete, and the next `run` then fails with "instance already exists".

## Command tree

```text
Commands
  unikraft run                 # Run an image as an instance (deploy)
  unikraft build               # Build a Unikraft project into a container image
  unikraft tui                 # Browse resources in a TUI

Resources
  unikraft metros              # List/inspect cloud metros
  unikraft quotas              # Show quota usage
  unikraft instances           # Manage instances  (aliases: instance, vm, vms)
  unikraft services            # Manage service groups (svc, svcs)
  unikraft volumes             # Manage persistent volumes (vol, vols)
  unikraft certificates        # Manage TLS certificates (cert, certs)
  unikraft images              # Manage images (image, img, imgs)

Utilities
  unikraft api <endpoint>      # Authenticated raw REST call (e.g. /v1/instances)
  unikraft version             # Version info
  unikraft upgrade             # Self-update the CLI
  unikraft completion          # Shell completions

Config
  unikraft login / logout      # Authentication
  unikraft profile             # get / list / use
  unikraft config              # Manage CLI configuration
```

## Build (package an image)

```bash
# Build the project in the current directory and PUBLISH to the registry.
# The <org> prefix is your organization (see `unikraft profile list`).
unikraft build . --output <org>/my-app:latest

# Build and save to a LOCAL OCI archive (no publish):
unikraft build . --output ./dist/my-app.oci.tar

# Build-time inputs and secrets:
unikraft build . --build-arg VERSION=1.2.3 --secret id=npm,src=$HOME/.npmrc
```

Flags: `-o/--output`, `--build-arg`, `--no-cache`, `--secret`, `--ssh`, `--insecure`, plus the global `--timeout=<duration>`.
The input path defaults to `.`.

## Run (deploy an instance)

```bash
# Deploy a new instance exposing an HTTPS service:
unikraft run --metro fra --image <org>/my-app:latest \
  -p 443:8080/http+tls -p 80:443/http+redirect \
  -m 2048M --restart on-failure -e NODE_ENV=production

# Preview without deploying:
unikraft run --metro fra --image <org>/my-app:latest --dry-run
```

Common flags: `--metro`, `--image`, `-n/--name`, `-e/--env`, `-m/--memory` (binary units, e.g. `2048M`), `--vcpus`, `-p/--publish` (`<src>:<dest>[/<handlers>]`), `--domain`, `--service` (attach to an existing service group), `--restart` (`on-failure` …), `--scale-to-zero`, `-v/--volume`, `--replicas`, `--rm`, `--follow`, `--dry-run`.

> Memory units changed from legacy kraftkit: `-M 2048` (MiB) → `-m 2048M`.

## Instances, services, images

```bash
unikraft instances list                    # or: ls
unikraft instances get <name>              # inspect
unikraft instances logs <name>             # console output
unikraft instances wait <filter>           # block until instances match (readiness gate)
unikraft instances delete <name>           # remove
unikraft instances start|stop|suspend|restart <name>

unikraft services list                     # service groups (load balancers)
unikraft services get <name>
unikraft services create …                 # create a named service group
unikraft services edit <name>              # change its config / instance set

unikraft images list                       # image refs + digests (--filter 'ref~="my-app"')
unikraft images build                      # same engine as `unikraft build`
unikraft images delete <ref>
```

`volumes` (create/attach/detach/clone/import/…) and `certificates` (create/get/wait/…) follow the same verb pattern.

> [!WARNING]
> **Reading an instance dumps its secrets.**
> `instances list -o json`/`-o yaml` and `instances get` (without `-f`) return the instance's full `runtime.env` — including secret values such as `DATABASE_URL` — in **cleartext**.
> When you only need identifiers, field-limit the output: `unikraft instances list -f name -o quiet` (names only, no env).
> Never pipe an un-limited instance read into shared logs, CI output, or an issue.

## Rolling updates (zero-downtime)

The new CLI has **no single `--rollout` flag** (kraftkit's `kraft cloud deploy --rollout remove_sequential`).
A zero-downtime swap of an instance behind an existing service group is composed from primitives:

1. `unikraft run --service=<group> --image <new-ref> …` — start a **new** instance and attach it to the service group that owns the domain/ports (do **not** re-pass `-p`/`--domain`; the group owns those).
   Omit `--name` so the new instance gets a unique name and can run alongside the old one.
1. `unikraft instances wait <filter>` — wait until the new instance is ready.
1. `unikraft instances delete <old-name>` — remove the previous instance.

Confirm the exact recommended sequence (and whether a built-in rollout exists in your CLI version) with `unikraft run --help` / the Unikraft team before relying on it for a production deploy.

> [!IMPORTANT]
> **Check quota headroom before a rolling update.**
> The overlap briefly runs two instances, so it needs ~2× the instance's memory + vCPUs at once.
> Run `unikraft quotas [--metro <m>]` first (optional `--metro`, no subcommand): if adding a second instance would exceed the memory or instance cap, fall back to delete-then-recreate (accepting a brief gap) or request a quota bump.
> Near the cap, a rolling `run` fails with a quota error mid-deploy.

## Kraftfile (spec v0.7)

Keep the `Kraftfile` (and the `Dockerfile` it references) **at the build context root** — the rootfs path can't reach parent directories, and the Docker build needs the repo root as context to `COPY` sibling folders.

```yaml
spec: v0.7
name: my-server
# base-compat runs the node binary bundled in the image.
runtime: base-compat:latest

labels:
  cloud.unikraft.v1.instances/scale_to_zero.policy: "off"

rootfs:
  source: ./Dockerfile
  format: erofs
cmd: ["/usr/local/bin/node", "/app/dist/main.mjs"]
```

## Deployment example (build once, deploy the image)

```bash
# 1) Log in (once per environment).
printf '%s' "$UKC_TOKEN" | unikraft login --token - --organization my-org

# 2) Build + publish a versioned image.
unikraft build . --output my-org/my-app:2026.7.9 --timeout 10m

# 3) First deploy — create the service group, then run into it.
unikraft services create --name my-app \
  --domain my-app.example.com 443:8080/http+tls 80:443/http+redirect
unikraft run --metro fra --service my-app --image my-org/my-app:2026.7.9 \
  -m 2048M --restart on-failure -e NODE_ENV=production

# 4) Subsequent deploys — see "Rolling updates" above.

# Verify:
unikraft instances list
curl -sf https://my-app.example.com/health
unikraft instances logs my-app-<suffix>
```

## Troubleshooting

| Symptom                                   | Cause / fix                                                                                   |
| ----------------------------------------- | --------------------------------------------------------------------------------------------- |
| `profile not setup`                       | Not logged in — run `unikraft login` (kraftkit's login/`UKC_TOKEN` doesn't count).            |
| `unknown flag --metro` on `*/list`        | List/get subcommands scope by profile/`--filter`, not `--metro`.                              |
| Image builds but instance won't boot      | Check `runtime`, the `cmd` path, and that env vars the app needs are passed with `-e`.        |
| `504 Gateway Timeout`                     | Scale-to-zero cold start — set the scale-to-zero policy `off` for latency-sensitive services. |
| rootfs / `COPY` build failures            | Keep the Kraftfile + Dockerfile at the build-context root; no `../` in the rootfs path.       |
| Legacy `fra0`/`kraft.cloud` host in a ref | Replace with the suffix-less metro (`fra`, `api.fra.unikraft.cloud`).                         |

______________________________________________________________________

## Unikraft Cloud REST API (v1)

The CLI wraps a REST API.
Reach for it via `unikraft api <endpoint>` (uses the logged-in profile), or call it directly with a bearer token for automation.

```bash
unikraft api /v1/instances                       # via the CLI (logged-in profile)

curl https://api.fra.unikraft.cloud/v1/instances \
  -H "Authorization: Bearer $UKC_TOKEN"          # raw, any HTTP client
```

- Base URLs are metro-scoped: `https://api.<metro>.unikraft.cloud/v1` for `fra`, `dal`, `sin`, `was`, `sfo`.
  **Never** use a `<metro>0` legacy host.
- Every response uses the `{status, message, data, errors, op_time_us}` envelope; bulk endpoints return per-item `status`.

Full endpoint tables, curl examples, and gotchas: [references/api-v1.md](references/api-v1.md).

______________________________________________________________________

## Examples repository

Reference examples (Kraftfiles + Dockerfiles for many runtimes): <https://github.com/unikraft-cloud/examples>.

