This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Incus

Inucs is a light-weight manager for LXC containers and KVM virtual machines. It provides a web and command line interface and handles all the normal lifecycle tasks, plus clustering and storage.

It was forked from Canonical’s widely admired LXD project. Canonical’s decision to move LXD in-house led the lead developer and most of the community to fork, thus giving us Incus.

Incus is currently experiencing very high, active development.

1 - Installation

Preparation

A Linux server is required and Debian is fine, though you may prefer Ubuntu which is what the developers use. This is a light-weight solution and 4 cores and 4G RAM is all you need to get started.

You usually run out of RAM first. If you want to cluster and run Windows VMs, start with 16 Gigs.

The sweet spot for a small cluster is 3 nodes with 16G RAM. Though after a few years you’ll have a lot of sprawl and wish you had 32G. Avoid ceph for small scale. It’s complex and RAM intensive.

Networking

It’s a good idea to use the hosts file so cluster members can easily find each other. If you have more than one interface and want to use a dedicated network for cluster communication, this is a good place to start.

sudo sh -c 'cat <<EOF >> /etc/hosts

10.0.0.1     debian1
10.0.0.2     debian2
10.0.0.3     debian3
10.0.0.4     debian4

EOF'

Storage

Incus recommends an empty zfs or btrfs1 partition so that you can snapshot your VMs and containers. It’s not required, but useful. If you don’t have a spare disk, create an extra partition to your boot disk during the OS install.

I use /dev/sdb in this example. Wipe it and install the BTRFS programs, but let Incus handle the rest.

sudo wipefs -a /dev/sdb
sudo apt install btrfs-progs

Open File Limits

Running many containers can use up the default number of file handles. You should increase that on the host.

sudo vi /etc/security/limits.d/10-nofile.conf

# <domain>      <type>  <item>         <value>
*               soft    nofile         65535
*               hard    nofile         65535

sudo vi /etc/systemd/system.conf

DefaultLimitNOFILE=65535

# also increase the system wide file limits
sudo vi /etc/sysctl.d/99-limits.conf

fs.file-max = 200000

sudo reboot

Kernel (optional)

Your stock kernel from Debian is fine, but the current kernel is about 10% faster for some things (especially for ZFS at time or writing). It also happens that Stéphane Graber, the lead developer of Incus, as well as Linus Torvalds and others run the current kernel.

Though I’ve had several showstopping bugs on older hardware and it’s caused DKMS issues, so I recommend skipping this at this point.

But if you must, use a build forked directly from the torvalds/linux repo2.

Note

  • These kernels aren’t signed by a trusted distribution key, so you may need to turn off secure boot.
  • If you’re building DKMS modules, such as for linstore (and you’ll know if you are), you’ll have an extra step..
  • Stick with stock kernels if you don’t have IPMI or easy keyboard access to roll back a buggy kernel
sudo apt install -y curl gpg 

sudo mkdir -p /etc/apt/keyrings/

sudo curl -fsSL https://pkgs.zabbly.com/key.asc -o /etc/apt/keyrings/zabbly.asc

sudo sh -c 'cat <<EOF > /etc/apt/sources.list.d/zabbly-kernel-stable.sources
Enabled: yes
Types: deb
URIs: https://pkgs.zabbly.com/kernel/stable
Suites: $(. /etc/os-release && echo ${VERSION_CODENAME})
Components: main
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/zabbly.asc

EOF'

# This will take the kernel from 6.12 to 6.18 on trixie at the time of writting
sudo apt update
sudo apt -y install linux-zabbly

# Reboot to apply the new kernel
sudo reboot

Installation

You can install Incus from your distro’s repo, but Zabbly’s (from the lead developer’s company) is a bit newer.

As per https://github.com/zabbly/incus

# Add their key
# sudo apt install -y curl gpg 
sudo mkdir -p /etc/apt/keyrings/
sudo wget -O /etc/apt/keyrings/zabbly.asc https://pkgs.zabbly.com/key.asc
     
# Add the LTS repo, needed for the web user interface
sudo sh -c 'cat <<EOF > /etc/apt/sources.list.d/zabbly-incus-lts-6.0.sources
Enabled: yes
Types: deb
URIs: https://pkgs.zabbly.com/incus/lts-6.0
Suites: $(. /etc/os-release && echo ${VERSION_CODENAME})
Components: main
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/zabbly.asc

EOF'

# Then add the stable repo, to access the latest stable version of incus
sudo sh -c 'cat <<EOF > /etc/apt/sources.list.d/zabbly-incus-stable.sources
Enabled: yes
Types: deb
URIs: https://pkgs.zabbly.com/incus/stable
Suites: $(. /etc/os-release && echo ${VERSION_CODENAME})
Components: main
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/zabbly.asc

