This repository includes a GitHub Actions workflow and a reusable composite action for building AlmaLinux OS cloud and Vagrant images using Packer. This is the primary build pipeline that produces images for all supported cloud platforms and hypervisors.
Main workflow that orchestrates image builds across multiple platforms, architectures, and variants.
What it does:
- Builds cloud images (Azure, GCP, GenCloud, GenCloud ext4, OCI, OpenNebula, Hyper-V) and Vagrant boxes (libvirt, VirtualBox, VMware)
- Supports AlmaLinux 8, 9, 10, and Kitten 10
- Builds for both x86_64 and aarch64 architectures, plus x86_64_v2 and aarch64 64K page variants
- Splits work between GitHub-hosted runners (x86_64) and self-hosted runners (aarch64, VMware)
- Generates SHA-256 checksums for all images
- Optionally stores images as GitHub Actions artifacts
- Optionally uploads images to AWS S3 with public tagging
- Optionally runs image tests
- Sends Mattermost notifications
Usage:
Trigger via GitHub UI: Actions → Build Cloud and Vagrant Images
Inputs:
- date_time_stamp: Custom timestamp YYYYMMDDhhmmss (auto-generated if empty)
- version_major: AlmaLinux version (choice: 10-kitten, 10, 9, 8)
- image_type: Cloud image type (NONE, ALL, azure, gcp, gencloud, hyperv, oci, opennebula)
- vagrant_type: Vagrant image type (NONE, ALL, vagrant_libvirt, vagrant_virtualbox, vagrant_vmware)
- self-hosted: Allow self-hosted runners (default: true)
- self_hosted_runner: Self-hosted runner type (self-hosted, aws-ec2)
- run_test: Test built images - vagrant only (default: true)
- store_as_artifact: Store images as GitHub artifacts (default: false)
- upload_to_s3: Upload to S3 bucket (default: true)
- notify_mattermost: Send notifications (default: false)
Reusable composite action containing the actual build, test, upload, and notification logic.
What it does:
- Detects the runner OS (Ubuntu or RHEL) and installs appropriate packages
- Installs KVM, Packer, and Ansible
- Configures virtualization (KVM, VirtualBox, or VMware depending on image type)
- Runs Packer to build the image
- Locates the output image and generates SHA-256 checksum
- Tests the image (mounts qcow2/raw for cloud images, or runs
vagrant upfor Vagrant boxes) - Generates SBOM (Software Bill of Materials) for GCP images
- Uploads images and artifacts to S3 and/or GCP storage
- Generates job summaries and Mattermost notifications
| Secret | Description |
|---|---|
AWS_ACCESS_KEY_ID |
AWS access key for S3 uploads |
AWS_SECRET_ACCESS_KEY |
AWS secret key for S3 uploads |
GIT_HUB_TOKEN |
GitHub PAT (used for Packer plugins and self-hosted runner provisioning) |
MATTERMOST_WEBHOOK_URL |
Mattermost incoming webhook URL |
EC2_AMI_ID_AL9_X86_64 |
AMI ID for x86_64 self-hosted EC2 runner |
EC2_AMI_ID_AL9_AARCH64 |
AMI ID for aarch64 self-hosted EC2 runner |
EC2_SUBNET_ID |
EC2 subnet for self-hosted runners |
EC2_SECURITY_GROUP_ID |
EC2 security group for self-hosted runners |
| Variable | Description |
|---|---|
AWS_REGION |
AWS region for S3 and EC2 |
AWS_S3_BUCKET |
S3 bucket name for storing built images |
MATTERMOST_CHANNEL |
Mattermost channel for notifications |
EC2_AMI_ID_AL9_X86_64 |
AMI ID for RunsOn x86_64 runners (AlmaLinux org only) |
The workflow requires:
id-token: write— for GCP Workload Identity Federation and Azure OIDCcontents: read— for repository checkout
| Type | Output Format | Architectures | Notes |
|---|---|---|---|
azure |
.raw |
x86_64, aarch64, aarch64-64k | Azure VHD source; 64K page variant for AL9+ |
gcp |
.tar.gz |
x86_64, aarch64 | Google Cloud; includes SBOM generation |
gencloud |
.qcow2 |
x86_64, aarch64 | Generic cloud (OpenStack, etc.); XFS filesystem |
gencloud_ext4 |
.qcow2 |
x86_64, aarch64 | Generic cloud with ext4 filesystem |
hyperv |
.box |
x86_64 only | Hyper-V Vagrant box; built with QEMU |
oci |
.qcow2 |
x86_64, aarch64 | Oracle Cloud Infrastructure |
opennebula |
.qcow2 |
x86_64, aarch64 | OpenNebula platform |
| Type | Provider | Architectures | Runner |
|---|---|---|---|
vagrant_libvirt |
libvirt (KVM/QEMU) | x86_64 | GitHub-hosted |
vagrant_virtualbox |
VirtualBox | x86_64 | GitHub-hosted |
vagrant_vmware |
VMware Desktop | x86_64 | Self-hosted (networking issues on GH runners) |
| Input | Variants Built | Notes |
|---|---|---|
8 |
8 |
Single variant; gencloud also builds gencloud_ext4 |
9 |
9 |
Single variant; Azure also builds 9-64k on aarch64; gencloud also builds gencloud_ext4 |
10 |
10, 10-v2 |
v2 = x86_64_v2 microarchitecture; Azure also builds 10-64k; gencloud also builds gencloud_ext4 |
10-kitten |
10-kitten, 10-kitten-v2 |
Kitten builds; Azure also builds 10-kitten-64k; gencloud also builds gencloud_ext4 |
The -v2 suffix produces images with x86_64_v2 microarchitecture level support. The -64k suffix produces aarch64 images with 64K page size (Azure only, AL9+). The gencloud_ext4 variant produces GenericCloud images with ext4 filesystem instead of the default XFS.
The workflow splits builds into two runner groups:
Runs on GitHub-hosted or RunsOn metal instances (when in the AlmaLinux org).
Builds: all x86_64 cloud images, Vagrant libvirt, Vagrant VirtualBox
Runs on self-hosted EC2 instances (via ec2-action-builder) or RunsOn ARM instances.
Builds: all aarch64 cloud images, Vagrant VMware (x86_64)
Certain variant/type combinations are excluded:
- v2 variants: excluded from Azure, OCI, GCP, DigitalOcean (cloud images don't use v2)
- 64k variants: excluded from OCI, GenCloud, GenCloud ext4, OpenNebula (only Azure supports 64K page)
- gencloud_ext4: excluded from 64k variants (ext4 variant does not produce 64K page images)
- OCI: excluded for Kitten (aarch64)
graph TD
A[Trigger Workflow] --> B[Initialize: Generate Timestamp, Build Matrix]
B --> C{Has GH-hosted builds?}
B --> D{Has Self-hosted builds?}
C -->|Yes| E[build-gh-hosted jobs]
D -->|Yes| F[Start self-hosted runners]
F --> G[build-self-hosted jobs]
E --> H[Shared Steps]
G --> H
H --> I[Install KVM/VBox/VMware + Packer + Ansible]
I --> J[Run Packer Build]
J --> K[Locate Image + Generate Checksum]
K --> L{Image Type?}
L -->|Cloud| M[Mount & Test Image via NBD]
L -->|Vagrant| N[vagrant up & Test via SSH]
L -->|GCP| O[Generate SBOM + Upload to GCS]
M --> P[Extract Package List]
N --> P
P --> Q{Upload to S3?}
Q -->|Yes| R[Upload Image + Checksum + Pkg List to S3]
Q -->|No| S{Store as Artifact?}
S -->|Yes| T[Upload to GitHub Artifacts]
R --> U[Generate Summary + Notify]
T --> U
Built images are uploaded to S3 with the following path structure:
s3://{bucket}/images/{version_major}/{release}/{type}/{timestamp}/
Examples:
s3://almalinux-cloud/images/9/9.6/azure/20260220143000/AlmaLinux-9-Azure-9.6-20260220.x86_64.raw
s3://almalinux-cloud/images/9/9.6/vagrant/20260220143000/AlmaLinux-9-Vagrant-libvirt-9.6-20260220.0.x86_64.box
s3://almalinux-cloud/images/kitten/10/azure/20260220143000/AlmaLinux-Kitten-Azure-10-20260220.0.x86_64.raw
All uploaded objects are tagged with public=yes.
For cloud images (not GCP, not Vagrant), the shared action:
- Loads the
nbdkernel module - Attaches the image using
qemu-nbd(read-only) - Mounts the root partition (partition 4 for x86_64, partition 3 for aarch64)
- Verifies
/etc/almalinux-releasematches the expected release string - Verifies the architecture of the
almalinux-releasepackage - Extracts the list of installed RPM packages
For Vagrant boxes (when run_test is enabled):
- Installs Vagrant and the appropriate provider plugin
- Adds the built box locally
- Creates a Vagrantfile with 2 CPUs and 2 GB RAM
- Runs
vagrant upwith the correct provider - Verifies
/etc/almalinux-releasevia SSH - Verifies architecture via SSH
- Runs
dnf check-updateto confirm repository access - Extracts installed package list via SSH
- Cleans up the VM and box
For GCP images, additional steps are performed:
- Generates SBOM (Software Bill of Materials) using
cloud-images-sbom-tools - Uploads the image tarball to a GCS bucket
- Uploads the SBOM to a separate GCS bucket
- Builds the
gce_image_publishtool fromGoogleCloudPlatform/compute-image-tools - Creates a test image in the GCP project
The shared action constructs Packer source names and options dynamically based on the image type and variant. The general pattern is:
{builder}.almalinux-{version}-{type}-{arch} (AL 8/9)
{builder}.almalinux_{version}_{type}_{arch} (AL 10/Kitten)
Where {builder} is qemu, virtualbox-iso, or vmware-iso.
| Runner OS | QEMU Binary | OVMF Firmware |
|---|---|---|
| Ubuntu | /usr/bin/qemu-system-{arch} |
/usr/share/OVMF/OVMF_CODE_4M.fd |
| RHEL | /usr/libexec/qemu-kvm |
(default) |
-
Packer build fails
- Check the Packer template exists for the variant/type/arch combination
- Ensure the runner has enough disk space and RAM
- For QEMU builds, verify KVM is available (
/dev/kvmexists and is accessible)
-
KVM permissions error
- The workflow configures udev rules for KVM access
- On self-hosted runners, ensure the runner user is in the
kvmgroup
-
VirtualBox or VMware build fails
- VirtualBox and VMware require KVM to be unloaded (they provide their own hypervisor)
- VMware requires a specific bundle version and license configuration
-
Image test fails (cloud)
- Ensure
nbdkernel module is available - Check that the root partition number is correct for the architecture
- Ensure
-
Vagrant test fails
vagrant upmay fail with SSH connection issues (known issue with libvirt)- VirtualBox tests may not work on standard GitHub runners (use self-hosted)
- VMware requires the
vagrant-vmware-utilityservice running
-
S3 upload fails
- Verify AWS credentials have S3 write permissions
- Check the bucket name and region are correct
-
GCP-related steps fail
- Verify Workload Identity Federation is configured for the repository
- Check the GCS bucket permissions
-
Self-hosted runner doesn't start
- Verify EC2 AMI IDs, subnet, and security group secrets
- Check AWS credentials have EC2 launch permissions
- The
ec2-action-builderhas a 30-minute TTL by default
- Packer Documentation: https://developer.hashicorp.com/packer/docs
- AlmaLinux Cloud SIG Chat: https://chat.almalinux.org/almalinux/channels/sigcloud
- Workflow run logs: GitHub Actions tab in the repository