LXD
Overview
LXD is Canonical's open-source manager for system containers and virtual machines. 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 command-line client, lxc, and a REST API to launch, snapshot, copy, limit and network those containers.
This image runs LXD on Ubuntu 24.04 LTS as a self-service container host. It is a lighter alternative to a full virtualization host when what you need is many isolated Linux environments on one machine.
What you get:
- LXD delivered as the 5.21 LTS snap, which is Canonical's supported delivery on Ubuntu
- First-boot initialisation: the image runs
lxd initwith a preseed that creates a directory-backed storage pool on the OS disk and a managed bridge network, so you can launch your first container as soon as you sign in over SSH - System containers that run on any Azure VM size, including Standard_B2s. LXD virtual machines, as opposed to system containers, additionally need a VM size that supports nested virtualization. Step 10 covers that.
- No web login and no network-exposed control plane by default. You administer LXD over SSH with the local
lxcCLI, which talks to the LXD daemon over a local unix socket. - The HTTPS API on port 8443 off, and no trust password baked into the image
- A server certificate that every VM creates on its first boot, so no key material is shared between customers. Step 11 documents the optional, deliberate path to exposing the HTTPS API using client certificate trust.
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 (
azversion 2.50 or later) installed locally, if you intend to use the CLI deployment path in step 2 - The SecureAnyCloud LXD offer enabled on your tenant in Azure Marketplace
Deploy the Virtual Machine from the Azure Portal
In the Azure Portal, go to Marketplace, search for LXD, and select the SecureAnyCloud publisher entry. Select Create to start 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. 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. Open TCP 8443, and then only to trusted sources, only if you later choose to expose the LXD HTTPS API (step 11).
On the Management, Monitoring and Advanced tabs, the defaults are appropriate. Select Review + create, wait for validation to pass, then select Create. Deployment takes around two minutes.
Deploy the Virtual Machine from the Azure CLI
To deploy from the command line, accept the Marketplace terms once, then deploy from the Marketplace image URN:
RG="lxd-prod" LOCATION="eastus" VM_NAME="lxd-host-01" ADMIN_USER="lxdadmin" IMAGE_URN="sac1647283583153:lxd-ubuntu-24-04:default: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
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:lxd-ubuntu-24-04:default:latest
az vm create \
--resource-group "$RG" \
--name "$VM_NAME" \
--image "$IMAGE_URN" \
--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 64Increase --os-disk-size-gb to give the directory backed storage pool room for the containers you intend to run.
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. After your first logout and login, you can run lxc without sudo. Until then, prefix commands with sudo as shown below.
Read the LXD access information
On first boot, the lxd-firstboot.service systemd oneshot initialises LXD. It 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.txtThe file shows 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 fingerprint is unique to this virtual machine because it is generated on first boot and never baked into the image.
Verify that 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 listThe 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. The managed bridge lxdbr0 provides NAT networking, so containers get outbound connectivity and an address on a private subnet.
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/nullThe 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 later launches the image is cached, so containers start almost instantly.
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. uname reports the host kernel, because the container shares it. lxc info summarises the container's status, type, architecture and resource usage.
To open an interactive shell inside the container, run this in your own SSH session, not from a script:
sudo lxc exec web01 -- bashConfirm the secure-by-default posture
LXD is 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 -sha256core.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.
Snapshots, copies, limits and cleaning up
LXD makes containers cheap to snapshot, copy and constrain. Run these common operations 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 1GiBWhen you are finished with the demo container, delete it:
lxc delete -f web01LXD 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. This is 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, but many burstable B-series sizes do not. If you intend to run LXD virtual machines rather than system containers, launch this image on a size that supports nested virtualization:
# On a nested-virt capable Azure size, an LXD virtual machine:
sudo lxc launch ubuntu:24.04 v1 --vm sudo lxc listIf you only need isolated Linux environments, system containers are lighter, start faster and require no special VM size.
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 the API 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-workstationCopy 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 TcpBecause trust is by client certificate, an unauthenticated network client cannot control LXD even after you open the port.
Component and filesystem reference
Components:
- 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 and lxd-firstboot.service: LXD daemon and first-boot initialisation
Paths (owner, then purpose):
- /snap/bin/lxc, /usr/sbin/lxc (root:root): LXD command-line client
- /var/snap/lxd/common/lxd/ (root:root): LXD data directory with the server certificate, database and 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/sac/data/lxd-firstboot.done (root:root): first-boot sentinel
- /root/lxd-access.txt (root:root 0600): per-VM access information and server certificate fingerprint
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. 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 that it exists with sudo lxc network list. Confirm 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, so run sudo lxc list again 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 that the oneshot finished with sudo systemctl status lxd-firstboot.service. After success, the expected state is active (exited) and the sentinel /var/lib/sac/data/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.
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
lxcCLI is the secure path, and the only inbound port you need is SSH from your management range. - If you do expose the HTTPS API (step 11), use client certificate trust and never a trust password. Open TCP 8443 only to trusted source CIDRs, never to the public internet.
- Treat membership of the
lxdgroup 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. Set
security.privileged=trueon a container only 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.
Support and licensing
LXD is published by Canonical under the AGPL-3.0 license. The SecureAnyCloud image installs it unmodified from Canonical's official 5.21 LTS snap channel. The full license text is available on the running server, and the 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 SecureAnyCloud image itself is distributed under the Microsoft Azure Marketplace standard contract terms, with PAYG pricing visible on the Partner Center plan. SecureAnyCloud provides best-effort image-level support. Send questions about the image, the first-boot initialisation, the secure-by-default posture, or the storage and networking setup to https://secureanycloud.com/support. Report upstream LXD bugs at https://github.com/canonical/lxd/issues.
Deploy on Azure
Launch LXD System Container & VM Host on Ubuntu 24.04 on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com