Operating Systems Azure

AlmaLinux 10.0 on Azure User Guide

| Product: AlmaLinux 10.0 on Azure

Overview

This guide covers the deployment and configuration of AlmaLinux 10.0 on Microsoft Azure using cloudimg's pre configured virtual machine image from the Azure Marketplace.

AlmaLinux is a community driven enterprise Linux distribution, built by the AlmaLinux OS Foundation to be binary compatible with Red Hat Enterprise Linux 10. Version 10.0 is the first point release in the RHEL 10 family, and this image is frozen to it: every package is pinned to the archived 10.0 vault snapshot, so the platform you certify against today is the exact platform you keep. It is built for reproducible and compliance environments that must not drift to a newer minor.

What's included:

  • AlmaLinux 10.0, binary compatible with RHEL 10, pinned to the 10.0 vault snapshot
  • Every package synced to the 10.0 vault, verified with a zero pending check
  • Unattended updates armed against the frozen 10.0 vault, so the machine applies 10.0 errata without ever rolling forward to a newer minor
  • Azure Linux Agent (waagent) and cloud-init for Azure integration
  • Chronyd for NTP time synchronisation
  • SELinux in enforcing mode, both at runtime and persisted for the next boot
  • BaseOS, AppStream, CRB and Extras vault repositories enabled, with signature checking on
  • Exactly one externally reachable port: SSH on 22 (the Cockpit web console ships disabled)
  • Gen2 Hyper V virtual machine support
  • 24/7 cloudimg support

Platform: Microsoft Azure (Gen2 Hyper V) 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 is by key.
  • KbdInteractiveAuthentication is also disabled, closing the other password path.
  • PermitRootLogin is set to prohibit-password, so root cannot be reached with a password even if one were later set.
  • 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 ever share a secret.
  • No SSH host keys are baked into the image. Each machine generates its own at first boot, so two machines built from this image never share a host identity.
  • /etc/machine-id ships empty and is regenerated per machine.
  • SELinux is left in enforcing mode, the AlmaLinux default.

Prerequisites

Before deploying this image, ensure you have:

  1. An active Microsoft Azure subscription
  2. Access to the Azure Portal or Azure CLI
  3. An SSH key pair for Linux VM access
  4. Familiarity with Azure VM management

Recommended VM Size: Standard_B2s (2 vCPU, 4 GB RAM) or larger.

Step 1: Deploy the Virtual Machine

Option A: Azure Portal

  1. Navigate to the Azure Marketplace and search for "AlmaLinux 10.0 cloudimg"
  2. Select the image and click Create
  3. Configure the basics:
  4. Subscription: Select your Azure subscription
  5. Resource Group: Create new or select existing
  6. Virtual Machine Name: Enter a name for your VM
  7. Region: Select your preferred Azure region
  8. Size: Standard_B2s recommended
  9. Under Administrator Account, select SSH public key and enter your key
  10. Under Inbound Port Rules, allow SSH (port 22)
  11. Click Review + Create, then Create

Option B: Azure CLI

az vm create \
  --resource-group myResourceGroup \
  --name my-almalinux-10-0-vm \
  --image cloudimg:almalinux-10-0:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

Step 2: Connect via SSH

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

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

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

Step 3: Confirm the release

Check that the machine is the AlmaLinux 10.0 release you expect:

cat /etc/almalinux-release
grep -E '^(NAME|VERSION|ID|PLATFORM_ID)=' /etc/os-release
uname -rm

Expected output:

AlmaLinux release 10.0 (Purple Lion)
NAME="AlmaLinux"
VERSION="10.0 (Purple Lion)"
ID="almalinux"
PLATFORM_ID="platform:el10"
6.12.0-55.43.1.el10_0.x86_64 x86_64

The terminal shows cat of etc almalinux-release reporting AlmaLinux release 10.0 Purple Lion, the os-release fields NAME AlmaLinux, VERSION 10.0, ID almalinux and PLATFORM_ID platform el10, and uname reporting the 6.12.0 el10_0 kernel on x86_64

The kernel version carries an el10_0 tag. That is the marker that the machine is on the 10.0 stream. Because this image is frozen to the 10.0 vault, that tag stays el10_0 for the life of the machine.

Step 4: Check the patch level

The image is fully synced to the 10.0 vault at build time. dnf check-update returns exit code 100 when updates are pending against the enabled repositories and 0 when none are. Because the machine is pinned to the frozen 10.0 vault, it reports 0:

