
Every few years a piece of infrastructure tooling comes along that quietly reshapes how production servers get built. Incus is one of those tools. It forked from LXD back when Canonical’s stewardship of that project became a point of friction for the wider community, and since then it has matured into arguably the most capable system container and VM manager available on Linux. If you have spent any time running LXD on Ubuntu Server, the transition to Incus feels less like learning something new and more like coming home to a tool that finally answers to the community instead of a single vendor.
Ubuntu 26.04 LTS, codenamed Resolute Raccoon, shipped in April 2026 and brought with it kernel 7.0 and a native Incus package sitting right in the default archive. That alone is a big deal for anyone managing bare metal or VPS fleets, because it means you no longer have to bolt on a third-party PPA just to get container orchestration working out of the box. But here is the catch that trips up a lot of admins: the version shipped in the Ubuntu archive lags behind what Zabbly’s repository offers, and depending on your workload, that gap actually matters.
This guide walks through both installation paths, explains the tradeoffs, and covers the operational details that tutorials usually skip, things like network bridge configuration, ZFS versus btrfs storage pool decisions, and the permission quirks that catch people off guard the first time they try to run incus without sudo. Whether you are standing up a homelab, migrating an LXD cluster, or provisioning containers for a client’s WordPress stack on a fresh Resolute Raccoon install, the steps below reflect what actually works in production, not just what looks good in a demo.
What Is Incus, Exactly?
Incus is a system container and virtual machine manager, and calling it “just Docker for VMs” undersells it. Unlike application containers such as Docker or Podman, Incus runs full system containers, meaning each instance behaves like a lightweight VM with its own init system, its own package manager, and its own systemd services. It also handles true KVM-backed virtual machines through the same command-line interface and API, which means you get container density with the option of full hardware isolation whenever you need it.
For sysadmins coming from a VMware or Proxmox background, Incus occupies an interesting middle ground. It is lighter than a hypervisor-only platform but far more capable than a container runtime designed purely for microservices. Under the hood it relies on liblxc, and as of the current stable branch it requires liblxc 6.0.0 or newer for full feature support.
Prerequisites Before You Start
Before touching a single apt command, confirm the following on your Ubuntu 26.04 LTS box:
- A clean or well-maintained install of Ubuntu 26.04 LTS, verified with
lsb_release -a - Root or sudo access on the host
- At least 2 vCPUs and 4GB RAM for a minimal lab setup; production hosts running multiple containers should budget significantly more
- A block device or partition set aside if you plan to use ZFS or LVM as the storage backend
- Kernel support for nested virtualization if you intend to run VMs inside a VM (check with
egrep -c '(vmx|svm)' /proc/cpuinfo)
One thing that catches people off guard: if you are running Ubuntu 26.04 inside a cloud VPS (DigitalOcean, Vultr, that sort of thing), check whether your provider’s kernel exposes KVM. Not all of them do by default, and Incus VMs simply will not start without it. Run kvm-ok after installing cpu-checker to confirm before you go further.
sudo apt install -y cpu-checker
kvm-ok
If that comes back negative, you can still run containers just fine, but VM instances are off the table until you sort out nested virtualization with your hosting provider.
Installation Path 1: Native Ubuntu Archive Package
This is the path of least resistance, and for a lot of use cases, it is genuinely good enough. Ubuntu 24.04 LTS and later ship a native incus package tracking the 6.0 LTS branch.
Step 1: Update Your System
Always start clean. Skipping this step is one of the most common causes of dependency conflicts during install.
sudo apt update && sudo apt upgrade -y
Step 2: Install Incus From the Default Archive
sudo apt install -y incus
If you plan to run virtual machines rather than just system containers, pull in QEMU as well:
sudo apt install -y qemu-system
And if you are migrating from an existing LXD installation, grab the migration tooling too:
sudo apt install -y incus-tools
That last package gives you lxd-to-incus, a genuinely well-built migration utility that handles the heavy lifting of moving instances, profiles, networks, and storage pools from LXD to Incus without a manual export/import dance.
Step 3: Verify the Installation
incus version
You should see version output confirming the daemon and client versions match. A mismatch here usually means a partial upgrade or a leftover package from a previous LXD install fighting for the same socket.
Installation Path 2: Zabbly Repository (Recommended for Production)
Here is the honest take: if you are deploying Incus for anything beyond a quick test, use Zabbly. The maintainer of Incus itself runs this repository, and it tracks the latest stable releases far more aggressively than the Ubuntu archive does. At the time of writing, Zabbly’s stable channel is shipping Incus 7.0, while the Ubuntu 26.04 native package sits on the 6.0 LTS branch. The feature gap between those two versions is not trivial, particularly around clustering improvements and OVN networking integration.
Step 1: Install Prerequisite Packages
sudo apt update
sudo apt install -y curl gpg ca-certificates
sudo mkdir -p /etc/apt/keyrings
Step 2: Import the Zabbly Signing Key
sudo curl -fsSL https://pkgs.zabbly.com/key.asc -o /etc/apt/keyrings/zabbly.asc
Take a second here and actually verify the key fingerprint against what Zabbly publishes on their site rather than blindly trusting curl output. It takes ten seconds and it is exactly the kind of habit that separates people who get burned by a supply chain compromise from people who do not.
Step 3: Add the Repository Definition
For Ubuntu 26.04 (Resolute Raccoon), create the sources file:
sudo sh -c 'cat > /etc/apt/sources.list.d/zabbly-incus-stable.sources <
If you want to pin to a long-term-support branch rather than riding the bleeding edge stable channel, swap stable for lts-6.0 or lts-7.0 in the URIs line and name your sources file accordingly. That choice matters more than people give it credit for. Stable gets new features faster; LTS branches get security patches with a much smaller blast radius of behavioral change. For a production fleet where three-in-the-morning surprises are unacceptable, LTS wins every time.
Step 4: Install Incus and Companion Packages
sudo apt update
sudo apt install -y incus incus-ui-canonical incus-extra qemu-system
incus-ui-canonical gives you the browser-based web UI, which honestly makes onboarding junior team members far easier than teaching them the CLI cold. incus-extra bundles additional tooling including lxd-to-incus for migrations.
Step 5: Confirm the Version
apt-cache policy incus
incus version
You should see the Zabbly repository listed as the install source, and the version number should reflect whichever channel you chose.
Post-Install Configuration
Getting the package installed is maybe a third of the job. The real work is configuring Incus so it behaves sanely on your specific hardware and network.
Grant Your User Non-Root Access
Running every single container command with sudo gets old fast, and it is unnecessary. Add yourself to the incus-admin group:
sudo usermod -aG incus-admin "$USER"
newgrp incus-admin
That newgrp command matters. Group membership changes do not apply retroactively to already-open shell sessions, so without it you will be scratching your head wondering why incus list still throws a permission error even though groups shows you belong to incus-admin.
Initialize Incus
This is the step where a lot of tutorials get lazy and just tell you to run the automated minimal setup. That is fine for a lab, but understanding what the interactive initializer actually asks you matters for anything touching real traffic.
incus admin init
The wizard walks through:
- Whether to configure clustering (say no unless you are actively building a multi-node setup)
- Storage backend selection (btrfs, ZFS, LVM, or plain directory)
- Whether to connect to MAAS (irrelevant for most standalone deployments)
- Network bridge configuration, including IPv4/IPv6 subnet ranges
- Whether to expose the Incus API over the network
For a single-node lab environment, the minimal flag skips all the interactive questions and picks sane defaults:
incus admin init --minimal
Choosing a Storage Backend
This decision has more downstream impact than most admins realize during initial setup. ZFS gives you copy-on-write snapshots, built-in compression, and genuinely fast container cloning because it does not need to duplicate blocks that have not changed. The tradeoff is memory overhead from ZFS’s ARC cache, which on a memory-constrained VPS can start competing with the workloads you are actually trying to run.
Btrfs offers similar copy-on-write benefits with a lighter memory footprint, but it has a rougher history with data integrity under heavy fragmentation and certain RAID configurations, so treat it with a bit more caution on anything holding data you cannot afford to lose.
For a quick disposable lab, the plain directory backend works, but do not expect snapshot performance anywhere close to what ZFS or btrfs deliver, since it falls back to full filesystem copies rather than block-level tricks.
If you already know ZFS well from prior work with TrueNAS or Proxmox, stick with it on Incus. The consistency of tooling across platforms saves real mental overhead when you are troubleshooting at 2 AM.
Creating Your First Container
With initialization done, spinning up an instance takes one command:
incus launch images:ubuntu/26.04 my-first-container
Check that it is running:
incus list
Drop into a shell inside it:
incus exec my-first-container -- bash
Want a VM instead of a system container? Add the --vm flag:
incus launch images:ubuntu/26.04 my-first-vm --vm
The syntax similarity between containers and VMs is one of Incus’s genuinely nice design decisions. You are not learning two separate mental models for two separate workload types.
Network Configuration Considerations
By default, incus admin init sets up a private bridge (typically incusbr0) with NAT outbound access. That works fine for development, but for production servers you often want your containers reachable directly on your LAN or with dedicated public IPs.
For bridged networking that puts containers directly on your host’s network segment, you will typically configure a macvlan or a proper Linux bridge attached to your physical interface, then assign that as the container’s network profile. Something like:
incus network create br0 --type=bridge
incus network attach br0 eth0
incus profile device add default eth0 nic nictype=bridged parent=br0
Be deliberate here. Misconfigured bridging is one of the most common ways admins accidentally knock a remote server offline, because the bridge setup can momentarily disrupt the host’s own network connectivity during application. If you are working over SSH on a remote box, always have an out-of-band console (IPMI, cloud provider’s web console, whatever you have) open before touching bridge configuration.
Security Hardening
Container isolation is strong but not absolute, and treating Incus containers as equivalent to full VM-grade isolation is a mistake that leads to real incidents.
Restrict the API listener. If you enabled the HTTPS API during init, do not leave it bound to all interfaces on a public-facing host:
incus config set core.https_address=127.0.0.1:8443
Only widen that binding to a specific management network interface, and pair it with a trust token workflow rather than password auth:
incus config trust add workstation-01
Keep the host firewall active. Incus manages its own iptables/nftables rules for bridge NAT, but that does not replace a properly configured ufw or nftables ruleset on the host itself. Explicitly allow only the ports you need, and audit iptables -L periodically to make sure Incus hasn’t opened anything wider than expected during a network reconfiguration.
Avoid privileged containers unless truly necessary. Unprivileged containers, the default in Incus, map container root to an unprivileged UID range on the host. This is the single biggest security boundary you have, and disabling it for convenience (some Docker-in-LXC setups tempt admins into this) meaningfully weakens your isolation.
Patch consistently. Whichever repository you chose, keep it updated:
sudo apt update && sudo apt upgrade -y
Security-relevant fixes to liblxc and the Incus daemon itself do land periodically, and letting a production host drift several versions behind is asking for trouble the next time a CVE drops.
Performance Tuning
CPU limits. Set explicit CPU limits per container to prevent noisy-neighbor problems on shared hosts:
incus config set my-container limits.cpu 2
Memory limits. Same logic applies to RAM. Without limits, a runaway process inside one container can pressure the entire host’s memory and trigger OOM kills across unrelated workloads:
incus config set my-container limits.memory 2GB
Disk I/O. For hosts running multiple containers with heavy database or logging workloads, storage pool choice matters more than almost any other tuning knob. ZFS with a dedicated SSD or NVMe backing device dramatically outperforms directory-backed storage under concurrent write load. If you are seeing unexplained latency spikes, check zpool iostat -v before assuming it is an application-level problem.
Network throughput. For high-traffic production containers (think a WordPress front end handling a traffic spike from a viral post), consider nictype=bridged over the default NAT setup, since NAT introduces a small but measurable overhead per packet that adds up under sustained load.
Troubleshooting Common Issues
“Error: Failed to connect to local Incus” after fresh install. Almost always a group membership issue. Confirm with groups $USER that incus-admin is listed, and if you just added it, run newgrp incus-admin in your current shell.
Containers stuck in “STOPPED” state after launch. Check the daemon logs:
sudo journalctl -u incusd -n 100 --no-pager
This usually points to a storage pool problem, commonly insufficient disk space or a misconfigured ZFS dataset.
VM instances fail to start with a KVM error. Run kvm-ok again. Nested virtualization getting disabled after a host reboot or a hypervisor-level change on your VPS provider’s end is more common than you would think, particularly after provider-side maintenance windows.
apt update fails with a GPG error after adding Zabbly’s repo. Double-check the Signed-By path in your sources file matches exactly where you saved the key. A typo here, especially copy-pasting from an older guide with a different key path convention, is the most frequent cause.
Network bridge kills SSH connectivity mid-configuration. This is why the out-of-band console advice above is not optional for remote servers. If it happens, use the console to reset networking with netplan apply after correcting your bridge configuration in /etc/netplan/.