Application Infrastructure Azure

LXD System Container & VM Host on Ubuntu 24.04 on Azure User Guide

| Product: LXD System Container & VM Host on Ubuntu 24.04 on Azure

Overview

This image runs LXD, Canonical's open source manager for system containers and virtual machines, on Ubuntu 24.04 LTS. A system container behaves like a full Linux machine with its own init system, users, packages and services, while sharing the host kernel, so it starts in seconds and is far lighter than a full virtual machine. LXD gives you one clean command line client, lxc, and a REST API to launch, snapshot, copy, limit and network those containers. The image is a self service container host: a lighter alternative to a full virtualization host when what you actually need is many isolated Linux environments on one machine.

LXD is delivered as the 5.21 LTS snap, which is Canonical's supported delivery on Ubuntu. On first boot the image runs lxd init with a preseed that creates a directory backed storage pool on the OS disk and a managed bridge network, so the moment you sign in over SSH you can launch your first container. System containers run on any Azure VM size, including Standard_B2s. LXD virtual machines, as opposed to system containers, additionally need a nested virtualization capable VM size; Section 9 covers that.

LXD's security model is deliberately different from a web application. There is no web login and no network exposed control plane by default. You administer LXD over SSH using the local lxc CLI, which talks to the LXD daemon over a local unix socket. The HTTPS API on port 8443 is off, no trust password is ever baked into the image, and every VM generates its own server certificate on its first boot, so no key material is shared between customers. Section 10 documents the optional, deliberate path to exposing the HTTPS API using client certificate trust.

The brand is lowercase cloudimg throughout this guide. All cloudimg URLs in this guide use the form https://www.cloudimg.co.uk.

Prerequisites

Before you deploy this image you need:

  • A Microsoft Azure subscription where you can create resource groups, virtual networks, and virtual machines
  • Azure role permissions equivalent to Contributor on the target resource group
  • An SSH public key for first login to the admin user account
  • A virtual network and subnet in the same region as the Azure Compute Gallery the image is published into, with an associated network security group
  • The Azure CLI (az version 2.50 or later) installed locally if you intend to use the CLI deployment path in Section 2
  • The cloudimg LXD offer enabled on your tenant in Azure Marketplace

Step 1: Deploy the Virtual Machine from the Azure Portal

Navigate to Marketplace in the Azure Portal, search for LXD, and select the cloudimg publisher entry. Click Create to begin the wizard.

On the Basics tab choose your subscription, target resource group, and region. The region must match the region your Azure Compute Gallery exposes the image in. Set the virtual machine name. Choose SSH public key as the authentication type, set the username to a name of your choice, and paste your SSH public key. Standard_B2s is a reasonable starting size because LXD itself is a lightweight Go daemon; your containers consume resources at runtime, so size the virtual machine's RAM and disk to cover the containers you plan to run plus headroom.

On the Disks tab the recommended OS disk type is Standard SSD. The LXD storage pool is directory backed and lives on the OS disk, so if you plan to run many containers, increase the OS disk size, or attach a separate data disk and add a second storage pool on it later.

On the Networking tab select your existing virtual network and subnet. Attach a network security group that allows inbound TCP 22 from your management IP range only. You do not need to open any other inbound port for normal use because LXD is administered over SSH and its HTTPS API is off by default. Only if you later choose to expose the LXD HTTPS API (Section 10) would you open TCP 8443, and then only to trusted sources.

On the Management, Monitoring, and Advanced tabs the defaults are appropriate. Click Review + create, wait for validation to pass, then click Create. Deployment takes around two minutes.

Step 2: Deploy the Virtual Machine from the Azure CLI

If you prefer the command line, use the gallery image resource identifier as the source. The exact resource identifier is published on your Partner Center plan. A representative invocation:

RG="lxd-prod"
LOCATION="eastus"
VM_NAME="lxd-host-01"
ADMIN_USER="lxdadmin"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/lxd-ubuntu-24-04/versions/latest"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"

