CentOS Stream 10 Arm64 on Azure User Guide
Overview
This guide covers the deployment and configuration of CentOS Stream 10 on 64-bit Arm (AArch64/arm64) on Microsoft Azure using cloudimg's pre configured virtual machine image from the Azure Marketplace.
CentOS Stream is the continuously delivered distribution that sits immediately upstream of Red Hat Enterprise Linux. It is where the next RHEL 10 minor release is built in the open, so it carries the RHEL 10 package set and the same 6.12 kernel line, and it is the natural platform for anyone who wants to develop or test against RHEL before it ships. This is the native Arm build. It runs on Azure's Arm virtual machine sizes, which are powered by the Azure Cobalt 100 processor (the Dpsv6, Dplsv6 and Epsv6 families), where many workloads deliver the same performance at a lower cost than on x86.
What's included:
- CentOS Stream 10, native 64-bit Arm (arm64 / AArch64) with the standard 4k page size kernel
- Every available update applied at build time, and again immediately before capture
- Automatic security updates installed, armed and enabled, so the machine keeps patching itself
- Azure Linux Agent (waagent) and cloud-init for Azure integration
- The Arm serial console corrected to
ttyAMA0, so Azure boot diagnostics and the serial console work - Chronyd for NTP time synchronisation against the Azure host clock
- SELinux in enforcing mode
- BaseOS, AppStream and Extras repositories enabled, so your update path is intact
- SSH as the only network listener, with no swap on the operating system disk
- Gen2 Hyper V virtual machine support
- 24/7 cloudimg support
Platform: Microsoft Azure (Gen2 Hyper V, Arm64, Azure Cobalt 100)
Default user: azureuser
CentOS Stream is a rolling release, on Arm
CentOS Stream has no minor releases. It is continuously delivered: VERSION_ID stays 10 for the life of the stream, and every security fix and package update flows into the same stream as it is prepared for the next RHEL 10 minor. There is no frozen point release to pin to and no vault to fall off, so sudo dnf upgrade always moves the machine to the current CentOS Stream 10 package set. CentOS Stream 10 became generally available on 12 December 2024 and is supported until 31 May 2030, the end of the RHEL 10 full support phase.
Because this is the Arm build, it is a genuinely separate product from the x86 CentOS Stream 10 image: it carries aarch64 packages and an aarch64 kernel, and it only runs on Arm virtual machine sizes. It is the standard 4k page size build. The 64k page size kernel is an optional package on this release and is not installed here.
If you need a frozen minor release with a fixed lifecycle rather than a rolling stream, one of the RHEL rebuild images (AlmaLinux, Rocky Linux or Oracle Linux) is the better fit. CentOS Stream is for tracking what RHEL is about to become.
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.
- No account on the image has a usable password at all.
PasswordAuthenticationand keyboard interactive authentication are disabled in the SSH daemon, so every login is by key.PermitRootLoginis set toprohibit-password, whichsshd -Treports under its older namewithout-password, and the root password is locked. The image itself ships noauthorized_keysat all. cloud-init does copy your own public key into root'sauthorized_keysat first boot, but behind its standard forced command, so anssh root@attempt printsPlease login as the user "azureuser" rather than the user "root".and disconnects. Usesudofrom your administrator account instead.- SSH host keys are removed before capture and regenerated on each machine at first boot, so no two machines you launch share a host identity.
/etc/machine-idis cleared before capture and regenerated per machine.- The only key that works is the public key you supply when you create the virtual machine. Azure injects it into the administrator account you name at first boot.
- SELinux is left in enforcing mode, the CentOS default.
- The Cockpit web console, which the upstream CentOS Stream cloud image ships listening on port 9090, has been removed from this image. SSH on port 22 is the only network listener.
Prerequisites
Before deploying this image, ensure 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_D2ps_v6 (2 vCPU, 8 GB RAM, Azure Cobalt 100) or larger. This is an Arm64 image, so it must be launched on an Arm virtual machine size, such as the Dpsv6, Dplsv6 or Epsv6 families. An x86 size such as Standard_B2s will not accept this image.
Step 1: Deploy the Virtual Machine
Option A: Azure Portal
- Navigate to the Azure Marketplace and search for "CentOS Stream 10 Arm64 cloudimg"
- Select the image and click Create
- Configure the basics:
- Subscription: Select your Azure subscription
- Resource Group: Create new or select existing
- Virtual Machine Name: Enter a name for your VM
- Region: Select a region that offers Arm sizes
- Size:
Standard_D2ps_v6recommended (an Arm64 size) - Under Administrator Account, select SSH public key and enter your key
- Under Inbound Port Rules, allow SSH (port 22)
- Click Review + Create, then Create
Option B: Azure CLI
Run this on your own workstation, substituting your resource group, VM name and preferred region. Accept the Marketplace terms once per subscription, then create the machine:
az vm image terms accept --urn cloudimg1647283583153:centos-stream-10-arm64:default:latest
az vm create \
--resource-group myResourceGroup \
--name my-centos-stream-10-arm64-vm \
--image cloudimg1647283583153:centos-stream-10-arm64:default:latest \
--size Standard_D2ps_v6 \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
The --size must be an Arm64 family (Standard_D2ps_v6 above). Azure rejects an x86 size for an Arm image at create time.
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-centos-stream-10-arm64-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 and the architecture
Check that the machine is CentOS Stream 10:
cat /etc/centos-release
CentOS Stream release 10 (Coughlan)
The os-release fields carry the same identity in machine readable form. Note that VERSION_ID is 10 with no minor component, because CentOS Stream does not have minor releases:
grep -E '^(NAME|VERSION|ID|VERSION_ID|PLATFORM_ID)=' /etc/os-release
NAME="CentOS Stream"
VERSION="10 (Coughlan)"
ID="centos"
VERSION_ID="10"
PLATFORM_ID="platform:el10"
/etc/redhat-release records the RHEL family lineage and reads the same on CentOS Stream:
cat /etc/redhat-release
CentOS Stream release 10 (Coughlan)
Confirm the kernel, the architecture and the page size. aarch64 is the proof this is the Arm build, and 4096 is the page size this release ships:
uname -srm
getconf PAGESIZE
Linux 6.12.0-267.el10.aarch64 aarch64
4096
The 64k page size kernel is an optional package on EL10 and is deliberately not installed, so the page size above will not change under you on a kernel update:
rpm -q kernel-64k || echo "(kernel-64k absent - this image is the standard 4k page size build)"
package kernel-64k is not installed
(kernel-64k absent - this image is the standard 4k page size build)
Confirm the processor. On an Azure Arm size the BIOS model name identifies the Cobalt 100 host and the core is an Arm Neoverse N2:
lscpu | grep -E '^(Architecture|Vendor ID|Model name|BIOS Model name):'
Architecture: aarch64
Vendor ID: ARM
Model name: Neoverse-N2
BIOS Model name: Cobalt 100 None CPU @ 3.4GHz

Step 4: Check the patch level and automatic updates
The image is fully patched at capture. dnf check-update exits 0 when nothing is pending and 100 when updates are waiting, so an exit code of 0 is the check you want:
sudo dnf -q check-update >/dev/null 2>&1; echo "exit=$?"
exit=0
Confirm nothing is waiting on a reboot:
sudo dnf needs-restarting -r
No core libraries or services have been updated since boot-up.
Reboot should not be necessary.
The update path is intact. BaseOS carries the core operating system, AppStream carries the application packages and the security updates that reach them, and Extras carries the CentOS specific additions:
sudo dnf repolist --enabled
repo id repo name
appstream CentOS Stream 10 - AppStream
baseos CentOS Stream 10 - BaseOS
extras-common CentOS Stream 10 - Extras packages
A single kernel is installed, the one the machine is running:
rpm -q kernel-core
kernel-core-6.12.0-267.el10.aarch64
Automatic updates
dnf-automatic is installed and its timer is enabled, so the machine applies security updates on its own without any action from you:
systemctl is-enabled dnf-automatic.timer
enabled
It runs daily at 06:00 with up to an hour of random delay, so a fleet does not stampede the mirrors at the same second:
systemctl cat dnf-automatic.timer | grep -E 'OnCalendar|RandomizedDelaySec'
OnCalendar=*-*-* 6:00
RandomizedDelaySec=60m
The policy downloads and applies security updates, and never reboots the machine by itself. An unannounced reboot on a production machine is worse than a deferred kernel, so rebooting is left to you:
grep -E '^(apply_updates|upgrade_type|download_updates|reboot) ' /etc/dnf/automatic.conf
upgrade_type = security
download_updates = yes
apply_updates = yes
reboot = never
To apply everything rather than just security fixes, edit upgrade_type to default in /etc/dnf/automatic.conf. To take all pending updates by hand at any time, run sudo dnf -y upgrade.

Step 5: Verify the security posture
SELinux is enforcing, the CentOS default, and is persisted in /etc/selinux/config so it survives your reboots:
getenforce
Enforcing
The root account password is locked. L in the second field is the proof:
sudo passwd -S root
root L 2009-12-22 -1 -1 -1 -1
No account on the image ships a usable password. This command prints the name of every account that has one, and prints nothing:
sudo awk -F: '($2 !~ /^[!*]/) && ($2 != "") {print $1}' /etc/shadow
Read the SSH policy from the daemon's own effective configuration rather than from the file, since drop-ins and includes can override what you see in /etc/ssh/sshd_config:
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|pubkeyauthentication|kbdinteractiveauthentication) '
permitrootlogin without-password
pubkeyauthentication yes
passwordauthentication no
kbdinteractiveauthentication no
without-password is the older name for prohibit-password: root may not log in with a password. Since root's password is locked and root has no authorized_keys, there is no root login on a machine you launch.
There is no swap on the operating system disk, so nothing pages onto the OS disk and nothing writes secrets to it:
swapon --show
SSH is the only thing listening off box. Port 22 is the entire TCP listener set, and the only UDP socket is chrony's on the loopback address, which is not reachable from off the machine:
ss -tln
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 [::]:22 [::]:*
ss -uln
State Recv-Q Send-Q Local Address:Port Peer Address:Port
UNCONN 0 0 127.0.0.1:323 0.0.0.0:*
UNCONN 0 0 [::1]:323 [::]:*
The upstream CentOS Stream cloud image ships the Cockpit web console enabled and listening on port 9090. It is removed from this image at the package level, so it cannot be re-enabled by accident:
rpm -q cockpit-ws || echo "(cockpit-ws absent - nothing listens on 9090)"
package cockpit-ws is not installed
(cockpit-ws absent - nothing listens on 9090)
rpcbind arrives as a dependency of the NFS client tooling. Both its socket and its service are disabled, so nothing binds port 111:
systemctl is-enabled rpcbind.socket rpcbind.service || true
disabled
disabled

Step 6: Verify Azure integration
The Azure Linux Agent is running and enabled at boot. It is what makes VM extensions, the serial console, password reset and boot diagnostics work:
systemctl is-active waagent
systemctl is-enabled waagent
active
enabled
The Azure guest stack is installed from the CentOS repositories:
rpm -q WALinuxAgent cloud-init chrony
WALinuxAgent-2.14.0.1-3.el10.noarch
cloud-init-24.4-7.el10.1.noarch
chrony-4.8-2.el10.aarch64
cloud-init ran to completion on this boot. It is what injected your SSH key, set the hostname and grew the root filesystem:
cloud-init status
status: done
The serial console is on ttyAMA0, the Arm PL011 UART. This matters on Arm: the upstream cloud image inherits the x86 default of ttyS0, a 16550 UART that does not exist on Cobalt 100, and left alone the Azure serial console and boot diagnostics would show nothing at all. This image corrects it, and the fix persists across kernel upgrades:
grep -o 'console=ttyAMA0[^ ]*' /proc/cmdline
console=ttyAMA0,115200n8
Time is synchronised against the Azure host clock by chronyd, and the machine is on UTC:
systemctl is-active chronyd
active
timedatectl | grep -E 'Time zone|System clock|NTP service'
Time zone: UTC (UTC, +0000)
System clock synchronized: yes
NTP service: active
The disk layout is flat: a 200 MB EFI system partition and an XFS root that cloud-init grows to the full size of the operating system disk on first boot. There is no LVM to unpick. sr0 is the small read-only ISO Azure attaches to pass provisioning data to the machine; it is not storage you use:
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT
NAME SIZE TYPE FSTYPE MOUNTPOINT
sda 30G disk
├─sda1 200M part vfat /boot/efi
└─sda2 29.8G part xfs /
sr0 630K rom
df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 30G 2.2G 28G 8% /
To use a larger operating system disk, set the size when you create the VM, or resize the disk in the portal and reboot. The root filesystem follows it automatically.

