Deployment guide

Ubuntu 22.04 Minimal

Overview

This guide shows you how to deploy and configure Ubuntu Server 22.04 LTS Minimal on Microsoft Azure, using SecureAnyCloud's preconfigured virtual machine image from the Azure Marketplace.

Ubuntu is a widely used Linux distribution produced by Canonical. Version 22.04 is a long-term support (LTS) release, with standard security maintenance to mid 2027. What sets this image apart from the standard Ubuntu Server 22.04 LTS image is the package profile. The installed set is trimmed to Canonical's own published Ubuntu Minimal cloud image seed, which is the package set Canonical defines as Minimal for this release, plus the Azure provisioning stack.

The result, measured at capture on this exact image, is 629 packages on the standard image and 435 on this one (194 fewer), with a dpkg payload 443 MiB smaller. Fewer installed packages means a smaller attack surface, fewer packages to patch and scan, and faster update runs.

The image describes itself. A manifest generated from its own package database ships at /usr/share/sac/ubuntu-22-04-minimal.manifest. It states what is installed, what was removed and what that means in practice. Anything you miss is one apt install away from the standard Ubuntu archives.

What you get:

  • Ubuntu Server 22.04 LTS with the Minimal package profile: 435 packages, cut to Canonical's published Minimal seed from SecureAnyCloud's own hardened standard image
  • The full Azure provisioning path, protected from the trim by construction: cloud-init, the Azure Linux Agent (walinuxagent), OpenSSH, netplan, the Azure kernel flavour and its initramfs boot chain
  • A shipped manifest and trim ledger derived from the image's own package database, so the delta is measured and never asserted
  • Every available security update applied at build time, with unattended-upgrades armed so the machine keeps patching itself (the update path is part of the Minimal seed)
  • Automatic root filesystem growth at first boot on larger disks (cloud-init growpart, shown below)
  • AppArmor in enforcing mode
  • Gen2 Hyper-V virtual machine support
  • 24/7 SecureAnyCloud support

Not included. This matches Canonical's Minimal seed, and each item is one apt install away. The image leaves out the man command, full-screen editors (vim, nano), ping and network diagnostic tools, classic cron (systemd timers remain), rsyslog (journald remains), an NFS client, ufw, and a long tail of libraries and utilities that a cloud VM managed by automation never touches.

Platform: Microsoft Azure (Gen2 Hyper-V, x64). Default user: azureuser.

Security posture

This image ships with no known credential. There is no default password and no baked-in SSH key:

  • The root account password is locked, so no one can log in as root with a password.
  • PasswordAuthentication is disabled in the SSH daemon, so every login uses a key.
  • PermitRootLogin is set to prohibit-password, so root cannot be reached with a password even if one is set later.
  • The only key that works is the public key you supply when you create the virtual machine. Azure injects it into azureuser at first boot, so no two machines you launch share a secret.
  • The machine identity and the SSH host keys are regenerated on every instance, so no two machines share a host key either.
  • AppArmor stays enabled and enforcing, which is the Ubuntu default.
  • The trim is itself a security feature. With 194 fewer installed packages, there are 194 fewer things to carry vulnerabilities, patch and audit.

Prerequisites

Before you deploy this image, make sure you have:

  • An active Microsoft Azure subscription
  • Access to the Azure Portal or Azure CLI
  • An SSH key pair for Linux VM access
  • Familiarity with Azure VM management

Recommended VM size: Standard_B2s (2 vCPU, 4 GB RAM) or larger. The Minimal profile is also comfortable on Standard_B1s for light duties.

Disk size: the image ships with a 30 GiB OS disk. If you deploy onto a larger disk, the root filesystem grows itself at first boot with no manual steps (see Step 7).

1

Deploy the Virtual Machine

Option A: Azure Portal

  • Go to the Azure Marketplace and search for "Ubuntu 22.04 LTS Minimal SecureAnyCloud".
  • Select the image and choose Create.
  • On the basics tab, set Subscription to your Azure subscription and Resource Group to a new or existing group.
  • Enter a Virtual Machine Name, select your preferred Region, and set Size to Standard_B2s (recommended).
  • Under Administrator Account, select SSH public key and enter your key.
  • Under Inbound Port Rules, allow SSH (port 22).
  • On the Disks tab, set the OS disk size you want. The root filesystem fills it automatically at first boot.
  • Choose Review + Create, then Create.

Option B: Azure CLI

az vm image terms accept --urn sac1647283583153:ubuntu-22-04-minimal:default:latest
az vm create \
  --resource-group myResourceGroup \
  --name my-ubuntu-minimal-vm \
  --image sac1647283583153:ubuntu-22-04-minimal:default:latest \
  --size Standard_B2s \
  --os-disk-size-gb 64 \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

