Related: user guide, backend support, Incus setup, libvirt setup, KubeVirt and Proxmox setup
This guide starts from what you see: an error message or a symptom. Each entry gives the cause and the fix. The error texts are the exact texts that Corral prints. Search this page for the text of your error.
corral doctor checks every context that you configured, and every peer.
It shows one section for each context:
local [qemu]
β QEMU (local backend) qemu-system-x86_64 found (probe 3ms)
! KVM acceleration no /dev/kvm access β local VMs fall back to slow emulation (...)
β qemu-img not installed β local VM disk creation needs it (...)
| Mark | Meaning |
|---|---|
β |
The check passed |
! |
A warning. The feature that the check names does not work, but Corral can still operate |
β |
A required check failed. corral doctor exits with status 1 |
(fixable) |
corral doctor --fix can repair this item |
Useful forms of the command:
corral doctor # every context and every peer
corral doctor --context NAME # one context only
corral doctor --fix # repair the fixable KubeVirt items, then check again
corral doctor --suggest # scan this host for backends; changes nothing, exits 0
corral setup # scan, then save a context for each backend that works--fix changes only the default Kubernetes context. It installs KubeVirt,
CDI and metrics-server when they are missing, and sets the KubeVirt feature
gates and the update strategy. It does not change the local host, Incus,
libvirt or Proxmox.
When a context fails, the other contexts still show. corral list behaves
the same way, and prints warning: context NAME unavailable: ... for each
context that it cannot reach.
| Check | If it fails |
|---|---|
QEMU (local backend) |
Install the qemu-system-x86 package. Needed only for local VMs |
KVM acceleration |
Add yourself to the kvm group and log in again, or enable virtualization in the firmware. Without KVM, local VMs use slow software emulation |
Nested virtualization |
Needed only when a VM runs its own VMs. The check text gives the modprobe command |
Tailscale CLI |
Install Tailscale. Local VMs bind VNC and SSH to the Tailscale address of the host |
virtctl CLI |
Needed only for KubeVirt consoles and SSH |
VSOCK host support |
Needed only for --vsock. Load vhost_vsock, and install a socat with VSOCK support and ssh-keygen |
swtpm (TPM emulation) |
Needed only for --tpm. Install swtpm |
OVMF firmware |
Install edk2-ovmf or ovmf. UEFI guests do not boot without it |
socat |
Needed for the VNC bridge, corral screenshot and --vsock. Install socat |
qemu-img |
Install qemu-img or qemu-utils. Local VM creation needs it |
VSOCK host support, swtpm, OVMF firmware, socat and qemu-img are
required checks. If one of them fails, corral doctor exits with status 1,
also when you do not use that feature.
| Check | If it fails |
|---|---|
Cluster reachable |
The Kubernetes API did not answer. Look at your kubeconfig, the context, your VPN and your credentials |
KubeVirt installed, CDI installed, metrics-server |
Run corral doctor --fix, or see the setup guide |
LiveUpdate rollout strategy, LiveMigrate workload updates, Feature gate: ... |
Run corral doctor --fix. If an operator such as HCO manages the KubeVirt CR, set the values in the HyperConverged CR |
Default StorageClass, Expandable StorageClass, VolumeSnapshotClass |
Install a CSI driver with those features. See the setup guide |
Migratable storage (RWX) |
VMs on a storage class without ReadWriteMany cannot live-migrate |
GPU/PCI passthrough |
Enable IOMMU and bind the device to vfio-pci on its node |
- Incus: see Incus setup.
- libvirt: see libvirt setup.
- Proxmox:
API tokenfails when the context has no token. SetCORRAL_PROXMOX_TOKEN, or addproxmox.tokento the context. - Proxmox:
API token acceptedfails when the cluster refuses the token. The role of the token needsVM.AuditandVM.PowerMgmt. - Proxmox:
TLS trustfails when your system does not trust the server certificate. Pin it withproxmox.fingerprint.
Corral writes structured logs to stderr. --log-level debug logs each
command that Corral runs (kubectl, virtctl, incus, virsh and the
others) with its duration and exit code. Corral replaces tokens,
passwords, Tailscale auth keys and Proxmox API tokens with [REDACTED]
before it writes the record. It logs the size of the input of a command,
not the input. The input can hold manifests and cloud-init passwords.
corral list --log-level debug
corral list --log-level debug --log-format json 2>corral.log| Flag | Variable | Values |
|---|---|---|
--log-level |
CORRAL_LOG_LEVEL |
debug, info (default), warn, error |
--log-format |
CORRAL_LOG_FORMAT |
text (default), json |
--log-file |
CORRAL_LOG_FILE |
A file that gets the log instead of stderr. Corral creates it with mode 0600 |
The TUI draws on the terminal, so it always writes its log to a file. The
default file is ~/.cache/corral/tui.log ($XDG_CACHE_HOME/corral when
that variable is set).
corral web gives each HTTP request an ID and sends it back in the
X-Request-ID response header (see Request IDs). Each
log record that the request causes has the ID in its request_id field.
These records include the backend commands, the Proxmox API calls, the
requests to peers, and the background tasks. A metrics collection run has an
ID that starts with collect-. To find all the records of a failed request,
search the log for its ID:
corral web --log-level debug --log-format json 2>corral-web.log
grep '"request_id":"5f0c9e2a7b1d4c3e"' corral-web.logAt the info level, the log of corral web shows the tasks that start and
end. At the warn level, it shows server errors, failed tasks and contexts
that Corral cannot list. Local QEMU commands, console streams and some
package-level KubeVirt helpers do not have the request ID yet.
Use these commands and files too.
| Tool | What it shows |
|---|---|
corral list |
Every instance, its backend, status and context. Also a warning for each context that failed |
corral info NAME |
The raw state from the backend (kubectl get vm -o json, incus info, virsh dominfo, or the local metadata.json) |
corral logs NAME |
KubeVirt: the compute container log of the virt-launcher pod. Local QEMU: the systemd journal of the VM unit |
corral logs NAME --serial --tail 100 |
Local QEMU: the serial console of the guest, also when the guest never reaches SSH |
corral diagnose NAME --bundle-dir ./diag |
Local QEMU: serial log, screenshot, journal, QMP status and host checks in one directory |
corral screenshot NAME |
Local QEMU: a PNG of the screen. --require-paint fails when the screen is blank |
corral qmp NAME query-status |
Local QEMU: the raw state from the QEMU monitor |
--json |
Machine-readable output for corral list and other commands |
On Incus, corral logs prints the console log of the instance
(incus console --show-log REMOTE:NAME). libvirt keeps no guest log that
Corral can read, so corral logs refuses libvirt domains. Use
virsh -c URI console NAME.
Files on the host where Corral runs:
| Path | Contents |
|---|---|
~/.config/corral/config.yaml |
Contexts, the default backend, and other settings ($XDG_CONFIG_HOME/corral when that variable is set) |
~/.local/share/corral/registry.json |
The backend, namespace, user and password that Corral saved for each VM |
~/.local/share/corral/vms/NAME/ |
Local QEMU VM: disk.qcow2, serial.log, metadata.json (address and SSH port), qmp.sock |
~/.local/share/corral/vms/NAME/qemu.log |
Local QEMU output when the host has no systemd user session |
~/.config/systemd/user/corral-NAME.service |
The systemd user unit of a local QEMU VM |
| Error | Cause and fix |
|---|---|
instance "NAME" does not exist |
No context has an instance with that name or ID. Run corral list. If a context shows a warning, fix that context first |
instance name "NAME" is ambiguous; use one of the fully-scoped IDs shown by corral list |
Two contexts have an instance with that name. Use the ID from the id: line of corral list |
no VMs found. Create one: corral create <name> |
You gave no name, and the inventory is empty |
unknown context "NAME" |
corral context use takes a context name, not a URI or a remote. Add the context first with corral context add |
qemu is local and does not take a context |
Do not give --context to corral context add --backend qemu |
cluster unreachable (a recent kubectl call timed out; retrying in ...) |
A kubectl call timed out, so Corral skips the cluster for 15 seconds. Look at the cluster, or raise CORRAL_KUBECTL_TIMEOUT (default 5s) |
A --context value that is not a context name is not an error. Corral gives
the value to the backend as a kube context, an Incus remote or a libvirt URI.
A misspelled name can therefore point Corral at a target that does not
exist. Run corral context list to see the names.
| Error | Cause and fix |
|---|---|
VM "NAME" already exists (backend: ...). Use --force to overwrite |
Corral already knows that name. Pick a new name, or add --force |
QEMU not found. Install: brew install qemu |
Corral cannot find both qemu-system-x86_64 and qemu-img. Install QEMU from your distribution or Homebrew |
UEFI requested: UEFI firmware not found on host (install edk2-ovmf or ovmf) |
Install OVMF, or set CORRAL_OVMF_CODE and CORRAL_OVMF_VARS to its files. Use --firmware bios for a BIOS guest |
vsock requested but not available: ... |
See the VSOCK host support check of corral doctor |
swtpm not found: install the 'swtpm' package |
Install swtpm, or create the VM without --tpm |
unknown image "NAME" β see corral images |
--image takes a name from corral images on KubeVirt. Incus takes an Incus image instead |
bootc build needs the bootc plugin: corral plugin install bootc |
Install the plugin, then try again |
no SSH public key found (needed for bootc VM) |
Make a key with ssh-keygen. A bootc VM gets your public key for root |
the Incus backend creates from an Incus image; use --image ... |
See Incus setup |
libvirt create needs --iso or --qcow |
See libvirt setup |
corral create without a backend flag goes to the default backend. This is
CORRAL_DEFAULT_BACKEND, then default.backend in the configuration file,
then qemu. The KubeVirt source flags --image, --container-disk,
--import and --pvc select KubeVirt when the default is qemu. Run
corral config get-default-backend to see the value.
Corral does not stop when /dev/kvm is not available. It starts QEMU with
software emulation (TCG), which is much slower. Run corral doctor. If
KVM acceleration shows !, add yourself to the kvm group and log in
again.
corral list shows the import state of a KubeVirt disk:
| State | Meaning |
|---|---|
β queued |
CDI has not started the import. The PVC can wait for a node, or the storage class cannot provision |
β importing or β NN% |
CDI copies the image |
β Failed |
The import stopped with an error |
β ready |
The disk is ready |
Corral has no timeout for an import. If the state does not change, look at
the CDI importer pod and the PVC in the namespace of the VM (default
corral-vms):
kubectl get datavolume,pvc,pods -n corral-vms
kubectl describe datavolume NAME -n corral-vmsIf the VM starts but you cannot log in, read the console of the guest first.
- Local QEMU:
corral logs NAME --serialshows the console from the firmware on.corral diagnose NAMElooks in it for a kernel panic or an emergency shell.corral screenshot NAMEshows the screen. - KubeVirt: open the console in the web UI, or run
corral viewer NAME. - Incus and libvirt: open the console in the web UI.
| Error | Cause and fix |
|---|---|
VM "NAME" does not exist (local QEMU) |
The systemd unit or the VM directory is missing. Run corral list, or create the VM again |
starting VM: ... (local QEMU) |
systemd could not start the unit. Run corral logs NAME |
QEMU exited immediately (...) |
QEMU stopped at start. The message ends with the last lines of qemu.log |
VM "NAME" has no recorded QEMU command β recreate it ... |
The host has no systemd user session, and an older Corral made this VM. Run corral create NAME --force ... |
no serial log β recreate the VM (corral create --force ...) to pick up console capture |
An older Corral made this VM. Make it again to get a serial log |
virtctl not found β install: brew install virtctl |
Install virtctl. KubeVirt start, stop, SSH and consoles need it |
corral ssh works differently on each backend:
| Backend | How corral ssh connects |
|---|---|
| Local QEMU | ssh to the Tailscale address of the host, on the port that QEMU forwards to port 22 of the guest |
| KubeVirt | virtctl ssh, through the Kubernetes API. The guest needs no open port |
| Incus | incus exec REMOTE:NAME -- bash. This is not SSH, and Corral ignores the SSH flags |
| libvirt | Not supported. Use ssh to the guest address |
| Corral peer | peer/NAME goes through the remote Corral |
The user is -u, then the user that Corral saved for the VM, then $USER,
then root. When you give -u, Corral saves it for the next time. The
password is --password, then the password that Corral saved. For a
KubeVirt VM, Corral saves the cloud-init password at create time.
| Error | Cause and fix |
|---|---|
VM "NAME" has no Tailscale IP β is Tailscale running? |
Tailscale did not answer when you created this local VM. Corral then bound VNC and SSH to 127.0.0.1. Use ssh -p PORT USER@127.0.0.1, with ssh_port from metadata.json. Or start Tailscale and create the VM again |
VM "NAME" has no forwarded SSH port β recreate it with this corral version |
An older Corral made this VM. Make it again |
ssh not found in PATH |
Install the OpenSSH client |
sshpass not found (needed for password auth) β install: brew install sshpass |
Password login needs sshpass. Install it, or use a key with -i |
VM "NAME" has no vsock β create with --vsock (or --vsock-cid) |
corral ssh --vsock needs a VM that you made with --vsock |
libvirt domain "NAME" has no discovered SSH address; ... |
Expected. Get the address with virsh domifaddr NAME and use ssh |
- KubeVirt. Cloud-init sets the password for the default user of the
image, for example
fedoraorubuntu. Corral uses$USERwhen you do not give-u. Trycorral ssh NAME -u fedora. - bootc. Corral puts your public key on the
rootaccount. Usecorral ssh NAME -u root. - The guest has no SSH server. Use the console in the web UI, or
corral typeandcorral keyon a local QEMU VM.
- Local QEMU VMs need the
tailscalecommand on the host. At create time, Corral readstailscale ip -4. VNC and SSH of the VM listen on that address only. - KubeVirt VMs get a tailnet Service only when the cluster has the
tailscaleIngressClass from the Tailscale operator. Without it, Corral skips that step.corral sshstill works throughvirtctl. corral configshows whether you setTS_AUTHKEYortailscale.auth_key. No create path uses the key today.
corral move runs a check before it changes anything. If the check fails,
Corral prints the reasons and this move was refused; nothing was changed.
| Reason | Fix |
|---|---|
no destination backend was given |
Add --to BACKEND |
the source and destination are both ... in the same context |
To move between nodes of one backend, use corral migrate (KubeVirt) |
containers cannot be moved between backends |
Move VMs only |
the ... backend cannot export a disk |
Corral cannot read the disk on this source backend |
the ... backend cannot receive a moved instance |
Pick another --to backend |
the guest boots via UEFI and the ... backend has no firmware path |
Install OVMF, or pick a destination with UEFI |
... has ... free and the disk needs up to ... |
Free space, or set --scratch to a larger directory |
... is a remote hypervisor and its disks are not on this filesystem |
Run Corral on the libvirt host, or use shared storage |
Run corral move NAME --to BACKEND --dry-run first. It prints the plan and
changes nothing. If the move fails after it starts, the error tells you
whether the source is intact. Corral stops the source, and it deletes the
source only when you give --delete-source.
These are the errors of the VDI broker (/api/vdi/, the Desktops screen,
and corral vdi connect). docs/vdi.md explains
the broker.
| Error | Cause | Fix |
|---|---|---|
the VDI broker is off (set CORRAL_VDI_BROKER=1) |
corral web runs without the broker |
Set CORRAL_VDI_BROKER=1 and restart corral web |
VDI self-service needs an authenticated identity (401) |
The request came without the identity headers | Connect through the Tailscale ingress or the corral-auth gateway |
a service identity cannot use VDI self-service (403) |
A Corral peer token sent the request | Use a user identity; peers cannot hold desktops |
the desktop belongs to another user (403) |
Another user holds the desktop | Use your own desktop, or ask an admin to release it |
pool has no free members (409) |
Users hold every member of the pool | Wait for a release, or ask an admin to add members |
no claimed desktop by that ID (404) |
The claim ended: a release or a reclaim | Claim the pool again |
the claim on this desktop expired (409) |
No broker renewed the Lease for an hour | Claim the pool again; the claim then renews |
the desktop is not running; start it, then connect (409) |
The member stopped | Select Start, or send POST .../start |
the desktop offers no console Corral can open (409) |
The VM has no RDP, graphics device, or serial console | Enable a console in the golden VM |
startError in a claim |
The member did not start; you keep the desktop | Fix the cause (often cluster capacity), then start it again |
A release task ends with the claim is kept; retry the release |
The stop or the recreate failed | Release again after you fix the cause |
| A desktop was released while you were away | The disconnect grace period or the maximum duration ran out | Claim again; see Sessions and reclaim |
Some commands work on KubeVirt only. On another backend they stop with
COMMAND is only supported for KubeVirt VMs. These commands are
migrate, scale, adddisk, rmdisk, addnic, lanservice,
snapshot, pause, unpause, export and template. corral clone
stops with cloning is only supported on KubeVirt VMs. See
backend support
for the gaps and for the native commands that you can use until then.
Some commands work on local QEMU only. On another backend they stop with
diagnose is only supported for QEMU VMs or a similar message. These are
diagnose, qmp, screenshot, type and key.
| Variable | Effect |
|---|---|
CORRAL_DEFAULT_BACKEND |
Backend for corral create without a backend flag |
CORRAL_KUBE_CONTEXT |
Kube context for kubectl and virtctl. --context sets it |
CORRAL_INCUS_REMOTE |
Incus remote. --context sets it for Incus |
CORRAL_LIBVIRT_URI |
libvirt URI. --context sets it for libvirt |
CORRAL_NAMESPACE |
KubeVirt namespace (default corral-vms) |
CORRAL_KUBECTL_TIMEOUT |
Timeout for each kubectl call (default 5s) |
CORRAL_LOG_LEVEL, CORRAL_LOG_FORMAT, CORRAL_LOG_FILE |
Log level, format and file. See Collect evidence |
CORRAL_OVMF_CODE, CORRAL_OVMF_VARS |
UEFI firmware files for local VMs |
CORRAL_PROXMOX_TOKEN, CORRAL_PROXMOX_TOKEN_<CONTEXT> |
Proxmox API token |
CORRAL_VDI_BROKER, CORRAL_VDI_NAMESPACES |
Turn on the VDI broker of corral web, and the namespaces it serves |
CORRAL_VDI_DISCONNECT_GRACE, CORRAL_VDI_MAX_SESSION |
The VDI reclaim policy (default 15m and no maximum) |
CORRAL_VDI_BROKER_URL |
Default --broker of corral vdi connect |
KUBECONFIG |
Kubeconfig file. When it exists, Corral adds a kubevirt context |
TS_AUTHKEY |
Tailscale auth key that corral config shows |
XDG_CONFIG_HOME |
Parent directory of the Corral configuration |
Why can't I SSH to my VM? Run corral list and look at the backend.
Then find the error in the SSH table. On libvirt, corral ssh is
not supported. On Incus, corral ssh uses incus exec.
How do I move a VM to another backend? Use corral move NAME --to BACKEND --dry-run, then run it again without --dry-run. The guest stops
during the move and gets a new MAC address. See Move.
How do I move a VM to another node? On KubeVirt, use
corral migrate NAME --node NODE. Other backends do not support this from
Corral yet.
Why does corral list not show my cluster? A context failed. The
warning line names it. Run corral doctor --context NAME.
Why does my Incus container not show in corral list? Corral shows
Incus containers in corral ct list. corral list shows VMs.
Can I try Corral without a cluster? Yes. Add --demo to any command,
for example corral web --demo.