Skip to main content

Incus

Incus can be used to manage system containers, application containers, and virtual machines.

1. Get a volume

The disk space on our VPS is almost full, so we need to get a volume from Hetzner, that we can use for Incus. A volume is like a virtual disk, that can be attached to our VPS.

Let's say that the size of the disk is 50 GB. Later we can resize it and make it larger, if needed. Set the name of the volume to "incus" and click on the mount option: Manually. Then click on the button "Create & Buy Now".

On the VPS, we can use this command to check the presence of the disk:

lsblk
Unformatting the disk

If we chose by mistake the mount option "Automatic", the new disk will be formated and mounted inside the VPS. We don't want this -- for Incus we need an unmounted and unformatted disk. To correct this we can delete the disk and create a new one. But we can also unmount and unformat it manually, like this:

lsblk
umount /dev/sdb
lsblk
dd if=/dev/zero of=/dev/sdb count=1 bs=1M

We also have to edit /etc/fstab and comment out (or delete) the line that mounts the disk.

2. Install Incus

We can install it from the Zabbly package repository:

  1. Get the key of the repository:

    mkdir -p /etc/apt/keyrings/
    curl -fsSL https://pkgs.zabbly.com/key.asc \
    -o /etc/apt/keyrings/zabbly.asc
  2. Add the package repository to the list of sources:

    cat <<EOF > /etc/apt/sources.list.d/zabbly-incus.sources
    Enabled: yes
    Types: deb
    URIs: https://pkgs.zabbly.com/incus/stable
    Suites: bookworm
    Components: main
    Architectures: amd64
    Signed-By: /etc/apt/keyrings/zabbly.asc
    EOF

    cat /etc/apt/sources.list.d/zabbly-incus.sources
  3. Install the package incus:

    apt update
    apt install incus

    incus --version
    incus ls
  4. Make sure that btrfs-progs is also installed, since we are going to use a Btrfs storage backend with incus:

    apt install btrfs-progs

3. Initialize Incus

Before we can create containers, we need to initialize Incus:

incus admin init

We use the default answers for almost all the questions. The important questions are these:

  • --> Name of the storage backend to use: btrfs

    We are using btrfs for the storage backend, because we need to install Docker inside the container, and only this filesystem supports it efficiently. Besides, deduplication features of Btrfs make it possible to use less disk space (or to use the disk space more efficiently).

  • --> Would you like to use an existing empty block device? yes

    We are using the second disk as a storage for the Incus containers.

  • --> Path to the existing block device: /dev/sdb

  • --> What IPv6 address should be used? none

    We are disabling IPv6 for the containers; we don't need it.

4. Networking

The connection of the Incus containers to the Internet goes through the host. The bridge network incusbr0 can be thought as a switch, which provides DHCP service for the containers that are connected to it. It also works as a gateway for them and provides NAT.

4.1 Fix the Firewall

In firewalld (that is installed on the server), any interface that is not explicitly added to a zone, is handled by the default zone, which is the zone public. This zone is meant for the interfaces that are facing the public internet, so it has restrictions.

The bridge interface of Incus (incusbr0) is also handled by default by the restricted public zone. As a result, DHCP requests are blocked, and the containers cannot get an IP.

  1. Test networking in a container.

    If we create a test container, we will notice that the network in the container is not working:

    incus launch images:ubuntu/24.04 u24
    incus ls
    incus exec u24 -- ip addr

    The container did not get an IP, as it normally should.

  2. However, if we stop firewalld and restart the container, everything works fine:

    systemctl status firewalld
    systemctl stop firewalld

    incus restart u24

    incus ls
    incus exec u24 -- ip addr
    incus exec u24 -- ping 8.8.8.8

    systemctl start firewalld
    systemctl status firewalld

    So the problem is that the firewall is not configured properly.

    Make sure that IP forwarding is enabled

    By the way, IP forwarding should already be enabled in the kernel of the host:

    sysctl net.ipv4.ip_forward
    cat /proc/sys/net/ipv4/ip_forward

    If it is not, enable it like this:

    echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
    sysctl -p
  3. Let's fix this problem by adding the bridge interface to the trusted zone, where everything is allowed:

    firewall-cmd --zone=trusted --list-all
    firewall-cmd --zone=trusted --add-interface=incusbr0 --permanent
    firewall-cmd --reload
    firewall-cmd --zone=trusted --list-all
  4. Check that it is working:

    incus restart u24
    incus ls
    incus exec u24 -- ip addr
    incus exec u24 -- ping 8.8.8.8
    note

    If the ping is still not working, usually the problem is that forwarding is blocked. Try this command:

    iptables-save | grep FORWARD

    If you see something like this: :FORWARD DROP [4:2508], it means that the policy for the FORWARD chain is DROP (maybe it is set by Docker).

    We can make the default policy ACCEPT, like this:

    iptables -P FORWARD ACCEPT

    However, the next time that the server is rebooted, or firewalld restarted, we may loose this configuration.

  5. Let's make sure that forwarding is enabled:

    firewall-cmd --permanent --direct --add-rule \
    ipv4 filter FORWARD 0 -j ACCEPT
    firewall-cmd --reload

    firewall-cmd --direct --get-all-rules
  6. Let's test again that networking works, and then clean up the test container:

    incus exec u24 -- ping 8.8.8.8

    incus stop u24
    incus rm u24
    incus ls

4.2 Limit DHCP range

Incus containers usually get an automatic IP from the DHCP that is provided by incusbr0. Sometimes we need to set a fixed IP to some containers. To avoid any possible IP conflicts between the fixed IP and the automatic IPs issued by DHCP, we should limit the range of the DHCP IPs, and make sure that the fixed IPs are outside the DHCP range.

Let's modify the DHCP range on the configuration of incusbr0:

incus network show incusbr0
incus network get incusbr0 ipv4.address

incus network set incusbr0 \
ipv4.dhcp.ranges 10.148.0.2-10.148.0.200

incus network get incusbr0 ipv4.dhcp.ranges
incus network show incusbr0