While incus has a fairly large feature surface, only a small subset of it actually matters for day to day operations in most scenarios. This guide will cover the most common usage scenarios without delving too deeply into complex topics like storage, networking or clustering. Think of it as a speedrun to getting productive in under an hour.
Instances, images & registries
Incus instances come in three varieties: OCI containers (effectively what docker/podman use), classic virtual machines and system containers (think "vm in a container, still sharing host kernel").
To run a single application, use OCI containers. You can add the official docker registry to use its container image directly:
incus remote add docker https://docker.io --type=ociIn addition, the official images.linuxcontainers.org registry should also be configured by default, offering incus images for system containers and virtual machine instances.
Note that images are incompatible with instances of different types, for example a VM image cannot be used to launch a system container instance or vice versa.
To find system container or VM images, search the default images registry:
incus image list images:The output will look similar to this (heavily reduced):
+----------------------------------+--------------+--------+----------------------------------------+--------------+-----------------+-----------+-----------------------+
| ALIAS | FINGERPRINT | PUBLIC | DESCRIPTION | ARCHITECTURE | TYPE | SIZE | UPLOAD DATE |
+----------------------------------+--------------+--------+----------------------------------------+--------------+-----------------+-----------+-----------------------+
| debian/13 (7 more) | 0cc172535ed1 | yes | Debian trixie amd64 (20260812_05:24) | x86_64 | CONTAINER | 100.46MiB | 2026/08/12 02:00 CEST |
+----------------------------------+--------------+--------+----------------------------------------+--------------+-----------------+-----------+-----------------------+
| debian/13 (7 more) | a99665329f4c | yes | Debian trixie amd64 (20260812_05:24) | x86_64 | VIRTUAL-MACHINE | 347.21MiB | 2026/08/12 02:00 CEST |
+----------------------------------+--------------+--------+----------------------------------------+--------------+-----------------+-----------+-----------------------+
| debian/13/arm64 (3 more) | b9d1d41779f3 | yes | Debian trixie arm64 (20260812_05:24) | aarch64 | VIRTUAL-MACHINE | 345.17MiB | 2026/08/12 02:00 CEST |
+----------------------------------+--------------+--------+----------------------------------------+--------------+-----------------+-----------+-----------------------+
| debian/13/arm64 (3 more) | b38272d6252f | yes | Debian trixie arm64 (20260812_05:24) | aarch64 | CONTAINER | 97.86MiB | 2026/08/12 02:00 CEST |
+----------------------------------+--------------+--------+----------------------------------------+--------------+-----------------+-----------+-----------------------+
| debian/13/armhf (3 more) | a4046595c49c | yes | Debian trixie armhf (20260812_05:24) | armv7l | CONTAINER | 89.85MiB | 2026/08/12 02:00 CEST |
+----------------------------------+--------------+--------+----------------------------------------+--------------+-----------------+-----------+-----------------------+
| debian/13/cloud (3 more) | 1e298e0b5f14 | yes | Debian trixie amd64 (20260812_05:24) | x86_64 | CONTAINER | 132.25MiB | 2026/08/12 02:00 CEST |
+----------------------------------+--------------+--------+----------------------------------------+--------------+-----------------+-----------+-----------------------+
| debian/13/cloud (3 more) | b5c99e3c5c14 | yes | Debian trixie amd64 (20260812_05:24) | x86_64 | VIRTUAL-MACHINE | 377.21MiB | 2026/08/12 02:00 CEST |
+----------------------------------+--------------+--------+----------------------------------------+--------------+-----------------+-----------+-----------------------+
| debian/13/cloud/arm64 (1 more) | 8ccfb9ddda9d | yes | Debian trixie arm64 (20260812_05:24) | aarch64 | CONTAINER | 128.84MiB | 2026/08/12 02:00 CEST |
+----------------------------------+--------------+--------+----------------------------------------+--------------+-----------------+-----------+-----------------------+
| debian/13/cloud/arm64 (1 more) | c9d198a8b2e9 | yes | Debian trixie arm64 (20260812_05:24) | aarch64 | VIRTUAL-MACHINE | 375.22MiB | 2026/08/12 02:00 CEST |
+----------------------------------+--------------+--------+----------------------------------------+--------------+-----------------+-----------+-----------------------+
| debian/13/cloud/armhf (1 more) | 8f7a1eefc4ca | yes | Debian trixie armhf (20260812_05:36) | armv7l | CONTAINER | 120.20MiB | 2026/08/12 02:00 CEST |
+----------------------------------+--------------+--------+----------------------------------------+--------------+-----------------+-----------+-----------------------+
| debian/13/cloud/riscv64 (1 more) | d5d66edd142f | yes | Debian trixie riscv64 (20260812_05:37) | riscv64 | CONTAINER | 125.37MiB | 2026/08/12 02:00 CEST |
+----------------------------------+--------------+--------+----------------------------------------+--------------+-----------------+-----------+-----------------------+
| debian/13/riscv64 (3 more) | 7a0cc71fad42 | yes | Debian trixie riscv64 (20260812_05:24) | riscv64 | CONTAINER | 93.90MiB | 2026/08/12 02:00 CEST |
+----------------------------------+--------------+--------+----------------------------------------+--------------+-----------------+-----------+-----------------------+Image names generally follow the format <os-name>/<version>/<metadata/architecture>. Images can have multiple alias names, so for example debian, debian/13, debian/13/cloud and debian/13/cloud/amd64 could all point to the same image, showing up in brackets as (3 more) in the output.
The previous output contains entries for both system containers and virtual machines. Use the type= filter to get only specific results:
incus image ls images: debian type=container
incus image ls images: debian type=virtual-machineKnowing the name or alias for an image is enough to launch an instance from it, or alternatively the fingerprint can be used as well.
Creating instances
The chosen system image typically defines what type of instance you launch. If you do not specify the type and an image with the same name exists for multiple types, it defaults to creating a system container.
You can either use incus init to just create an instance, or incus launch to create and immediately start it.
The syntax remains the same across instance types:
# OCI container
incus launch docker:hello-world --console
# system container
incus launch debian/13 --console
# virtual machine
incus launch debian/13 --console --vmNote the --console used to connect immediately to the instance text console in the same command.
If you want to create an instance that self-deletes when stopped, add the --ephemeral flag to init or launch commands.
An instance can be created with default hardware limits mirroring that of common cloud provider offerings by specifying a type. For example, to get a debian 13 instance comparable to AWS t2.medium (2 cores, 4GiB memory), run:
incus launch debian/13 -t aws:t2.mediumSee incus instance types reference for a complete list of available types and their resource limits.
Installing VM instances from ISO
In order to install virtual machines from an installer ISO, for example to create Windows instances, you need to first create an empty VM instance:
incus init --empty --vm my-vmThen attach the ISO image as a virtual disk, faking a cdrom drive:
incus config device add my-vm cdrom disk \
readonly=true \
boot.priority=10 \
io.bus=usb \
source=/path/to/installer.isoFor boot mode, the default is UEFI with secure boot, which you can set explicitly if you like:
incus config set my-vm security.secureboot=true security.csm=falseFor ISO images requiring legacy boot, reverse those properties:
incus config set my-vm security.secureboot=false security.csm=trueFinally, start the instance. For most installers, you want a graphical console, so attach it during start:
incus start my-vm --console=vgaNote the options on the fake cdrom drive device: It is intentionally made read-only since ISO images are not supposed to be written to, sets a higher boot priority and registers itself as a USB, which helps guests better handle changing the inserted ISO image at runtime and hot-swap behavior.
Starting, stopping and connecting to instances
You can list all existing instances:
incus lsStarting an instance can be either detached:
incus start my-instanceor alternatively attach to the text or graphical console output directly after starting:
incus start my-instance --console #text console
incus start my-instance --console=vga # graphical consoleTo exit the text console, press ctrl+a, then q. Detaching from console does not turn off the instance.
To later reattach to the console, use
incus console my-instance # text console
incus console my-instance --type=vga #graphical consoleWhen you want to stop an instance, you can either do it gracefully:
incus stop my-instanceOr force an immediate (potentially unclean) shutdown:
incus stop -f my-instanceIf your instance somehow ends up in an error state, you can always force stop it to return to a normal stopped state, but may lose snapshot/runtime state.
Instance property configuration
Instances are configured through simple property=value pairs.
For example, you can tell incus that an instance is running a Windows OS:
incus config set my-vm image.os=windowsOr perhaps limit the instance to 4 CPU cores and 6GiB memory:
incus config set my-vm limits.cpu=4 limits.memory=6GiBOr allow nested virtualization:
incus config set my-vm security.nesting=trueCheck the instance options documentation for a complete list of all properties.
Instance configuration can be checked either in a minimal format:
incus config show my-vmSample output:
architecture: x86_64
config:
image.architecture: amd64
image.description: Debian trixie amd64 (20260521_05:24)
image.os: Debian
devices: {}
ephemeral: false
profiles:
- default
stateful: false
description: ""Or fully expanded with information on devices and all set metadata:
incus config show my-vm --expandedSample output:
architecture: x86_64
config:
image.architecture: amd64
image.description: Debian trixie amd64 (20260521_05:24)
image.os: Debian
devices:
eth0:
name: eth0
network: incusbr0
type: nic
root:
path: /
pool: default
type: disk
ephemeral: false
profiles:
- default
stateful: false
description: ""Note how default devices (root disk, NIC) were not detailed without passing --expanded.
Attaching, overriding and configuring devices
Devices are also just part of instance configuration. Some of them are supplied by the default profile (root disk and default ethernet adapter), while you can create others if you like.
Instances will almost always receive default devices from the default profile. They are only metadata before the first start, so if you want to change them before starting the instance, you need to override their data.
There are two frequently recurring needs for this.
Increasing the root disk size:
incus config override my-instance root size=55GiBDetaching the default network adapter:
incus config override my-instance eth0 attached=falseFor devices not coming from a profile, or after an instance has been started at least once, instead use normal set syntax:
incus config set my-instance root size=55GiB
incus config set my-instance eth0 attached=falseCreating a new device can be done in a single command, in this example attaching a .qcow2 disk image as a secondary disk for the instance, mounted at /disk:
incus config device add my-instance mydisk disk \
source=/path/to/disk.qcow2 \
path=/diskOr instead create a new incus-managed virtual disk and attach it at /data inside the instance:
incus storage volume create default mydisk size=11GiB
incus config device add my-instance mydisk \
pool=default \
source=mydisk \
path=/disk2Or bind-mount a host directory into the instance, this time read-only:
incus config device add my-instance my-bindmount disk \
source=/path/on/host \
path=/path/in/instance \
readonly=trueThere are other device types like network interfaces, TPM or GPUs. See incus device types for a list and details on each. Also check the disk device documentation, as it contains special sources like agent:configand cloud-init:config for advanced incus features.
Storage gotchas
The default storage pool driver created automatically during installation is often Btrfs. This driver is great for desktop and end user machines as it has tiny overhead while offering first-class support for subvolume quotas and snapshots.
However, the default pool is also restricted to 30GiB max size (potentially less, depending on free disk space at the time of installation). Instances receive 10GiB of sparse disk space by default, meaning you can over-provision storage space beyond your pool size, but will eventually run into the total storage limit.
You will very likely want to increase the default storage pool to a more sane value if you have the disk space for it:
incus storage set default size=100GiBAnother thing to be aware of is that Btrfs quotas are not visible as hard limits to instances. For example if your default storage pool is 100GiB in size and you create an instance with a 10GiB root disk, commands like df -h will show 100GiB instead of the expected 10GiB, but the instance still cannot use more than 10GiB of space on that disk.
This is a Btrfs-specific issue; switching to ZFS will show virtual disk size properly - at the cost of significantly more memory consumption. Try to stay clear of LVM pools as you would need to manage loop devices or hardware independently, incus will automate this for Btrfs/ZFS.
Profiles and projects
Profiles are a collection of default configuration settings and devices that can be reused across many instances. When not specifying a profile during init or launch, the default incus profile is automatically used, creating a default network interface card device and a root disk with 10GiB of storage space.
Note that devices contained in profiles are theoretical until the first instance start, where they are actually created. If you want to change an instance device that comes from a profile, you have to use config override to alter it when theoretical, but the normal config set after first start.
You can also create a custom profile, for example to set default cpu and memory limits, and make it create a TPM device:
incus profile create limited
incus profile set limited limits.cpu=2 limits.memory=2GiB
incus profile device add limited tpm tpmYou can then either attach the profile when creating a new instance:
incus launch images:debian/13 my-instance -p limited
incus init images:debian/13 my-instance -p limitedOr alternatively assign a profile to an existing instance:
incus profile assign my-instance profile1,profile2Note that an instance can have multiple profiles assigned, and they replace previously defined values in order of assignment.
Projects are like profiles but on a larger scope. They act as isolated namespaces with allocated resources, like a total number of cpus, memory or storage space shared across all instances within the project.
They can be used for simple instance grouping or as a self-service mechanism, allocating a specific amount of hardware to an internal team that they can split up into instances as they see fit.
When not specifying a project, the default project is used, which imposes no limits.
Incus agent
Incus allows interacting with instances without setting up authentication over SSH or similar, either to manage disk contents or execute commands.
For OCI or system containers, this is done automatically - virtual machines need to install the incus-agent service inside the guest instances to support the feature.
Note that nearly all container and VM images from the default images: registry have incus-agent installed already, with the notable exception of BSD-based instances.
If you need to install the incus-agent manually in a guest (BSD/Windows, or a VM manually installed from an ISO), you need to make sure to set the correct OS type under image.os, then attach the special agent:config disk source to get a matching installer inside the guest:
incus config set my-instance image.os=windows
incus config device add my-instance cdrom disk \
source=agent:config \
io.bus=usb \
readonly=trueNote the readonly and usb bus config to help the guest identify the disk as a non-writable removable media that may be hot-plugged.
Access the mounted disk contents inside the guest and execute the contained install script.
Instance shell and file access
Once you have a container, or a VM with incus-agent running, you can easily execute commands on the host with exec:
incus exec my-instance -- bashNote how the incus command and arguments are separated from the command you intend to run inside the guest by -- to prevent confusion.
By default, exec allocates a pseudo-terminal when running from an interactive shell to support common terminal behavior like stopping running programs with ctrl+c or command completion.
If you need to run "dumb" commands without terminal emulation (e.g. when running commands inside Windows hosts which are incompatible with interactive sessions), pass the -T flag:
incus exec my-windows-instance -T -- powershell.exeAccessing files can be done without running programs at all, with three different mechanics.
First, you can mount an entire directory from the instance onto the host to access it with any local host tooling you choose:
incus file mount my-instance/data /mnt/dataThe directory /data from my-instance is now available under /mnt/data on the host until you stop the command by pressing ctrl+c or close the terminal.
The second way is to pull directories/files from the instance to the host, or push them from the host to the instance disk.
The syntax is similar:
incus pull my-instance/data/test.txt /path/on/host/test.txt
incus push my-instance/data/test.txt /path/on/host/test.txtThe common flags -p (autocreate missing dirs) and -r (transfer dirs recursively) are supported, just like with mkdir/cp
Finally, files can be accessed with more fine-grained control through file create, file edit and file delete. This is almost always overkill outside of scripts, prefer mount or push/pull for human interaction.
Be careful when transferring text files between linux hosts and Windows guests, as Windows expects CR+LF to signal newlines, but most linux tooling is set to LF only. Run unix2dos before saving/transferring files to be safe.
Snapshots, pausing and recovery
Backups are mostly covered through snapshots. You can either create a stateless snapshot:
incus snapshot create my-instance snap1or a stateful snapshot, which preserves the runtime state (memory contents, network state etc) as much as possible:
incus snapshot create my-instance snap2 --statefulSnapshots can be restored with:
incus snapshot restore my-instance snap1You can explicitly restore with or without runtime state by using the --diskonly or --stateful flags, respectively
Snapshots can optionally expire automatically, enabling daily blind backup cronjobs creating new snapshots on a schedule without managing their lifetime:
incus snapshot create my-instance snap-jan-1-1990-12-00 --expiry 7dSince snapshots are bound to the incus host and cannot be exported directly, you have to rely on import and export for backups that are meant for use or transfer off the host machine:
incus export my-instance /my-instance.tar.gzExporting includes all associated snapshots by default, use --instance-only to omit them.
Then import the instance backup on any incus host with:
incus import /my-instance.tar.gzSimilar to instance creation, you can also use -c to change configuration, -d to alter devices and -s to set the storage pool to use.
Creating and sharing images
Since incus is based on images, you can also create your own. You can turn any instance into an image, but remember that only types of the same type can be created from it (VM images cannot be used to create system containers or vice-versa).
Configure the instance until it is in the state you would like to use as an image for new instances, then export it:
incus publish my-instance --alias my-imageThis creates a new local image named my-image. You can add multiple aliases to the same image.
Note that publish does not make the image publicly available unless you also provide the --public flag.
You can also create an image from an instance snapshot instead of current instance state:
incus publish my-instance/snap1 --alias my-snapshot-imageConfigured devices and metadata are preserved in the image, so you do not have to set boot options or hardware limits again on any instance created from the image (unless you want to override them).
Creating an instance from the image requires only the alias name or fingerprint:
incus init my-image
incus launch my-imageImages are stored in the default storage pool for the current project and are not visible across projects or hosts without exporting.