Applications Azure

Incus on Ubuntu 24.04 on Azure User Guide

| Product: Incus on Ubuntu 24.04 LTS on Azure

Overview

This image runs Incus 7.2 on Ubuntu 24.04 LTS. Incus is a system container and virtual machine manager from the LinuxContainers project, the community fork of LXD. It gives you full Linux systems rather than single process application containers: each instance boots its own init, runs its own services and behaves like a small independent machine, while sharing the host kernel so it starts in about a second and costs a fraction of a VM.

The image ships the Incus web UI, so you can create and manage instances from a browser, and the incus command line client for everything you would rather script.

No credential ships in this image, and it is locked until first boot. No password, certificate or token of any kind is baked into it. A unique password is generated on your VM specifically by incus-firstboot.service during first boot, and the Incus API's trust store is empty until that runs. There is no default login to find, and no window during which a known credential is live.

What is included:

  • Incus 7.2, pinned, from the Zabbly package channel published by the Incus maintainer
  • The Incus web UI, served through nginx over HTTPS on port 443
  • A ready to use default storage pool and an incusbr0 NAT bridge, so you can launch an instance immediately
  • A unique web UI password generated on this VM at first boot, in a root only file
  • The incus command line client, working out of the box over the local socket
  • A recorded licence notice and dependency CVE audit on the image itself

Before you start

You need an Azure subscription, and the Azure CLI if you deploy from the command line. Everything below uses azureuser, the default administrator name this image expects.

Sizing. System containers run comfortably on the recommended Standard_B2s (2 vCPU, 4 GiB). Scale up for more instances.

Containers and virtual machines. Incus can run both. System containers work on any VM size, including the recommended B2s, and are what this guide covers. Incus can also run full virtual machines (incus launch ... --vm), but that needs nested virtualisation, which Azure exposes only on Dv3/Ev3 series and later and not on the B series. If you want Incus VMs as well as containers, choose a nested virtualisation capable size such as Standard_D4s_v3. On a B series VM /dev/kvm is absent and --vm will not work; containers are unaffected.

Deploy from the Azure Marketplace

  1. In the Azure portal, search the Marketplace for Incus on Ubuntu 24.04 LTS by cloudimg and select Create.
  2. Choose your subscription, resource group and region.
  3. Set the VM name and size (Standard_B2s or larger).
  4. Set the administrator username to azureuser and upload your SSH public key.
  5. Under Inbound port rules, allow SSH (22) and HTTPS (443).
  6. Select Review + create, then Create.

Do not open port 8443. The Incus API is bound to 127.0.0.1 and is not reachable from outside the VM by design. nginx on 443 is the only door it needs.

Deploy from the Azure CLI

az group create --name incus-rg --location eastus

az vm create \
  --resource-group incus-rg \
  --name incus-vm \
  --image cloudimg:incus-ubuntu-24-04:incus-ubuntu-24-04:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_rsa.pub \
  --public-ip-sku Standard

az vm open-port --resource-group incus-rg --name incus-vm --port 443 --priority 1001

Get the public IP:

az vm show -d --resource-group incus-rg --name incus-vm --query publicIps -o tsv

Retrieve your unique password

The password is generated on your VM at first boot and written to a root only file. SSH in and read it:

ssh azureuser@<your-vm-public-ip>
sudo cat /root/incus-credentials.txt

You will see the URL, the username admin, and the password generated for this VM. Confirm the three services are running at the same time:

systemctl is-active incus incus-firstboot nginx

The per VM credentials file with the password masked, and the incus, incus-firstboot and nginx units all reporting active

If the web UI does not answer, first boot has not finished. nginx is deliberately configured so it cannot start until incus-firstboot.service has successfully generated this VM's password and certificate. That is intentional: the appliance fails closed rather than ever serving without authentication. Give it a moment after first boot and check systemctl status incus-firstboot.

Sign in to the web UI

Open https://\<your-vm-public-ip>/ in a browser and sign in as admin with the password from the file above.

Your browser will warn about the certificate. The image generates a self signed certificate for your VM's own IP at first boot, because it cannot know your domain name in advance. The warning is expected. Add your own domain and a trusted certificate when you are ready, as shown at the end of this guide.

The Incus UI opens on Instances. A fresh VM starts with none, so the list is empty until you launch one.

The Incus web UI instance list showing running and stopped system containers with their images and status

Launch your first container

From the VM, launch a system container. Incus pulls the image from the LinuxContainers image server the first time and caches it:

sudo incus launch images:debian/12 app-01

Why sudo. Incus grants full control to members of the incus-admin group. This image does not add your administrator account to that group, because doing so would hand anyone who reaches that account complete control of every instance without a further step. Run incus with sudo, or add your own account deliberately with sudo usermod -aG incus-admin $USER (log out and back in) if you prefer.

Check it:

sudo incus list
sudo incus storage list

incus version reporting client and server 7.2, incus list showing the running containers with their bridge addresses, and the default dir storage pool

The container appears in the web UI immediately, with its image, status and address:

The Incus web UI showing the app-01 container overview with its base image, type, IPv4 address, MAC address and architecture

Get a shell inside it, from the command line:

sudo incus exec app-01 -- bash

or from the Terminal tab in the web UI:

A live bash terminal running inside the app-01 Debian container in the Incus web UI