sudo dnf check-update
echo "check-update exit code: $?"

Confirm no reboot is outstanding. needs-restarting -r returns 0 when nothing requires a restart:

sudo dnf needs-restarting -r
echo "needs-restarting exit code: $?"

List the enabled repositories to confirm the update path is the vault:

dnf repolist --enabled

Expected output:

repo id                     repo name
appstream-vault-10-0        AlmaLinux 10.0 - AppStream (vault, frozen)
baseos-vault-10-0           AlmaLinux 10.0 - BaseOS (vault, frozen)
crb-vault-10-0              AlmaLinux 10.0 - CRB (vault, frozen)
extras-vault-10-0           AlmaLinux 10.0 - extras (vault, frozen)

The terminal shows dnf check-update returning exit code 0 meaning no updates are pending against the vault, needs-restarting returning 0 meaning no reboot is required, the dnf-automatic timer enabled and active, and the automatic.conf keys showing upgrade_type default with apply_updates yes

Package signatures are verified on every install: no repository on this image sets gpgcheck=0, and the vault packages are signed with the same AlmaLinux 10 GPG key.

Step 5: This image is frozen to the 10.0 vault

This is the defining property of this image, and the reason to choose it over the rolling AlmaLinux 10 image. Every repository points at the archived 10.0 vault at repo.almalinux.org/vault/10.0/, and the rolling mirrorlist and metalink resolvers are disabled, so nothing can pull the machine forward to a newer minor:

grep -h baseurl /etc/yum.repos.d/almalinux-vault-10.0.repo | head -2
grep -rlE '^[[:space:]]*(mirrorlist|metalink)=' /etc/yum.repos.d/ 2>/dev/null || echo "no rolling resolver present"

Expected output:

baseurl=https://repo.almalinux.org/vault/10.0/BaseOS/$basearch/os/
baseurl=https://repo.almalinux.org/vault/10.0/AppStream/$basearch/os/
no rolling resolver present

The terminal shows the four vault repositories enabled, the vault baseurls pointing at repo.almalinux.org vault 10.0, no rolling mirrorlist or metalink present, and waagent, cloud-init and chronyd all healthy

Unattended updates are armed, but against the frozen vault. dnf-automatic applies any errata published within the 10.0 stream and never rolls the machine to a newer minor:

systemctl is-enabled dnf-automatic.timer
grep -E '^(upgrade_type|download_updates|apply_updates)' /etc/dnf/automatic.conf

Expected output:

enabled
upgrade_type = default
download_updates = yes
apply_updates = yes

If you would rather track whichever AlmaLinux 10 release is current, this frozen image is not what you want: use the cloudimg AlmaLinux 10 image, which follows the rolling stream, or convert this machine to the rolling channel yourself by restoring the stock repositories:

echo "to move to the rolling 10 stream: sudo rm /etc/yum.repos.d/almalinux-vault-10.0.repo && sudo dnf config-manager --set-enabled baseos appstream extras && sudo dnf distro-sync -y"

That reverses the pin and lets the machine move to the current 10 series minor. Leaving the vault in place is what keeps the machine reproducible.

Step 6: Verify the security posture

Confirm SELinux is enforcing, root is locked, and SSH is key only:

getenforce
grep -E '^[[:space:]]*SELINUX=' /etc/selinux/config
sudo passwd -S root
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|pubkeyauthentication|kbdinteractiveauthentication)'

Expected output:

Enforcing
SELINUX=enforcing
root L 2009-12-22 -1 -1 -1 -1
permitrootlogin without-password
pubkeyauthentication yes
passwordauthentication no
kbdinteractiveauthentication no

The terminal shows getenforce reporting Enforcing with SELINUX equals enforcing persisted in the config, passwd -S root reporting the root account locked, the effective sshd policy reporting permitrootlogin without-password, pubkeyauthentication yes, passwordauthentication no and kbdinteractiveauthentication no, swapon showing no swap, and the listening sockets showing only SSH on 22 externally with chrony on loopback

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

Both the runtime mode and the persisted /etc/selinux/config value are checked, because a runtime setting that is not persisted would silently revert on your next reboot.

Step 7: Verify Azure integration

Confirm the Azure Linux Agent, cloud-init and time synchronisation are healthy, and check your resources:

systemctl is-active waagent
systemctl is-enabled waagent
cloud-init --version
systemctl is-active chronyd
df -h /
free -h

waagent is what lets Azure provision your SSH key, resize the OS disk, run extensions and report VM health, so it should always be active and enabled.