EOF'

Now you have a choice;

A Full Install - includes the Web UI and support for both containers and VMs

# A full install
sudo apt update
sudo apt -y install incus incus-ui-canonical

A Minimal Install - command line and only supports containers.

# A minimal install
sudo apt update
sudo apt -y install incus-base

The full install can add up to a gig of dependencies. If keeping it trim is important, start with a full install on just one node, and add the rest as needed. Both versions fully support clustering and all other features.

Next Steps

Install Incus on your other nodes, then start the configuration.

2 - Configuration

Incus Initialization

Start the initialization on the node with the full install. Node 1 for us.

ssh debain1
sudo incus admin init

You’re safe to take the defaults for all but a couple questions.

Would you like to use clustering? (yes/no) [default=no]: yes

Would you like to use an existing empty block device (e.g. a disk or partition)? (yes/no) [default=no]: yes

Path to the existing block device: /dev/sdb

Post Initialization Checks

For Debian

The initializer uses the hostname by default. In some cases it resolves that as 127.0.1.1 based on the first line in the hosts file. That will cause problems for the other hosts and prevent more esoteric things like split-brain DNS. You can check and possibly correct this with the command incus admin cluster.

Web UI

If you have multiple NICs you must tell Incus to listen on them (or at least the one you want).

# Be careful about changing the port later on, as it's also used for cluster communication
sudo incus config set core.https_address 0.0.0.0:8443

Adding Cluster Members

Before you leave the 1st host, create a cluster join token.

# Assuming the next server you add will be NODENAME2
sudo incus cluster add debian2 

Repeat the config on successive hosts, adding the disk similar to before.

sudo incus admin init

Would you like to use clustering? (yes/no) [default=no]: yes

Are you joining an existing cluster? (yes/no) [default=no]: yes

The Web UI

You can now connect to the Web user interface. It will prompt you to generate a key pair. I suggest not using a password as it complicates the import. You can then copy the key from the server to your workstation and import it into your browser.

Managing Incus Networks

The Default Network

Incus creates a virtual network for your guests and provides DHCP and DNS. It does this by creating a linux bridge device incusbr0, wrapping an instance of dnsmasq, and creating a nftable to handle masquerade.

If you want to isolate guests, you create a new network and a profile that uses that, Then assign guests to that profile.

Create a LAN Network

If you have a cluster you probably want instances to talk to each other. The simplest way to do that is to put them on the LAN. Do that by installing bridge-utils and creating a bridge.

On Debian 13 use systemd-networkd for this.

# Install the bridge utilities
sudo apt install bridge-utils
# Create a network device file to define the bridge device
vi /etc/systemd/network/10-br0.netdev
[NetDev]
Name=br0
Kind=bridge
# Define the network details in a network file
vi /etc/systemd/network/10-br0.network
[Match]
Name=br0

[Network]
Address=192.168.250.100/24
Gateway=192.168.250.1
DNS=192.168.250.1
# And then reload
sudo systemctl restart systemd-networkd.service

# Tell Incus about it  
incus network create LAN-BRIDGE --type=physical parent=br0

# Change the default profile so instances use the bridge by default
incus profile device remove default eth0
incus profile device add default eth0 nic nictype=bridged parent=br0 name=eth0

Note: Incus may need a restart before it accepts br0

Create a Fully Isolated Network

An interesting edge case is testing netboot where you need to turn off Incus’s DHCP. For that you must create a network without those services at the command line.

incus network create test ipv4.address=none ipv6.address=none
incus profile copy default isolated

Managing Storage

Individual Instances

Incus uses the concept of a storage pool. This is a location to hold disk images, ISOs, profiles and such. You can see the default with:

sudo incus storage show default

This is a shared space but instances are kept separate. Each gets their own volume inside the pool. This is thin-provisioned so it only takes as much space as used, but there is a max size of 10G. In some cases this isn’t enough so you can change the default.

sudo incus storage set default size 100GiB

Container Access to Shares

The best way is to mount it on your host and pass it to your containers with a bind-mount. This keeps the kernel modules and caching out of the containers where you’d otherwise need escalated privileges and have less efficient caching.

User IDs are normally mis-matched because containers have their own name-space. But you can line them up easily with the shift option.

incus config device add syncthing srv disk shift=true source=/srv/media path=/srv/media

Passing Video Cards

If you’re transcoding media it helps to have access to a video card, or at least the integrated video card hardware in modern CPUs.

Here’s a good discussion on it.

https://discuss.linuxcontainers.org/t/sharing-a-gpu-with-multiple-containers/21913/2