--os-disk-size-gb 64 deploys the 30 GiB image onto a 64 GiB disk. Unlike an LVM layout, nothing more is needed. cloud-init grows the partition and the filesystem to fill the disk during first boot.

2

Connect via SSH

Run these commands on your own workstation, not on the VM. Find the public IP, then connect:

az vm show --resource-group myResourceGroup --name my-ubuntu-minimal-vm --show-details --query publicIps -o tsv ssh azureuser@<vm-ip>

There is no password to enter. If SSH asks you for one, your client is not offering the key you supplied at create time.

3

Confirm the release and the Minimal profile

Check that the machine runs the Ubuntu 22.04 LTS release you expect:

grep -E '^(NAME|VERSION|ID|VERSION_ID|PRETTY_NAME)=' /etc/os-release uname -srm dpkg --print-architecture

The output looks like this:

PRETTY_NAME="Ubuntu 22.04.5 LTS" NAME="Ubuntu" VERSION_ID="22.04" VERSION="22.04.5 LTS (Jammy Jellyfish)" ID=ubuntu Linux 6.8.0-1065-azure x86_64 amd64

The -azure kernel matters. This image keeps the Azure kernel flavour of the standard image, with the Hyper-V drivers built in. Canonical uses the same arrangement for its own Azure images.

Now confirm the defining property of this image, the Minimal package profile:

dpkg-query -Wf '${Package}\n' | sed 's/:.*$//' | sort -u | wc -l df -h /

On a fresh VM deployed with the default 30 GiB disk, the output looks like this:

435 Filesystem Size Used Avail Use% Mounted on /dev/root 29G 1.5G 28G 5% /

This image has 435 packages, against 629 on the standard image. The trim ran as a single guarded first package transaction, simulated first, against Canonical's published Minimal manifest. The provisioning path (cloud-init, the Azure agent, OpenSSH, netplan, the boot chain) is on the guard's protected list, so the trim cannot remove it.

4

Read the shipped manifest

The image states what it is. The manifest is generated from the package database at capture and is never written by hand, so it cannot disagree with the machine you are on:

head -8 /usr/share/sac/ubuntu-22-04-minimal.manifest

The output looks like this:

# Ubuntu 22.04 LTS Minimal — sac shipped manifest
# DERIVED from the dpkg database on this image at 2026-09-12T02:41:39Z (capture time) — never hand-written.
# Baseline: the sac 'Ubuntu Server 22.04 LTS' (std) image this Minimal is cut from.
#
# Measured trim delta vs the std baseline: 629 -> 435 packages (-194), dpkg payload -443 MiB.
# Trim target: Canonical's published Ubuntu 22.04 Minimal cloud-image package seed (manifest sha256 58a9ea8c5fdea393...). # Guard refusals (candidates KEPT because their removal would cascade a keeper): hwdata libdw1 libip4tc2 libip6tc2 libnetfilter-conntrack3 libnfnetlink0 libnftnl11 libnuma1 libpci3 libpython3.10 libslang2 libtraceevent1 libunwind8 pci.ids usb.ids # Installed now: 435 packages, dpkg payload 871 MiB, rootfs used 2092 MiB.

The capability notes further down the file say what is not on the image and how to restore each piece:

sed -n '/capability notes/,/^#$/p' /usr/share/sac/ubuntu-22-04-minimal.manifest

The output looks like this:

# --- capability notes, each DERIVED from a probe on this image --- NFS: this image has NO NFS client. nfs-common is not part of the Ubuntu Minimal seed, NFS: so 'mount -t nfs' will not work as shipped. Restore it with:
sudo apt install nfs-common MAN: no man command (man-db is not part of the Minimal seed). Restore with:
sudo apt install man-db CRON: no classic cron daemon (cron is not part of the Minimal seed); systemd timers CRON: provide scheduling. Restore classic cron with:
sudo apt install cron EDITOR: no full-screen editor ships (matching Canonical's Minimal seed). EDITOR: Install one with: sudo apt install nano (or vim) PROVISIONING: cloud-init 26.1 + walinuxagent 2.15.0.1 are installed — the full Azure provisioning path is retained. KERNEL: 6.8.0-1065-azure (the Azure kernel flavour — Hyper-V drivers built in, matching Canonical's own Azure minimal SKUs). UPDATES: unattended-upgrades is installed and armed — security updates auto-apply, exactly as on the std image. DOCS: unlike Canonical's minimal cloud image, this image does NOT strip documentation of DOCS: RETAINED packages at the dpkg level — the trim is package-level; /usr/share/doc of the kept set is intact.