Note that free -h reports 0B of swap. That is deliberate. Azure manages swap on the ephemeral resource disk through waagent rather than on the OS disk, and a swap file baked into a Marketplace image fails certification. Configure swap yourself through waagent if your workload needs it.

Step 8: Networking and open ports

The Azure Network Security Group is the control plane for inbound and outbound traffic, and it is the first and usually the only place you need to open a port.

Confirm what is listening before you open anything:

ss -tlnu

On a fresh machine SSH on port 22 is the only externally bound listener. Chrony appears on 127.0.0.1:323, which is loopback only and not reachable from the network.

The Cockpit web console is disabled on this image. An operating system base should expose exactly one service, so this image disables the socket and keeps the port closed, while leaving the Cockpit packages installed so you can turn it on in one command:

echo "cockpit.socket: $(systemctl is-enabled cockpit.socket 2>/dev/null || echo disabled)"
echo "to enable the Cockpit web console on 9090: sudo systemctl enable --now cockpit.socket"

rpcbind is disabled on this image. The stock cloud image enables it because nfs-utils depends on it, which leaves TCP and UDP port 111 open on every interface. NFSv4, the default in AlmaLinux 10, does not use rpcbind at all, so this image disables it and closes the port while keeping nfs-utils installed and fully working for NFSv4 mounts:

echo "rpcbind.socket: $(systemctl is-enabled rpcbind.socket 2>/dev/null || echo disabled)"
rpm -q nfs-utils

If you specifically need NFSv3, which does require the portmapper, turn it back on with one command:

echo "to re-enable NFSv3 support: sudo systemctl enable --now rpcbind.socket"

Both services are disabled rather than masked precisely so those commands work without further steps.

If your policy requires a host firewall as well, install and enable it yourself:

sudo dnf install -y firewalld
sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-port=443/tcp
sudo firewall-cmd --reload

Once firewalld is running, a port must be open in both the Azure NSG and firewalld before traffic reaches your service.

Step 9: Install software

AppStream carries the application packages, served from the 10.0 vault. Search it, then install what you need:

dnf search nginx

Install a package and enable it in the usual way, for example sudo dnf install -y nginx followed by sudo systemctl enable --now nginx. Everything you install comes from the frozen 10.0 vault, so a package you add today resolves to the same version on a machine you build from this image next year.

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

getent group wheel

Common Use Cases

  • Reproducible and compliance builds pinned to a fixed minor
  • Certifying software against an exact 10.0 platform
  • A stable base for long lived workloads that must not drift to a newer minor
  • Recreating a historical environment for audit or forensic work

Troubleshooting

Cannot connect via SSH

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

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

Azure agent not running

systemctl is-active waagent
sudo systemctl enable --now waagent

Package manager issues

Refresh the metadata cache first:

sudo dnf clean all
sudo dnf makecache

If repositories are unreachable, check DNS resolution:

getent hosts repo.almalinux.org

Install bind-utils if you want dig and nslookup available.

A service is blocked and the logs mention SELinux

SELinux is enforcing on this image. Rather than disabling it, look at what was denied. On a healthy machine there are no recent denials, so the command reports none:

sudo ausearch -m AVC -ts recent 2>/dev/null || echo "no recent AVC denials"

If a denial is genuine, prefer setting the correct file context with semanage fcontext and restorecon, or enabling the relevant boolean with setsebool -P, over turning SELinux off.

Checking for failed services

A healthy machine reports running with nothing failed:

systemctl is-system-running
systemctl list-units --state=failed --no-legend

Important Notes

AlmaLinux is free and open source, and it is a collection of licences rather than a single one. This image carries several hundred installed packages between them declaring many distinct licence strings, most commonly LGPL-2.1-or-later, GPL-2.0-or-later, MIT, GPL-3.0-or-later, GPL-2.0-only, BSD-3-Clause and LGPL-3.0-or-later. Each package's own terms are readable on the machine:

rpm -qa --qf '%{LICENSE}\n' | sort | uniq -c | sort -rn | awk 'NR<=8'

No subscription, licence key or support contract is required for any of it.

AlmaLinux 10 follows the Red Hat Enterprise Linux 10 lifecycle. This image is frozen to the 10.0 point release through the archived vault, which is exactly what a reproducible or compliance environment wants. If you would rather track whichever 10 series release is current, use the cloudimg AlmaLinux 10 image instead, or convert this machine to the rolling channel with the command shown in Step 5.

Support

For assistance with this image, contact cloudimg support: