CentOS Stream 10 ARM64
Overview
This guide covers the deployment and configuration of CentOS Stream 10 on 64-bit Arm (AArch64/arm64) on Microsoft Azure. It uses SecureAnyCloud's preconfigured virtual machine image from the Azure Marketplace.
CentOS Stream is the continuously delivered distribution that sits immediately upstream of Red Hat Enterprise Linux. The next RHEL 10 minor release is built in the open on it, so it carries the RHEL 10 package set and the same 6.12 kernel line. It is a natural platform for developing or testing 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). Many workloads deliver the same performance on these sizes at a lower cost than on x86.
What you get:
- 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 SecureAnyCloud 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. 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.
This Arm build is a 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, a RHEL rebuild image (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. At first boot, cloud-init copies your own public key into root'sauthorized_keysbehind its standard forced command. Anssh root@attempt therefore 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 you deploy this image, make sure that 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 you must launch it 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.
Deploy the Virtual Machine
Option A: Azure Portal
- Go to the Azure Marketplace and search for "CentOS Stream 10 Arm64 SecureAnyCloud".
- Select the image and click Create.
- Configure the basics:
- Subscription: select your Azure subscription.
- Resource Group: create a new one or select an existing one.
- Virtual Machine Name: enter a name for your VM.
- Region: select a region that offers Arm sizes.
- Size:
Standard_D2ps_v6is recommended (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. Substitute 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 sac1647283583153:centos-stream-10-arm64:default:latest
az vm create \
--resource-group myResourceGroup \
--name my-centos-stream-10-arm64-vm \
--image sac1647283583153:centos-stream-10-arm64:default:latest \
--size Standard_D2ps_v6 \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku StandardThe --size must be an Arm64 size (Standard_D2ps_v6 above). Azure rejects an x86 size for an Arm image at create time.
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 for one, your client is not offering the key that you supplied at create time.
Confirm the release and the architecture
Check that the machine is CentOS Stream 10:
cat /etc/centos-releaseCentOS 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-releaseNAME="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-releaseCentOS 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 PAGESIZELinux 6.12.0-267.el10.aarch64 aarch64 4096The 64k page size kernel is an optional package on EL10 and is deliberately not installed. The page size above therefore does not change 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.4GHzCheck 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 you want an exit code of 0:
sudo dnf -q check-update >/dev/null 2>&1; echo "exit=$?"exit=0Confirm nothing is waiting on a reboot:
sudo dnf needs-restarting -rNo 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. Extras carries the CentOS-specific additions:
sudo dnf repolist --enabledrepo id repo name appstream CentOS Stream 10 - AppStream baseos CentOS Stream 10 - BaseOS extras-common CentOS Stream 10 - Extras packagesA single kernel is installed, the one the machine is running:
rpm -q kernel-corekernel-core-6.12.0-267.el10.aarch64Automatic updates
dnf-automatic is installed and its timer is enabled, so the machine applies security updates on its own:
systemctl is-enabled dnf-automatic.timerenabledIt runs daily at 06:00 with up to an hour of random delay, so a fleet does not hit the mirrors at the same second:
systemctl cat dnf-automatic.timer | grep -E 'OnCalendar|RandomizedDelaySec'OnCalendar=*-*-* 6:00 RandomizedDelaySec=60mThe 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 you decide when to reboot:
grep -E '^(apply_updates|upgrade_type|download_updates|reboot) ' /etc/dnf/automatic.confupgrade_type = security download_updates = yes apply_updates = yes reboot = neverTo apply every update rather than only security fixes, set upgrade_type to default in /etc/dnf/automatic.conf. To take all pending updates by hand at any time, run sudo dnf -y upgrade.
Verify the security posture
SELinux is enforcing, the CentOS default. The setting is persisted in /etc/selinux/config, so it survives reboots:
getenforceEnforcingThe root account password is locked. An L in the second field confirms it:
sudo passwd -S rootroot L 2009-12-22 -1 -1 -1 -1No account on the image ships a usable password. This command prints the name of every account that has one, and it prints nothing:
sudo awk -F: '($2 !~ /^[!*]/) && ($2 != "") {print $1}' /etc/shadowRead the SSH policy from the daemon's effective configuration rather than from the file. 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 nowithout-password is the older name for prohibit-password: root may not log in with a password. Root's password is locked and root has no authorized_keys, so 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 --showSSH is the only service listening off the machine. Port 22 is the entire TCP listener set. The only UDP socket is chrony's on the loopback address, which is not reachable from off the machine:
ss -tlnState 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 -ulnState 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 with the Cockpit web console enabled and listening on port 9090. This image removes it 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 || truedisabled disabledVerify Azure integration
The Azure Linux Agent is running and enabled at boot. It provides VM extensions, the serial console, password reset and boot diagnostics:
systemctl is-active waagent
systemctl is-enabled waagentactive enabledThe Azure guest stack is installed from the CentOS repositories:
rpm -q WALinuxAgent cloud-init chronyWALinuxAgent-2.14.0.1-3.el10.noarch cloud-init-24.4-7.el10.1.noarch chrony-4.8-2.el10.aarch64cloud-init ran to completion on this boot. It injected your SSH key, set the hostname and grew the root filesystem:
cloud-init statusstatus: doneThe 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, so the Azure serial console and boot diagnostics would show nothing at all. This image corrects the setting, and the fix persists across kernel upgrades:
grep -o 'console=ttyAMA0[^ ]*' /proc/cmdlineconsole=ttyAMA0,115200n8Time is synchronised against the Azure host clock by chronyd, and the machine is on UTC:
systemctl is-active chronydactivetimedatectl | grep -E 'Time zone|System clock|NTP service'Time zone: UTC (UTC, +0000) System clock synchronized: yes NTP service: activeThe 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. sr0 is the small read-only ISO that Azure attaches to pass provisioning data to the machine. It is not storage you use:
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTNAME SIZE TYPE FSTYPE MOUNTPOINT sda 30G disk ├─sda1 200M part vfat /boot/efi └─sda2 29.8G part xfs / sr0 630K romdf -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 grows to match automatically.
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 that the Azure Linux Agent installs to protect the Azure wire server endpoint 168.63.129.16. They are present on every Azure Linux image and only affect traffic to that one address. 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 drops traffic before it reaches the machine, so it is the right place to control inbound access. Open only the ports you serve, and restrict the SSH source range to the addresses you administer from.
To add a rule, run this on your own workstation:
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 TcpIf you also want a host firewall, install it 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.
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 -2Available Packages nginx.aarch64 2:1.26.3-15.el10 appstreamInstall a package and enable it in the usual way. For example, run 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 on the file and mode 700 on the .ssh directory, both 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 that the Network Security Group allows inbound TCP 22 from your address, and confirm that the machine is running. Make sure your client offers 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 tsvThe 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 that 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. Both 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-pagerIf it reports anything other than active, start it:
sudo systemctl enable --now waagentPackage manager issues
Clear the metadata cache and rebuild it:
sudo dnf clean all
sudo dnf makecacheIf a repository is unreachable, confirm that the machine has outbound HTTPS and that DNS resolves. 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.
Run production workloads that need a frozen package set on a RHEL rebuild image instead. 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, so keep it 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 you consider any policy change.
Important Notes
CentOS Stream is free and open source. It is not covered by a single licence. Its package set carries many free and open-source licences, 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. SecureAnyCloud 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 help 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 CentOS Stream 10 Arm64 on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com