Step 7: Networking and the firewall
There is no host firewall in this image. firewalld is not installed:
rpm -q firewalld || echo "(firewalld absent - the Azure NSG is your only firewall)"
package firewalld is not installed
(firewalld absent - the Azure NSG is your only firewall)
The only nftables rules on the machine are the three the Azure Linux Agent installs to protect the Azure wire server endpoint 168.63.129.16. They are present on every Azure Linux image, they only affect traffic to that one address, and they do not filter anything you serve. sudo nft list ruleset shows them:
table ip security {
chain OUTPUT {
type filter hook output priority 150; policy accept;
ip daddr 168.63.129.16 tcp dport 53 counter packets 0 bytes 0 accept
ip daddr 168.63.129.16 ip protocol tcp skuid 0 counter packets 1133 bytes 353881 accept
ip daddr 168.63.129.16 ip protocol tcp ct state invalid,new counter packets 0 bytes 0 drop
}
}
Your Azure Network Security Group is the firewall. It sits in front of the network interface and is the right place to control inbound access, because it drops traffic before it reaches the machine at all. Open only the ports you actually serve, and restrict the source range on SSH to the addresses you administer from.
Add a rule from your own workstation like this:
az network nsg rule create \
--resource-group myResourceGroup \
--nsg-name my-centos-stream-10-arm64-vmNSG \
--name allow-https \
--priority 1010 \
--destination-port-ranges 443 \
--access Allow --protocol Tcp
If you prefer a host firewall as well, install one in the usual way with sudo dnf install -y firewalld and enable it with sudo systemctl enable --now firewalld. Open SSH before you start it, or you will lock yourself out.
Step 8: Install software
AppStream carries the application packages. Everything in the repositories is built for Arm, so dnf resolves aarch64 packages automatically with nothing extra to configure:
sudo dnf -q list --available nginx 2>/dev/null | tail -2
Available Packages
nginx.aarch64 2:1.26.3-15.el10 appstream
Install a package and enable it in the usual way, for example sudo dnf install -y nginx followed by sudo systemctl enable --now nginx, then open the port in your Network Security Group.
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 the administrator you named at create time is the only login until you add one.
Common Use Cases
- Developing and testing against the next RHEL 10 minor release before it ships, on Arm
- Building and validating packages, containers and RPM builds for the RHEL 10 ecosystem on Arm
- Arm build and CI runners on cost efficient Azure Cobalt 100 virtual machines
- Container hosts and cloud native workloads that track a current, continuously delivered EL10 userspace
- Upstream contribution and QA work on the CentOS Stream distribution itself
Troubleshooting
Cannot connect via SSH
Check the Network Security Group allows inbound TCP 22 from your address, confirm the machine is running, and make sure your client is offering the private key that matches the public key you supplied at create time. Password authentication is disabled, so a password prompt means key negotiation already failed. Root cannot log in over SSH, so connect as the administrator you named at create time.
az vm show --resource-group myResourceGroup --name my-centos-stream-10-arm64-vm --show-details --query powerState -o tsv
The VM will not create or will not boot
This is an Arm64 image. It only runs on Arm virtual machine sizes (the Dpsv6, Dplsv6 and Epsv6 Azure Cobalt 100 families and other p sizes). If Azure rejects the size at create time, confirm you selected an Arm size and not an x86 one such as Standard_B2s or Standard_D2s_v5. If the machine never becomes reachable, open Boot diagnostics and the Serial console in the portal, which read from ttyAMA0 on this image.
Azure agent not running
Check whether the agent is running and read its recent log:
systemctl is-active waagent
sudo journalctl -u waagent -n 20 --no-pager
If it reports anything other than active, start it:
sudo systemctl enable --now waagent
Package manager issues
Clear the metadata cache and rebuild it:
sudo dnf clean all
sudo dnf makecache
If a repository is unreachable, confirm the machine has outbound HTTPS and that DNS resolves, since the CentOS mirror system is reached over both.
An update changed behaviour unexpectedly
CentOS Stream is a rolling distribution: packages move forward continuously as the next RHEL 10 minor is prepared, so a dnf upgrade can bring a newer component than you had yesterday. This is the point of the distribution, not a fault. Pin production workloads that need a frozen package set to a RHEL rebuild image instead, and use dnf history to inspect or undo a transaction:
sudo dnf history list 2>/dev/null | awk 'NR<=5'
A service is blocked and the logs mention SELinux
SELinux is enforcing, which is the secure default and should stay that way. Look at the denial rather than disabling SELinux:
sudo ausearch -m AVC -ts recent </dev/null 2>/dev/null || echo "no recent AVC denials"
Most denials on a fresh machine come from software installed into a non standard path. Relabel with sudo restorecon -Rv /path before considering any policy change.
Important Notes
CentOS Stream is free and open source. It is not covered by a single licence: the distribution is a collection of free and open source licences across its package set, predominantly GPL and LGPL, with MIT, BSD, Apache and others. No subscription, entitlement or licence key is required, and no per seat cost applies.
CentOS is a trademark of Red Hat, Inc. cloudimg is not affiliated with, endorsed by or sponsored by Red Hat, and uses the name only to identify the distribution this image contains. Every package in this image comes from the CentOS Project's own repositories.
CentOS Stream 10 became generally available on 12 December 2024 and is supported until 31 May 2030, the end of the RHEL 10 full support phase. It is continuously delivered and has no minor releases, so there is nothing to pin and nothing to migrate between within the stream.
Support
For assistance with this image, contact cloudimg support:
- Website: www.cloudimg.co.uk
- Product Catalogue: www.cloudimg.co.uk/products
- User Guides: www.cloudimg.co.uk/guides
- SLA: 24 hour response guaranteed