A note on Alpine images. The web UI's Terminal tab opens a bash login shell. Alpine ships BusyBox ash and has no bash at all, so the Terminal tab will fail to attach on an Alpine container. Use the Console tab for those, or sudo incus exec <name> -- sh from the command line, or install bash inside the container.

Useful lifecycle commands:

sudo incus stop app-01
sudo incus start app-01
sudo incus restart app-01
sudo incus info app-01

When you are finished with an instance, remove it (-f stops it first). Leave app-01 running for now if you intend to follow the networking section below:

sudo incus delete -f <instance-name>

Storage

The image ships a default storage pool using the dir backend, which stores instances as plain directories on your VM's own OS disk. It works on every VM size, needs no kernel module, and grows with whatever disk you attach.

The Incus web UI storage pool view showing the default dir pool with its size and usage

The dir backend does not support pool level snapshots or quotas. If you want those, add a second pool on a dedicated data disk. For example, after attaching a data disk as /dev/sdc:

sudo apt-get install -y zfsutils-linux
sudo incus storage create fast zfs source=/dev/sdc
sudo incus launch images:debian/12 db-01 --storage fast

Both pools coexist; default stays as it is and you choose per instance.

Networking

Instances attach to the managed incusbr0 bridge and get a private address with NAT egress, so they can reach the internet while remaining unreachable from outside the VM.

To publish a service from a container, forward a port from the VM to the instance:

sudo incus config device add app-01 http proxy \
  listen=tcp:0.0.0.0:8080 connect=tcp:127.0.0.1:80

Then open that port in your network security group:

az vm open-port --resource-group incus-rg --name incus-vm --port 8080 --priority 1002

Open only the ports you intend to publish, and never open 8443.

How this image is secured

Worth understanding, because it is deliberately different from a stock Incus install.

Incus authenticates its remote API with TLS client certificates, not passwords. Exposing that API to a browser means meeting a certificate prompt with no certificate to offer. So this image does not expose it at all:

  • The Incus API listens on 127.0.0.1:8443 only and is never reachable from the network.
  • nginx on 443 is the only door. It terminates TLS and gates on HTTP Basic Auth with your VM's unique password.
  • nginx authenticates to the API with a TLS client certificate that first boot generated on your VM and added to the trust store. It is the only entry in it.
  • Your SSH key is the root of trust. The password is readable only via sudo on a VM only your key opens.

You can verify all of this on your own VM:

sudo ss -tlnp | grep -E '8443|:443|:80 '
curl -sk -o /dev/null -w 'unauthenticated: HTTP %{http_code}\n' https://localhost/ui/

The Incus API listening only on 127.0.0.1:8443 while nginx listens on 443, an unauthenticated request refused with HTTP 401, the correct password accepted with HTTP 200, and the API reporting auth trusted and version 7.2

To change the web UI password at any time:

sudo htpasswd -B /etc/cloudimg-incus/htpasswd admin
sudo systemctl reload nginx

Licence and dependency audit

The image records what it ships and under which licence, verified at build time against the real upstream artifacts:

head -18 /usr/share/doc/incus-cloudimg/LICENCE-NOTICE.txt
head -8 /usr/share/doc/incus-cloudimg/cve-audit.txt

The recorded licence notice showing Incus core under Apache 2.0 and the web UI under GPL 3.0 with their verified source tags, and the dependency CVE audit summary

Incus core is Apache-2.0. The Incus web UI is GPL-3.0, conveyed verbatim and unmodified; the notice on the image names the exact upstream tag its corresponding source is published at, together with a written offer to supply it. Nothing in this image or cloudimg's support terms restricts the rights those licences grant you.

Add your own domain and certificate

For production, put a real name and a trusted certificate in front:

  1. Point a DNS A record at your VM's public IP.
  2. Install a certificate with Certbot:
sudo apt-get update
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d incus.example.com

Certbot edits the nginx site in place and sets up renewal. Your password and the API binding are unchanged.

Keeping the image up to date

The OS receives security updates automatically through unattended-upgrades, and no package is held back, so security fixes are never blocked, including for Incus itself.

Incus is installed from the Zabbly package channel, which is a third party origin that unattended-upgrades does not auto-install from. So your appliance stays on the release this image shipped until you upgrade it deliberately:

sudo apt-get update && sudo apt-get install -y incus incus-ui-canonical

Review the Incus release notes first, and snapshot the VM before a major jump.

Troubleshooting

The web UI does not load. Check systemctl is-active incus incus-firstboot nginx. If nginx is inactive or failed, first boot has not completed successfully; nginx will not start without this VM's generated password and certificate. Check journalctl -u incus-firstboot.

I get a 401 and my password is correct. Confirm you are signing in as admin, and re read the password with sudo cat /root/incus-credentials.txt.

The Terminal tab will not attach. On Alpine containers this is expected, as they have no bash. Use the Console tab or sudo incus exec <name> -- sh.

incus launch says the image cannot be found. The LinuxContainers image server retires old releases. Run sudo incus image list images: | grep <distro> to see what is currently published and use a current alias.

incus launch --vm fails. Full VMs need nested virtualisation. Check with ls /dev/kvm; if it is absent, your VM size does not support it. Use a Dv3/Ev3 or later size, or use system containers.

Support

Covered by cloudimg support: email support@cloudimg.co.uk.