Alpine Linux 3.24 on Azure User Guide
Overview
This guide covers the deployment, verification and day one operation of Alpine Linux 3.24 on Microsoft Azure, using the cloudimg virtual machine image from the Azure Marketplace.
Alpine Linux is a security oriented, extremely small general purpose Linux distribution. It is best known as the base of a very large share of the world's container images, but it is equally at home as a full virtual machine operating system where you want a minimal attack surface and a small resource footprint. Alpine is built on musl libc and BusyBox rather than glibc and GNU coreutils, and it uses OpenRC as its init system rather than systemd. The installed footprint of this image is 235 packages and roughly 157 MiB.
Those choices are the point of Alpine, and they are the main thing to understand before you deploy it. Software distributed only as a glibc binary may need a compatibility shim or a rebuild, and any runbook or automation you already have that calls systemctl will need translating to rc-service and rc-update. In exchange you get a base system small enough to audit, that boots in seconds and that leaves almost nothing running.
This image was captured from Alpine 3.24.1 and every available update had been applied at the moment of capture.
What is included:
- Alpine Linux 3.24.1, fully patched at the moment the image was captured
- A daily automatic patch job, enabled and verified, that keeps the instance current within the 3.24 stable branch
- cloud init with the Azure datasource for full platform provisioning, including SSH key injection and host naming
- Serial console access enabled for out of band recovery
sudo,bash,curl,ca-certificatesandutil-linuxadded to the minimal base so standard tooling and automation work normally- SSH hardened: no password authentication, no root login, no keyboard interactive authentication
- No baked password, no bundled SSH key, no shared SSH host key and no swap on the operating system disk
- Generation 2 Hyper V virtual machine support
- 24/7 cloudimg support
Platform: Microsoft Azure (Generation 2 Hyper V)
Default user: azureuser
Default shell: /bin/ash (BusyBox)
Recommended size: Standard_B2s (2 vCPU, 4 GB RAM) or larger
Licensing
Alpine Linux is free software, but it is not covered by a single licence. Alpine is a distribution: a collection of thousands of independently packaged components, each carrying its own terms. You will find MIT, BSD, GPL, LGPL, Apache 2.0, ISC and many others across the repositories.
This image enables exactly two repositories, both of them Alpine's own: main and community on the pinned v3.24 stable branch. The rolling edge and testing repositories are not enabled, because a product image that tracked a rolling branch could not honestly be described as Alpine 3.24. Step 4 shows you how to confirm both facts for yourself, and how to list the licence of any installed package.
Running Alpine Linux requires no licence key, no paid subscription, no registration and no support contract. Alpine 3.24 is a supported stable branch with security maintenance until 2028-06-01 as published by the Alpine Linux project.
A note on Azure VM extensions
Alpine does not ship the Azure Linux Agent (waagent), and neither does this image. Provisioning is handled entirely by cloud init's Azure datasource, which is fully supported by the platform and is what injects your SSH key and sets the host name.
The practical consequence is that Azure VM extensions are not available on this image. That includes az vm run-command, the VMAccess password reset extension, and the Azure Monitor and custom script extensions. If you rely on those, this is the wrong image for that workload. If you do lock yourself out, the serial console in Step 10 is the supported out of band recovery path and it works normally.
Prerequisites
Before deploying this image, ensure you have:
- An active Microsoft Azure subscription
- Access to the Azure Portal or the Azure CLI
- An SSH key pair for Linux virtual machine access
- Familiarity with Azure virtual machine management
Step 1: Deploy the virtual machine
Option A: Azure Portal
- Open the Azure Marketplace and search for "Alpine Linux cloudimg"
- Select the image and click Create
- Configure the basics:
- Subscription: your Azure subscription
- Resource group: create a new one or select an existing one
- Virtual machine name: a name of your choosing
- Region: your preferred Azure region
- Size:
Standard_B2sor larger - Authentication type: SSH public key
- Username:
azureuser - SSH public key source: use an existing key or let Azure generate one
- On the Disks tab choose your preferred OS disk type
- On the Networking tab allow inbound SSH (port 22) from your own address range only
- Review and create
Option B: Azure CLI
az vm create \
--resource-group myResourceGroup \
--name my-alpine-vm \
--image <cloudimg-alpine-linux-offer-urn> \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
Open SSH to your own address range only:
az vm open-port --resource-group myResourceGroup --name my-alpine-vm --port 22
Step 2: Connect over SSH
ssh azureuser@<public-ip>
You are dropped into BusyBox ash, not bash. bash is installed and available if you prefer it:
bash --version
Confirm your privilege escalation works:
sudo id
Step 3: Confirm which release you are running
cat /etc/alpine-release
cat /etc/os-release
uname -sr
Confirm the init system really is OpenRC and that PID 1 is BusyBox:
readlink -f /sbin/init
sudo rc-status -r
List the services running in the default runlevel. On a freshly booted image this is a deliberately short list:
sudo rc-update show default
Step 4: Confirm the repositories and the patch level
Show exactly which repositories are enabled. There should be two lines, both on v3.24, and neither should mention edge or testing:
cat /etc/apk/repositories
Prove that no rolling branch is enabled:
grep -Ev '^\s*(#|$)' /etc/apk/repositories | grep -E '(edge|testing)' && echo "ROLLING BRANCH ENABLED" || echo "stable only - no edge, no testing"
Refresh the package index and confirm nothing is left to upgrade:
sudo apk update
sudo apk list --upgradable
An empty result means the image is fully patched. Count the installed packages and the footprint:
sudo apk info | wc -l
Check the licence of any individual package. Alpine records it in the package metadata:
sudo apk info --license busybox musl openssh
Step 5: Confirm the package manager reaches the repositories
Install and remove a package to prove apk has working network and repository access:
sudo apk add --no-cache jq
jq --version
sudo apk del jq
Step 6: Review the secure by default posture
Confirm the effective SSH configuration, which is what sshd actually applies rather than what a config file happens to say:
sudo sshd -T | grep -E '^(passwordauthentication|permitrootlogin|kbdinteractiveauthentication|permitemptypasswords|pubkeyauthentication) '
You should see password authentication off, root login off, keyboard interactive off, empty passwords off and public key authentication on.
Confirm your account has exactly one authorised key, the one Azure injected:
wc -l < ~/.ssh/authorized_keys
Confirm the SSH host keys are unique to this virtual machine. They were generated on this machine at its first boot, not baked into the image, so no other customer's virtual machine shares them:
for f in /etc/ssh/ssh_host_*_key.pub; do sudo ssh-keygen -lf "$f"; done
Confirm there is no swap on the operating system disk:
cat /proc/swaps
swapon --show
Both should be empty. Now confirm what is actually listening on the network:
sudo netstat -tulnp
The only listening TCP port should be 22. The two UDP entries belong to the DHCP client (dhcpcd) holding this machine's Azure lease; they are client sockets, not services.
Step 7: Automatic security updates
Alpine has no errata feed and no unattended-upgrades equivalent, so "security only updates" is not something this distribution can express. Be aware of exactly what this image does instead, because it is a deliberate design decision.
An Alpine stable branch only ever receives bugfix and security updates. Moving to a new feature release requires editing /etc/apk/repositories to point at a different vX.Y branch. Because this image pins v3.24, upgrading everything within that branch is the closest equivalent to automatic security patching, and it cannot move your machine to a different Alpine release.
The job is installed here:
cat /etc/periodic/daily/cloudimg-apk-upgrade
It is dispatched by BusyBox crond at 02:00 daily via the root crontab:
grep periodic/daily /etc/crontabs/root
The important part is that crond is actually enabled and running. On a stock Alpine install it is in no runlevel at all, which means /etc/periodic/* never fires and any job placed there is silently dead. On this image it is enabled:
sudo rc-update show default | grep crond
sudo rc-service crond status
You can run the job by hand at any time and read its log:
sudo /etc/periodic/daily/cloudimg-apk-upgrade
sudo tail -5 /var/log/cloudimg-apk-upgrade.log
If you would rather control patching yourself, disable the job and remove it:
sudo rc-update del crond default
Re-enable it again with sudo rc-update add crond default && sudo rc-service crond start.
Step 8: Restrict SSH access
The image ships with SSH hardened, but the network boundary is yours to set. Restrict port 22 to your own address range in the network security group rather than leaving it open to the internet:
az network nsg rule update \
--resource-group myResourceGroup \
--nsg-name my-alpine-vmNSG \
--name default-allow-ssh \
--source-address-prefixes <your-ip>/32
Step 9: Set the time zone and host name
Alpine uses setup-timezone and setup-hostname from alpine-conf rather than timedatectl and hostnamectl. Check the current time source first:
sudo rc-service chronyd status
date
To change the host name, edit /etc/hostname and apply it:
cat /etc/hostname
Note that cloud init sets the host name from Azure at first boot. If you want a different one to survive reboots, disable cloud init's host name management by adding preserve_hostname: true to /etc/cloud/cloud.cfg.d/.
Step 10: Serial console recovery
Because this image has no Azure Linux Agent, the serial console is your out of band recovery path and it is the one to know. It is enabled on this image — the kernel is booted with console=ttyS0:
cat /proc/cmdline
To use it, open your virtual machine in the Azure Portal and select Help → Serial console. You will get the boot messages and a login prompt.
Note that the serial console gives you a password login prompt, and this image ships with no password set for any account, which is the correct secure default. If you need serial console access as a recovery path, set a password for your admin user before you need it:
echo "set a password now if you want serial console recovery later:"
Run sudo passwd azureuser interactively when you are ready to set one. Keep in mind that doing so enables a credential that did not previously exist; SSH password authentication stays off regardless, so the password is only usable on the serial console.
Troubleshooting
Cannot connect over SSH. Confirm the network security group allows port 22 from your address, and that you are using the private key matching the public key you gave Azure at deployment. This image never accepts passwords over SSH, so an authentication prompt means your key was not offered.
systemctl: not found. Alpine uses OpenRC. Use rc-service <name> start|stop|status instead of systemctl start|stop|status, and rc-update add|del <name> default instead of systemctl enable|disable. List everything with rc-status -a.
A vendor binary fails with a linker or GLIBC_ error. Alpine uses musl libc. Either use a musl or statically linked build, or install the gcompat compatibility layer with sudo apk add gcompat.
az vm run-command or a VM extension fails. Expected. Alpine ships no Azure Linux Agent, so extensions are unavailable — see the note in the Overview. Use SSH or the serial console.
A command behaves unexpectedly compared with another distribution. Most base utilities are BusyBox applets and their flags differ from GNU. util-linux is installed, so lsblk, findmnt, swapon and dmesg are the full implementations. For GNU coreutils behaviour elsewhere, sudo apk add coreutils.
Patching appears not to be happening. Confirm crond is both enabled and running with sudo rc-update show default | grep crond and sudo rc-service crond status. A job in /etc/periodic/daily does nothing if crond is not in a runlevel.
Support
cloudimg provides 24/7 support for this image. Contact support@cloudimg.co.uk with your Azure subscription ID and virtual machine name.
Alpine Linux itself is a community project. Upstream documentation is at wiki.alpinelinux.org, and package sources are at gitlab.alpinelinux.org.