az group create --name "$RG" --location "$LOCATION"

az network vnet create \
  --resource-group "$RG" \
  --name lxd-vnet \
  --address-prefix 10.40.0.0/16 \
  --subnet-name lxd-subnet \
  --subnet-prefix 10.40.1.0/24

az network nsg create --resource-group "$RG" --name lxd-nsg

az network nsg rule create \
  --resource-group "$RG" --nsg-name lxd-nsg \
  --name allow-ssh-mgmt --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" \
  --destination-port-ranges 22 --access Allow --protocol Tcp

az vm create \
  --resource-group "$RG" \
  --name "$VM_NAME" \
  --image "$GALLERY_IMAGE_ID" \
  --size Standard_B2s \
  --storage-sku StandardSSD_LRS \
  --admin-username "$ADMIN_USER" \
  --ssh-key-values "$SSH_KEY" \
  --vnet-name lxd-vnet --subnet lxd-subnet \
  --nsg lxd-nsg \
  --os-disk-size-gb 64

Increase --os-disk-size-gb to give the directory backed storage pool room for the containers you intend to run.

Step 3: Connect via SSH

After deployment, find the public or private IP of the new virtual machine and connect as the admin user you set:

ssh lxdadmin@<public-ip>

The first login may take a few seconds while cloud init and the LXD first boot initialisation finalise. Once initialisation completes, LXD is ready and your admin user has been added to the lxd group, so after your first logout and login you can run lxc without sudo. Until then, prefix commands with sudo as shown below.

Step 4: Read the LXD Access Information

On its first boot the lxd-firstboot.service systemd oneshot initialises LXD and writes an access information file, readable only by root, that records this VM's settings and the fingerprint of its own server certificate:

sudo cat /root/lxd-access.txt

You will see the LXD channel, this VM's unique server certificate SHA256 fingerprint, the admin user that was added to the lxd group, the storage pool and network names, and a short reminder of how to administer LXD. The server certificate fingerprint is unique to this virtual machine because it is generated on first boot, never baked into the image.

Step 5: Verify LXD Is Installed and Initialised

Confirm the LXD version, that the daemon is active, and that the storage pool and network are ready:

snap list lxd
lxc version
sudo systemctl is-active snap.lxd.daemon.service
lxc storage list
lxc network list

The lxc version output reports both the client and server version as 5.21.x LTS. The storage pool named default uses the dir driver on the OS disk, and the managed bridge lxdbr0 provides NAT networking so containers get outbound connectivity and an address on a private subnet.

The terminal shows snap list reporting the lxd snap on the 5.21/stable channel, lxc version reporting client and server version 5.21.6 LTS, the default dir storage pool created on the OS disk, and the lxdbr0 managed bridge network

Step 6: Launch Your First System Container

Launch an Ubuntu 24.04 system container and wait for it to reach the RUNNING state:

lxc info web01 >/dev/null 2>&1 </dev/null || lxc launch ubuntu:24.04 web01 </dev/null
for i in $(seq 1 60); do [ "$(lxc list web01 -c s -f csv 2>/dev/null </dev/null)" = RUNNING ] && break; sleep 2; done
lxc list </dev/null

The first lxc launch downloads the Ubuntu 24.04 image from the official image server, which takes a few seconds, then starts the container. lxc list shows the container as RUNNING. On subsequent launches the image is cached, so containers start almost instantly.

The terminal shows lxc launch downloading and unpacking the Ubuntu 24.04 image and starting a container named web01, followed by lxc list reporting web01 in the RUNNING state as a CONTAINER

Step 7: Run Commands Inside the Container

A system container behaves like a full machine. Run commands inside it with lxc exec, and inspect it with lxc info:

lxc exec web01 -- cat /etc/os-release </dev/null 2>/dev/null | grep PRETTY_NAME
lxc exec web01 -- uname -srm </dev/null 2>/dev/null
lxc info web01 </dev/null 2>/dev/null | sed -n '1,12p'

The os-release line inside the container reports Ubuntu 24.04, and uname reports the host kernel because the container shares it. lxc info summarises the container's status, type, architecture and resource usage.

The terminal shows lxc exec running inside the web01 container, reporting Ubuntu 24.04 from os-release and the shared host kernel from uname, followed by lxc info summarising the container status, type and architecture

To open an interactive shell inside the container (run this in your own SSH session, not from a script):

sudo lxc exec web01 -- bash

Step 8: Confirm the Secure by Default Posture

LXD ships administered over its local unix socket only. Confirm that the network HTTPS API is not exposed and that this VM has its own server certificate:

A="$(lxc config get core.https_address 2>/dev/null)"; echo "core.https_address='${A}'"
ss -tlnH 'sport = :8443' 2>/dev/null | awk '{print $4}' | grep -vE '^(127\.|\[::1\]|::1)' || echo 'API not exposed to the network'
sudo openssl x509 -in /var/snap/lxd/common/lxd/server.crt -noout -fingerprint -sha256

core.https_address is empty, which means LXD is not listening for the network API at all. Nothing is bound on port 8443 to a non loopback address. The server certificate fingerprint matches the one recorded in /root/lxd-access.txt and is unique to this VM. This is why the only inbound port you need open is SSH.

The terminal shows core.https_address reported as empty meaning the HTTPS API is off, no listener bound on port 8443 to any network address, and the openssl output of this VM's unique per VM server certificate SHA256 fingerprint

Step 9: Snapshots, Copies, Limits, and Cleaning Up

LXD makes containers cheap to snapshot, copy and constrain. A few common operations (run these in your own SSH session):

# Snapshot a container and restore it later
sudo lxc snapshot web01 before-change
sudo lxc restore web01 before-change

# Copy a container to a new one
sudo lxc copy web01 web02

# Set CPU and memory limits
sudo lxc config set web01 limits.cpu 2
sudo lxc config set web01 limits.memory 1GiB

When you are finished with the demo container, delete it:

lxc delete -f web01

Step 10: LXD Virtual Machines and Nested Virtualization

Everything above uses system containers, which share the host kernel and run on any Azure VM size, including Standard_B2s. LXD can also run full virtual machines with lxc launch ubuntu:24.04 v1 --vm. A VM boots its own kernel under a hypervisor, which requires hardware virtualization inside the Azure guest, known as nested virtualization.

Nested virtualization is available only on specific Azure VM sizes (for example the Dv3, Ev3 and newer Intel based series expose it; many burstable B series sizes do not). If you intend to run LXD virtual machines rather than system containers, launch this image on a nested virtualization capable size:

# On a nested-virt capable Azure size, an LXD virtual machine:
sudo lxc launch ubuntu:24.04 v1 --vm
sudo lxc list

If you only need isolated Linux environments, system containers are lighter, faster to start, and require no special VM size.

Step 11: Optionally Expose the HTTPS API with Client Certificate Trust

For normal use you never expose the LXD HTTPS API; you administer LXD over SSH. If you need remote API access or clustering, expose it deliberately and secure it with client certificate trust. Never use a trust password, and never open port 8443 to the public internet. From an SSH session on the VM:

# Turn on the HTTPS API listener
sudo lxc config set core.https_address :8443

# Mint a one-time token for YOUR client's certificate (do NOT set a trust password)
sudo lxc config trust add --name my-workstation

Copy the printed token, then on your workstation run lxc remote add <name> <vm-ip>:8443 and paste the token when prompted; this registers your client's certificate as trusted. Only then, open TCP 8443 in your Azure Network Security Group to your management CIDR only:

az network nsg rule create \
  --resource-group lxd-prod --nsg-name lxd-nsg \
  --name allow-lxd-api-mgmt --priority 120 \
  --source-address-prefixes "<your-mgmt-cidr>" \
  --destination-port-ranges 8443 --access Allow --protocol Tcp

Because trust is by client certificate, an unauthenticated network client cannot control LXD even after you open the port.

Step 12: Component and Filesystem Reference

Component Version Purpose
LXD 5.21 LTS (snap, channel 5.21/stable) System container and virtual machine manager
Ubuntu 24.04 LTS Base operating system
systemd units snap.lxd.daemon.service, lxd-firstboot.service LXD daemon and first boot initialisation
Path Owner Purpose
/snap/bin/lxc, /usr/sbin/lxc root:root LXD command line client
/var/snap/lxd/common/lxd/ root:root LXD data directory: server certificate, database, storage pools
/var/snap/lxd/common/lxd/server.crt root:root This VM's per VM server certificate (generated on first boot)
/usr/local/sbin/lxd-firstboot.sh root:root 0750 First boot initialisation (lxd init preseed, access file)
/etc/systemd/system/lxd-firstboot.service root:root 0644 First boot oneshot unit
/var/lib/cloudimg/lxd-firstboot.done root:root First boot sentinel
/root/lxd-access.txt root:root 0600 Per VM access information and server certificate fingerprint

Step 13: Troubleshooting

lxc reports permission denied. Your admin user is added to the lxd group on first boot, but group membership takes effect on your next login. Either log out and back in, or run commands with sudo lxc ... in the meantime.

A container has no network. Containers get NAT networking through the managed lxdbr0 bridge. Confirm it exists with sudo lxc network list and that the container's profile includes an eth0 device with sudo lxc profile show default. A freshly launched container may take a few seconds to acquire a DHCP lease; re run sudo lxc list to see its assigned IPv4 address.

The storage pool is full. The default pool is directory backed on the OS disk. Check usage with df -h /. Resize the OS disk in the Azure Portal, or attach a data disk and add a second storage pool on it with sudo lxc storage create <name> dir source=/mnt/<mount>.

First boot did not complete. Confirm the oneshot finished with sudo systemctl status lxd-firstboot.service; the expected state after success is active (exited) and the sentinel /var/lib/cloudimg/lxd-firstboot.done exists. If it failed, read sudo journalctl -u lxd-firstboot.service, fix the cause, remove the sentinel, and start the service again.

Step 14: Security Recommendations

  • Keep the LXD HTTPS API off unless you have a specific need for it. For day to day administration, SSH plus the local lxc CLI is the secure path, and the only inbound port you need is SSH from your management range.
  • If you do expose the HTTPS API (Section 10), use client certificate trust, never a trust password, and open TCP 8443 only to trusted source CIDRs, never to the public internet.
  • Treat membership of the lxd group as root equivalent, because a member can launch privileged containers. Grant it only to appliance administrators.
  • Prefer unprivileged containers, which LXD uses by default, over privileged ones. Only set security.privileged=true on a container when you have a specific, understood reason.
  • Keep the base image patched. Unattended security updates are enabled on the OS, and the LXD snap refreshes within its 5.21 LTS channel automatically.

Step 15: Support and Licensing

LXD is published by Canonical under the AGPL-3.0 license, and the cloudimg image installs it unmodified from Canonical's official 5.21 LTS snap channel. The full license text is available on the running server, and upstream source is at https://github.com/canonical/lxd. Ubuntu 24.04 LTS is distributed by Canonical under the terms visible via dpkg -L base-files on the running server.

The cloudimg image itself is distributed under the Microsoft Azure Marketplace standard contract terms, with PAYG pricing visible on the Partner Center plan. cloudimg provides best effort image level support: questions about the image, the first boot initialisation, the secure by default posture, or the storage and networking setup go to https://www.cloudimg.co.uk/support. Upstream LXD bug reports go to https://github.com/canonical/lxd/issues.