The full installed package list follows the notes in the same file, one package per line with its version. Use it for compliance snapshots and drift comparisons.

5

Check the patch level

The image is fully updated at build time. Confirm that no upgrades are pending and that the update path is intact:

apt-get -s -o APT::Get::Always-Include-Phased-Updates=true dist-upgrade | grep -c '^Inst ' | sed 's/^/pending upgrades: /' apt-mark showhold dpkg -l | awk '/^ii linux-image-[0-9]/{print $2, $3}'

The output shows pending upgrades: 0, no held packages, and exactly one installed Azure kernel:

pending upgrades: 0 linux-image-6.8.0-1065-azure 6.8.0-1065.73~22.04.1

The Always-Include-Phased-Updates=true flag matters on Ubuntu. Without it, a plain check can hide phased security updates that are held back on this machine.

This image also arms unattended-upgrades, which is part of Canonical's Minimal seed, so automatic security updates survive the trim by design. You can still run updates on your own schedule:

sudo apt-get update && sudo apt-get -y dist-upgrade

Reboot if a new kernel was installed:

sudo systemctl reboot
6

Verify the security posture

Confirm that AppArmor is enforcing, that root is locked, and that SSH accepts keys only:

sudo aa-status --enabled && echo "apparmor: enabled"
sudo passwd -S root
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|pubkeyauthentication) '

The output looks like this:

apparmor: enabled root L 12/22/2009 -1 -1 -1 -1 permitrootlogin without-password pubkeyauthentication yes passwordauthentication no

without-password is how OpenSSH reports the prohibit-password setting. Both names mean the same thing: root may not authenticate with a password. The L from passwd -S means that the root account is locked.

7

Disk growth: automatic on this image

If you deployed onto a disk larger than 30 GiB, as in the Step 1 CLI example, you do not need to do anything. This image keeps the standard flat root layout, and cloud-init's growpart module extends the root partition and filesystem during first boot. Confirm it:

df -h / lsblk -o NAME,SIZE,TYPE,MOUNTPOINT "$(readlink -f /dev/disk/azure/root)"

On a 64 GiB disk, this image produces the output below. On 22.04, the root partition is sda1 and /boot lives on the root filesystem. There is no separate boot partition:

Filesystem Size Used Avail Use% Mounted on /dev/root 62G 1.5G 61G 3% / NAME SIZE TYPE MOUNTPOINT sda 64G disk |-sda1 63.9G part / |-sda14 4M part `-sda15 106M part /boot/efi

cloud-init's own log records the automatic grow:

grep 'resized' /var/log/cloud-init.log | tail -1

The output looks like this. Byte counts vary with your disk size:

2026-09-12 02:52:49,862 - cc_growpart.py[INFO]: '/' resized: changed (/dev/disk/by-partuuid/ce587b22-55bb-4ef3-a3db-64be7e36f126) from 32096894464 to 68603067904

Growing an existing VM later. Enlarge the OS disk. Deallocate the VM, run az disk update --resource-group <rg> --name <os-disk-name> --size-gb <new-size>, and start the VM. You can also use the portal's Disks blade. cloud-init grows the root again on the next boot. This layout never needs manual partitioning.

8

Networking and the firewall

The Azure Network Security Group controls inbound and outbound traffic. It is the first, and usually the only, place where you open a port. The image exposes a single inbound port, SSH on 22. Confirm what is listening:

sudo ss -tlnp

The output shows sshd on port 22 (IPv4 and IPv6) and systemd-resolved on the loopback-only stub address:

State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=607,fd=3)) LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=416,fd=14)) LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=607,fd=4))

ufw is not installed on this image, because it is not part of Canonical's Minimal seed. The NSG is the enforced firewall, which is the normal arrangement on Azure. If your policy also requires a host firewall, install and enable one. Add the SSH rule first so that you do not lock yourself out:

sudo apt install -y ufw
sudo ufw allow OpenSSH
sudo ufw enable

Once ufw is running, a port must be open in both the Azure NSG and ufw before traffic reaches your service. The iptables layer is present on the image, and the Azure agent uses it to protect the platform metadata endpoint.

9

Install software

The trim does not put any software out of your reach. The machine points at the standard Ubuntu archives, so every removed package, and anything else in the archive, installs normally. Search, then install what you need:

apt-cache search --names-only '^nginx' 2>/dev/null | sed -n '1,3p'

Install a package and enable it in the usual way, for example sudo apt-get install -y nginx followed by sudo systemctl enable --now nginx.

The shipped manifest (Step 4) lists the everyday absences and their one-line restores: man-db for manual pages, nano or vim for an editor, iputils-ping for ping, cron for classic cron jobs, and nfs-common for NFS mounts.

To add another administrator, create the account with sudo adduser <name>. Add it to the sudo group with sudo usermod -aG sudo <name>. Then copy your public key into /home/<name>/.ssh/authorized_keys, with mode 600 on the file and mode 700 on the .ssh directory, both owned by that user. The image ships with no human accounts, so azureuser is the only login until you add one:

getent group sudo

The output looks like this:

sudo:x:27:azureuser

Common Use Cases

  • Container and automation hosts that want the smallest practical Ubuntu 22.04 base under their workloads
  • Security-conscious deployments with fewer installed packages to patch, scan and audit
  • Fleets managed entirely by SSH and configuration management, where interactive tools add nothing
  • A fast-booting, quick-patching Ubuntu LTS base for ephemeral and scale-out workloads

Troubleshooting

Cannot connect via SSH

  • Verify that the VM is in the Running state in the Azure Portal.
  • Check that port 22 is allowed in the Network Security Group.
  • Make sure you are using the correct username: azureuser.
  • Verify that your SSH key matches the one you supplied at create time. Password login is disabled, so a wrong key cannot fall back to a password prompt.

To see which key your client offers, run ssh -v azureuser@<vm-ip> from your workstation and look for the Offering public key lines.

"command not found" for man, vim, nano, ping or crontab

This is expected. Those tools are not part of Canonical's Minimal seed, which this image follows. Each is one command away (see the shipped manifest, Step 4): sudo apt install man-db, sudo apt install nano, sudo apt install iputils-ping, sudo apt install cron.

Until you install an editor, sed -i and here-documents cover most quick edits, and systemctl edit opens its own fallback editor.

df shows 29G on a bigger disk

The automatic grow runs during first boot. If you resized the disk while the VM was running, the VM has not rebooted since the disk grew. Reboot once (sudo systemctl reboot) and cloud-init claims the space.

Check /var/log/cloud-init.log. A cc_growpart line reading '/' resized: changed means the grow ran. A line reading '/' NOCHANGE: no change necessary means the partition already fills the disk.

Azure agent not running

sudo systemctl status walinuxagent

If the agent is not running, start it on the VM:

sudo systemctl enable --now walinuxagent

Package manager issues

Refresh the metadata cache first:

sudo apt-get update

If repositories are unreachable, check DNS resolution. getent is always present and resolves through the same system resolver:

getent hosts archive.ubuntu.com

A service is blocked and the logs mention AppArmor

AppArmor is enforcing on this image. A healthy machine shows zero denials. A fresh instance of this exact build, including first boot, measured zero. cloud-init's provisioning DHCP probe runs dhclient, which completes on 22.04 without tripping any profile. The check below excludes that probe anyway, so it stays at zero even on releases where the probe is denied:

sudo journalctl -k --no-pager | awk '/apparmor/ && /DENIED/ && $0 !~ /sbin\/dhclient/{n++} END{print "apparmor denials: " n+0}'

The output looks like this:

apparmor denials: 0

If you see denials after installing software, the denied profile's name appears in the log line. Adjust or disable that one profile instead of turning AppArmor off.

Important Notes

Ubuntu is free and open source. A single licence does not cover it. It is a distribution assembled from thousands of independently packaged components, each with its own terms, including the GPL, LGPL, MIT, BSD, Apache 2.0 and MPL. You can read every package's terms with dpkg -s <package> and under /usr/share/doc/<package>/copyright.

No subscription or licence key is required to run this image. Ubuntu Pro is an optional Canonical subscription that is neither included nor required here.

Ubuntu 22.04 LTS receives standard security maintenance until mid 2027. Plan a move to a newer LTS image before that date for workloads that need to outlive it.

This image's Minimal profile works at the package level. The installed set matches Canonical's published Minimal cloud image seed, plus the Azure provisioning stack. Documentation that belongs to the remaining packages stays on disk. Canonical's own minimal cloud image also strips documentation files at the dpkg level. If you want that behaviour, install their image outside the Marketplace. This image keeps the documentation.

Ubuntu is a trademark of Canonical Ltd. The name is used here only to identify the distribution this image contains. SecureAnyCloud is not affiliated with, endorsed by or sponsored by Canonical Ltd.

Support

For assistance with this image, contact SecureAnyCloud support:

  • Website: secureanycloud.com
  • Product Catalogue: secureanycloud.com/products
  • User Guides: secureanycloud.com/guides
  • SLA: 24 hour response guaranteed

Deploy on Azure

Launch Ubuntu 22.04 LTS Minimal on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure