Incus 7.6 on AWS User Guide
Overview
This guide covers launching and using the Incus 7.6 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened MeshCentral & Helpdesk and Project Management AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
Incus is a system container and virtual machine manager from the LinuxContainers project, the community fork of LXD. A system container is a full Linux system rather than a single process: it boots its own init, runs its own services and behaves like a small independent server, while sharing the host kernel so it starts in about a second. You manage instances from the Incus web UI in a browser or with the incus command line client.
The cloudimg image adds:
- HTTPS and a password from the first boot. nginx serves the Incus web UI and API on port 443 with a certificate generated on your instance's first boot, behind HTTP Basic Auth with a password generated on your instance and written to
/root/incus-credentials.txt. Port 80 redirects to HTTPS. - The Incus API is never exposed. Incus listens on
127.0.0.1:8443only. nginx is the only way in, and it authenticates to the API with a TLS client certificate minted on your instance at first boot, the only entry in the Incus trust store. Its private key is readable by root only. - Fail closed. No password, certificate or key is baked into the image. nginx refuses to start until first boot has written your instance's own password, server certificate and client certificate.
- Pinned to your instance's address. nginx answers only to the address first boot found for your instance (its public IPv4, else its private IPv4) and drops requests for any other host name. Cross site requests and WebSocket connections from another origin are refused.
sudo incus-set-hostmoves it to an Elastic IP or a DNS name. - Metadata service blocked. The AMI requires IMDSv2, and Incus, nginx and every container you run are blocked from the instance metadata service, so a workload inside a container cannot read your instance's IAM role credentials.
- Ready to use. A
defaultstorage pool and anincusbr0NAT bridge are configured, so you can launch a container straight away. - Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on, a recorded licence notice and a dependency CVE audit on the image.
Containers, not virtual machines, on most instance types. Incus can also run full virtual machines, but that needs hardware virtualisation (/dev/kvm), and EC2 exposes it only on bare metal instance types (*.metal). On every other type, including the recommended m5.large, this image runs system containers; incus launch --vm is refused. See Step 9.
What is on the image: Incus 7.6 (the incus and incus-ui-canonical packages from the Zabbly repository published by the Incus maintainer, pinned and verified against the repository's signing key), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (containers download their images from the LinuxContainers image server).
- A security group that allows TCP 22 (SSH), TCP 443 (the web UI and API) and TCP 80 (redirects to HTTPS) from your own networks only.
Never open port 8443. The Incus API is bound to
127.0.0.1and is not reachable from outside the instance by design. nginx on 443 is the only door it needs.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Incus 7.6 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (2 vCPU, 8 GiB) or larger for more containers, your key pair and a security group as above.
Containers are stored on the root volume (16 GiB by default). Give the root volume more space at launch if you plan to run many containers, or add a second EBS volume as a separate storage pool (Step 7).
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| Incus 7.6 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot takes a few seconds. Then the four units are active, and the client and server both report 7.6:
systemctl is-active incus incus-firstboot nginx cloudimg-incus-imds-guard
sudo incus version
active
active
active
active
Client version: 7.6
Server version: 7.6

The Incus API listens on loopback only; nginx serves ports 80 and 443:
sudo ss -tlnp | awk 'NR==1 || /incusd|nginx|sshd/' | awk '{print $1, $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g'
State Local Peer
LISTEN 127.0.0.1:8443 users:(("incusd"))
LISTEN 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
LISTEN 0.0.0.0:22 users:(("sshd"),("systemd"))
LISTEN 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
LISTEN [::]:80 users:(("nginx"),("nginx"),("nginx"))
LISTEN [::]:22 users:(("sshd"),("systemd"))
LISTEN [::]:443 users:(("nginx"),("nginx"),("nginx"))

Step 4: Get your URL and password
sudo cat /root/incus-credentials.txt
The file is readable by root only. It holds the address the web UI is served as (your instance's public IP address by default), the username, the password generated on this instance and the SHA-256 fingerprint of the instance's certificate:
INCUS_UI_URL=https://<public-ip>/
INCUS_UI_USERNAME=admin
INCUS_UI_PASSWORD=<24 letters and digits>
TLS_CERT_SHA256=<the fingerprint your browser should show>

If the web UI does not answer, first boot has not finished. nginx cannot start until
incus-firstboot.servicehas generated this instance's password and certificates. Checksystemctl status incus-firstboot.
Step 5: Sign in from your browser
Open INCUS_UI_URL in your browser and sign in as admin with the password from the file. The certificate is self signed (generated on your instance), so your browser warns once; compare the fingerprint it shows with TLS_CERT_SHA256, or install your own certificate (Step 11).
The Incus UI opens on Instances. A new instance starts with none; here three containers have been created:

Step 6: Launch your first container
From the instance, launch a system container. Incus downloads the image from the LinuxContainers image server the first time and caches it:
sudo incus launch images:debian/12 app-01
Launching app-01
Then list your instances:
sudo incus list -c ns4t
┌────────┬─────────┬───────────────────────┬───────────┐
│ NAME │ STATE │ IPV4 │ TYPE │
├────────┼─────────┼───────────────────────┼───────────┤
│ app-01 │ RUNNING │ 10.138.219.166 (eth0) │ CONTAINER │
└────────┴─────────┴───────────────────────┴───────────┘
Why
sudo. Incus gives full control to members of theincus-admingroup. The image does not addubuntuto that group, because that would give anyone who reaches the account control of every instance without a further step. Runincuswithsudo, or add an account deliberately withsudo usermod -aG incus-admin <user>.
Run a command inside it:
sudo incus exec app-01 -- sed -n 1,2p /etc/os-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
For an interactive shell run sudo incus exec app-01 -- bash. The container appears in the web UI at once, with its image, type, address and resource use:

The Terminal tab opens a shell inside the container from your browser:

Alpine containers. The Terminal tab opens
bash, which Alpine does not ship. Use the Console tab orsudo incus exec <name> -- shfor Alpine containers.
Other lifecycle commands: sudo incus stop app-01, sudo incus start app-01, sudo incus restart app-01, sudo incus info app-01, and sudo incus delete -f <instance-name> to remove an instance.
Step 7: Storage
The default storage pool uses the dir backend, which stores each instance as a directory on the root volume. It works on every instance type and needs no kernel module.
sudo incus storage list
┌─────────┬────────┬─────────────────────────────────────────────┬─────────┬─────────┐
│ NAME │ DRIVER │ DESCRIPTION │ USED BY │ STATE │
├─────────┼────────┼─────────────────────────────────────────────┼─────────┼─────────┤
│ default │ dir │ cloudimg default storage pool (dir backend) │ 2 │ CREATED │
└─────────┴────────┴─────────────────────────────────────────────┴─────────┴─────────┘

The dir backend has no pool level snapshots or quotas. For those, attach a second EBS volume to the instance and create a ZFS pool on it (the device name is the one lsblk shows for the new volume, such as /dev/nvme1n1):
lsblk -o NAME,SIZE,MOUNTPOINT
sudo apt-get install -y zfsutils-linux
sudo incus storage create fast zfs source=/dev/nvme1n1
sudo incus launch images:debian/12 db-01 --storage fast
Both pools coexist and you choose one per instance.
Step 8: Networking
Instances attach to the managed incusbr0 bridge and get a private address with NAT egress, so they can reach the internet while staying unreachable from outside the instance.
sudo incus network list

To publish a service from a container, forward a port from the instance to it:
sudo incus config device add app-01 http proxy listen=tcp:0.0.0.0:8080 connect=tcp:127.0.0.1:80
sudo incus config device show app-01
Device http added to app-01
http:
connect: tcp:127.0.0.1:80
listen: tcp:0.0.0.0:8080
type: proxy
Then allow that port in the instance's security group, from your own networks only, for example with the AWS CLI:
aws ec2 authorize-security-group-ingress --group-id <your-security-group-id> --protocol tcp --port 8080 --cidr <your-mgmt-cidr>
Open only the ports you mean to publish, and never 8443.
Step 9: Virtual machines on EC2
Incus runs full virtual machines with QEMU, which needs hardware virtualisation. EC2 exposes /dev/kvm only on bare metal instance types such as m5.metal or c5.metal. On every other instance type the image runs system containers only, and Incus reports the lxc driver alone:
ls /dev/kvm 2>&1; sudo incus info | grep -E '^\s+driver:'
ls: cannot access '/dev/kvm': No such file or directory
driver: lxc
On a *.metal instance type, /dev/kvm is present and sudo incus launch images:debian/12 vm-01 --vm starts a virtual machine. On other types that command is refused; containers are unaffected.
Step 10: Security checks
You can confirm how the image is secured on your own instance. Without the password nginx answers 401, and the Incus API on 8443 trusts nothing that does not present the instance's client certificate:
H=$(sudo sed -n 's/^HOST=//p' /etc/cloudimg/incus.conf)
curl -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w 'no password: HTTP %{http_code}\n' "https://$H/ui/"
curl -sk https://127.0.0.1:8443/1.0 | jq -r '.metadata.auth'
sudo incus config trust list -f csv -c n
no password: HTTP 401
untrusted
cloudimg-nginx-ui-proxy
The client certificate's private key and the server's TLS key are readable by root only, and Incus and nginx are denied the instance metadata service:
sudo stat -c '%a %U:%G %n' /etc/cloudimg-incus/ui-client.key /etc/cloudimg-incus/nginx-selfsigned.key
systemctl show -p IPAddressDeny incus.service nginx.service
600 root:root /etc/cloudimg-incus/ui-client.key
600 root:root /etc/cloudimg-incus/nginx-selfsigned.key
IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32
IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32

Containers are blocked from the metadata service too, by the cloudimg_imds_guard nftables table, while their normal internet access works:
sudo incus exec -n app-01 -- timeout 5 bash -c 'echo > /dev/tcp/169.254.169.254/80' && echo reachable || echo blocked
sudo incus exec -n app-01 -- timeout 5 bash -c 'echo > /dev/tcp/deb.debian.org/80' && echo reachable || echo blocked
blocked
reachable

To change the web UI password:
sudo htpasswd -B /etc/cloudimg-incus/htpasswd admin
sudo systemctl reload nginx
Two optional Incus features run helper programs that this image leaves switched off. Setting acme.domain makes Incus run the bundled lego ACME client, and adding an OCI image remote makes it run the bundled skopeo. Neither has a newer upstream release to move to if an advisory is published against it, so the image configures neither; the build records both binaries in the CVE audit (Step 12). Prefer Certbot on nginx (Step 11) to acme.domain.
Step 11: Your own DNS name and certificate
After you attach an Elastic IP, after a stop and start changes the public IP, or to use a DNS name, point the instance at its new address. With no argument it uses the current public IP; it writes a new certificate and updates INCUS_UI_URL:
sudo incus-set-host incus.example.com
Then, with a DNS A record for the name pointing at the instance, install a trusted certificate with Certbot:
sudo apt-get update
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d incus.example.com
Your password and the API binding are unchanged.
Step 12: Licence and dependency audit
The image records what it ships and under which licence, verified at build time against the upstream artifacts, and the result of a vulnerability scan of the shipped filesystem:
head -14 /usr/share/doc/incus-cloudimg/LICENCE-NOTICE.txt
sed -n 1,9p /usr/share/doc/incus-cloudimg/cve-audit.txt
cloudimg Incus appliance - licence notice
=========================================
Verified at build time against the real upstream artifacts, not package metadata or badges.
Incus core (packages: incus, incus-base, incus-client) Apache-2.0
Source : https://github.com/lxc/incus @ tag v7.6.0
Artifact verified : COPYING, byte-identical to https://www.apache.org/licenses/LICENSE-2.0.txt
sha256 cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30
Incus web UI (package: incus-ui-canonical) GPL-3.0
Source : https://github.com/zabbly/incus-ui-canonical @ tag incus-0.22.1
(a fork of https://github.com/canonical/lxd-ui)
Artifact verified : LICENSE, byte-identical to https://www.gnu.org/licenses/gpl-3.0.txt
sha256 3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986
cloudimg Incus appliance - dependency CVE audit
Scanner : Version: 0.76.2
Scanned : 2026-11-07T12:35:06Z - Trivy rootfs scan of the SHIPPED filesystem
OS : ubuntu 24.04
Packages enumerated : 716 (positive control: a scan of nothing FAILS the build)
Total findings : 24033
HIGH/CRITICAL, fix available : 0 <-- the build gate; must be 0
HIGH/CRITICAL in lego/skopeo : 0 (d-320 residual: no fixed upstream release; recorded, see below)
HIGH/CRITICAL, no fix yet : 2245 (recorded, not gated: no version resolves these)
The build fails on any HIGH or CRITICAL finding that has a fixed version available. The findings with no fix yet are all Linux kernel CVEs against the newest kernel Ubuntu publishes, counted once per kernel sub-package; the file lists them as distinct CVE ids.
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, with a written offer to supply it. Nothing in this image or cloudimg's support terms restricts the rights those licences grant you.
Step 13: Keeping Incus up to date
Ubuntu security updates install automatically through unattended-upgrades, and no package is held back. Incus comes from the Zabbly repository, which unattended-upgrades does not install from, so the instance stays on the Incus release it shipped with until you upgrade it deliberately:
sudo apt-get update && sudo apt-get install -y incus incus-ui-canonical
Read the Incus release notes first, and snapshot the root volume before a major upgrade.
Troubleshooting
The web UI does not load. Check systemctl is-active incus incus-firstboot nginx. If nginx is inactive, first boot has not completed; see journalctl -u incus-firstboot. If the instance's public IP changed after a stop and start, run sudo incus-set-host.
The browser connection is closed with no page. nginx drops requests for host names it does not serve. Browse to the exact INCUS_UI_URL, or run sudo incus-set-host <name-or-ip> for the address you use.
I get a 401 and my password is correct. Sign in as admin, and re-read the password with sudo cat /root/incus-credentials.txt.
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 published and use a current alias.
incus launch --vm fails. Full virtual machines need /dev/kvm, which EC2 provides only on *.metal instance types. Use system containers on other types.
Support and licensing
Covered by cloudimg support: email support@cloudimg.co.uk.