If you don’t have an existing media or render group, you can use the GUI of the process owner. For a server like Jellyfin, you can use the GID of the jellyfin user.

Command Line Administration Users

You can sudo Incus commands, but you can also add users to the admin group.

sudo adduser YOUR-USERNAME incus-admin
incus admin init

3 - Operation

You can access the Web UI at https://debian1:8443/ui/. Authentication is handled by client certificate and the GUI will step you through it. The main thing to know is that after creating a certificate pair, you must copy the private key from the server to your workstation and import it in your browser. You may want to generate the cert without a password to facilitate this.

Creating Containers

This is the most efficient use of resources and the OCI is built in. Simply pick one from the list. This only allows for linux as it’s a kernel-sharing technology.

Some containers may run out of file handles if they are handling lots of streams. You can increase the limit per-container with:

sudo incus config set SOMECONTAINER limits.kernel.nofile 65535

Creating VMs

Windows 11

Download the Windows installation ISO and attach it. Also, since Windows does’nt include VirtIO drivers, you’ll need that too.

# Check the on the web for the current URLs before you wget them
wget https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/latest-virtio/virtio-win.iso
wget https://drive.massgrave.dev/en-us_windows_11_consumer_editions_version_24h2_updated_sep_2025_x64_dvd_6d1ad20d.iso -O Win11_24H2_English_x64.iso

# Create a new VM and allow access to the TPM
incus init win11vm --empty --vm
incus config device add win11vm vtpm tpm path=/dev/tpm0

# Increase the hardware over the defaults
incus config device override win11vm root size=64GiB io.bus=nvme
incus config set win11vm limits.cpu=4 limits.memory=8GiB

# Attach the install and driver ISOs
incus config device add win11vm install disk source=/home/allen/Downloads/Win11_24H2_English_x64.iso io.bus=usb boot.priority=10
incus config device add win11vm virtio disk source=/home/allen/Downloads/virtio-win.iso io.bus=usb boot.priority=5

# Start the VM and attach
incus start win11vm --console=vga

# When done, shutdown and remove the ISOs 
# (after installing the rest of the VirtIO Drivers)
incus config device remove win11vm install
incus config device remove win11vm virtio

Windows will automatically find the storage drivers, but not the network. You can manually search when prompted, or you can use the shift + F10 + OOBE\BYPASSNRO trick. This will restart the config and let you bypass that part. Then you can install the full set of drivers from the root of the driver disk, post-install.

Windows 2025

The process is similar for Server 2025, but you may need a newer version of the VirtIO drivers. The above is usually the latest, but check https://github.com/qemus/virtiso/releases if you run into problems.

incus init win25 --empty --vm
incus config device add win25 vtpm tpm path=/dev/tpm0

incus config device override win25 root size=64GiB io.bus=nvme
incus config set win25 limits.cpu=4 limits.memory=8GiB


incus config device add win25 install disk source=/home/allen/Downloads/26100.1742.240906-0331.ge_release_svc_refresh_SERVER_EVAL_x64FRE_en-us.iso io.bus=usb boot.priority=10
incus config device add win25 virtio disk source=/home/allen/Downloads/virtio-win-0.1.285.iso io.bus=usb boot.priority=5

incus start win25 --console=vga

# As the system restarts, use:
incus console win25 --type=vga

incus config device remove win25 install
incus config device remove win25 virtio 

Windows Repacking

If you do a lot of Windows installs, you may want to create your own ISO that includes some installation logic as well as the VirtIO drivers.

Simos Xenitellis has some great info on that on his blog as does Scott Thompson who’s info I use a lot.

Linux Guests

You normally want just containers. But if you must run a full VM, you should install the incus agent.

sudo apt install incus-agent

You may need to manually mount an install source if the above command fails.

Backup

There’s no built-in facility for this. But it’s fairly trivial to create script like this;

#!/bin/bash

BACKUP_DIR="/mnt/gluster/media/backup"
INSTANCES=$(incus list --format csv -c n)

for instance in $INSTANCES; do
    incus export "$instance" "$BACKUP_DIR"/"$instance"_backup_$(date +%Y%m%d%H%M) --instance-only --optimized-storage
    # Optional: Add logic here to delete old backups in BACKUP_DIR
done

You may need to dedicate temp space for this. When exporting, the server first creates the backup artifact (a compressed tarball) at /var/lib/incus/backups/. If there’s not enough space there, the export will fail.

If you need a dedicated location, use these commands on all your nodes.

# Assuming you have a large pool named 'local' to create a volume in
sudo incus storage volume create local backup-tmpfiles
sudo incus config set storage.backups_volume local/backup-tmpfiles

Sources

Scott at scottibyte is the main resource for the windows install.