[ English | русский | Indonesia ]

Building a Windows Server Image for OpenStack

This guide describes, end to end, how to build a reusable Windows Server image for an OpenStack cloud. The general approach is as follows:

  1. Upload the official Windows installation media and the VirtIO driver ISO to the Image service (Glance).

  2. Boot a temporary instance from a blank volume, attaching both ISOs as virtual CD-ROM devices.

  3. Install Windows Server, the VirtIO paravirtualized drivers, the QEMU guest agent, the balloon (dynamic memory) service, and Cloudbase-Init.

  4. Generalize the installation with Sysprep and shut the instance down.

  5. Detach the resulting volume and upload it to the Image service as a new, reusable Glance image.

Once published, this image can be used as the image source for any future instance, and Cloudbase-Init will take care of per-instance customization (administrator password, hostname, and network configuration) on first boot.

Prasyarat

  • A Linux client machine (Debian, Ubuntu, RHEL, CentOS or AlmaLinux) — or Windows Subsystem for Linux (WSL), see this example guide used only to run the openstack command-line client; it does not need to be powerful, only network-reachable to the OpenStack API endpoints.

  • Permission to manage security groups and floating IPs in the target project.

  • A local machine with an RDP client to connect to the instance once it is reachable over the network.

Obtaining OpenStack Credentials

Before the openstack command-line client can be used, it needs credentials for the target project. The Horizon dashboard can generate these automatically.

Log in to Horizon, open the account menu in the top-right corner (or navigate to Project -> API Access in some Horizon themes), and download either the OpenStack RC File or the OpenStack clouds.yaml File. Either one is sufficient; this guide uses the RC file.

Horizon menu with links to download the OpenStack RC file and clouds.yaml file

Downloading the project credentials from the Horizon dashboard.

Copy the downloaded file to the Linux client and source it. You will be prompted for the account password if it was not embedded in the file:

# Example content of the downloaded RC file (values are placeholders)
#!/usr/bin/env bash
export OS_AUTH_URL="https://<keystone-endpoint>:5000/v3"
export OS_PROJECT_ID="<project_id>"
export OS_PROJECT_NAME="<project_name>"
export OS_USER_DOMAIN_NAME="Default"
export OS_PROJECT_DOMAIN_ID="default"
export OS_USERNAME="<username>"
export OS_REGION_NAME="<region_name>"
export OS_INTERFACE=public
export OS_IDENTITY_API_VERSION=3
echo "Please enter your OpenStack Password for project $OS_PROJECT_NAME as user $OS_USERNAME: "
read -sr OS_PASSWORD_INPUT
export OS_PASSWORD=$OS_PASSWORD_INPUT
source admin-openrc.sh

Installing the OpenStack Client

Install python-openstackclient on the Linux client used to run the openstack commands in this guide. The package is available from most distributions' repositories, or from PyPI with pip. Installing into a Python virtual environment is recommended, so that the client and its dependencies stay isolated from any system-managed Python packages:

python3 -m venv ~/openstack-venv
source ~/openstack-venv/bin/activate
pip install python-openstackclient

Verify that the client can authenticate against the cloud:

openstack image list

Preparing the Installation Media

Two ISO images are required to build the instance:

  1. The official Windows Server installation media. This guide uses Windows Server 2025, available from the Microsoft Evaluation Center.

  2. The VirtIO driver ISO, which provides paravirtualized disk, network, and memory-balloon drivers for KVM/QEMU-based hypervisors. This guide uses version 0.1.302.

Download both ISO files to the Linux client before continuing.

Uploading the Images to the Image Service

With both ISOs on the client, upload them to Glance as private images so they can be attached to the build instance as virtual CD-ROM devices.

Upload the Windows installer ISO:

openstack image create \
  --disk-format iso \
  --container-format bare \
  --file <your_windows_installer.iso> \
  --private \
  "Win 2025 Installer"

Upload the VirtIO driver ISO:

openstack image create \
  --disk-format iso \
  --container-format bare \
  --file virtio-win.iso \
  --private \
  "virtio-win"

Record the resulting image IDs (openstack image list will show them); they are needed in the next step.

Booting the Installation Instance

Boot a temporary instance from a blank volume, with the Windows installer and the VirtIO driver ISO attached as CD-ROM devices. The blank volume becomes the future system disk and uses the VirtIO SCSI/block bus (disk_bus=virtio) so that it will require the VirtIO storage driver during setup; the ISOs are attached on the IDE bus, which Windows Setup can read natively without any extra drivers.

openstack server create \
  --flavor <your_flavor_id> \
  --block-device source_type=blank,destination_type=volume,volume_size=50,disk_bus=virtio,boot_index=1 \
  --block-device source_type=image,uuid=<windows_installer_image_id>,destination_type=volume,volume_size=10,disk_bus=ide,device_type=cdrom,boot_index=0 \
  --block-device source_type=image,uuid=<virtio_win_image_id>,destination_type=volume,volume_size=2,disk_bus=ide,device_type=cdrom,boot_index=2 \
  --network <your_network_id> \
  Win-Install

Because boot_index=0 is assigned to the Windows installer ISO, the instance boots directly into Windows Setup. Wait for the instance to reach the ACTIVE state, then open its console from Horizon (Compute -> Instances -> Win-Install -> Console).

Installing Windows Server

The console should show the first screen of Windows Setup.

Windows Server Setup, Select language settings screen

The initial Windows Setup screen, reached automatically because the installer ISO was assigned boot_index=0.

Proceed through the wizard: accept the default language and keyboard settings, click Install now, select the desired Windows Server edition, and accept the license terms. When Setup reaches the disk selection screen, the list will be empty, because the system volume was attached over the VirtIO bus and Windows does not ship a driver for it by default.

Select location to install Windows Server, with an empty disk list

No disks are visible yet: the VirtIO block driver has not been loaded.

Click Load driver, then Browse, and navigate on the VirtIO driver CD-ROM (typically drive E:) to the viostor directory that matches the target Windows version and architecture, for example E:\viostor\2k25\amd64.

Browse for Folder dialog, navigating to the viostor driver folder

Locating the viostor (VirtIO SCSI/block) driver on the VirtIO ISO.

Confirm the folder, select the listed VirtIO SCSI controller driver, and click Next to install it. The blank system volume then becomes visible as unallocated space.

Select location to install Windows Server, showing the 50 GB unallocated disk

The 50 GB system volume is now visible after the VirtIO driver has been loaded.

Select the disk, create a partition (or accept Setup's default layout), and click Next to begin installation. Windows Setup copies files and reboots the instance several times as part of the installation process; this can take several minutes. When installation finishes, set the new password for the local Administrator account and complete the first sign-in.

Catatan

Only the storage driver is required at this stage. The remaining VirtIO drivers (network, balloon, serial, and so on) are installed after the first successful logon, in the next section.

Installing the VirtIO Drivers

After signing in, open an Windows Command Prompt and install the remaining VirtIO drivers from the driver ISO using pnputil. Adjust the version subdirectory (2k25 in the examples below) to match the Windows Server version being installed.

Command prompt installing the VirtIO balloon driver with pnputil

Installing a VirtIO driver package with pnputil -i -a.

pnputil -i -a E:\Balloon\2k25\amd64\balloon.inf
pnputil -i -a E:\NetKVM\2k25\amd64\netkvm.inf
pnputil -i -a E:\pvpanic\2k25\amd64\pvpanic.inf
pnputil -i -a E:\qemupciserial\2k25\amd64\qemupciserial.inf
pnputil -i -a E:\vioinput\2k25\amd64\vioinput.inf
pnputil -i -a E:\viorng\2k25\amd64\viorng.inf
pnputil -i -a E:\vioscsi\2k25\amd64\vioscsi.inf
pnputil -i -a E:\vioserial\2k25\amd64\vioserial.inf

Each invocation should report Successfully installed the driver and Driver package added successfully.

Installing the QEMU Guest Agent

The VirtIO ISO also ships an MSI installer for the QEMU guest agent, which allows the hypervisor to query guest status and issue commands such as filesystem freeze/thaw for consistent snapshots. Run it from the same elevated Command Prompt:

E:\guest-agent\qemu-ga-x86_64.msi
QEMU guest agent installer running from the Command Prompt

The QEMU guest agent installing silently after the VirtIO serial driver has been loaded.

The installer runs with no further prompts and returns to the command line once complete.

Installing the Balloon (Dynamic Memory) Service

The balloon driver installed earlier exposes a virtual device, but the companion balloon service (blnsvr) must also be installed so that the guest can respond to memory ballooning requests from the hypervisor. Copy the Balloon directory from the VirtIO ISO to a local path, for example C:\Balloon, and run the service installer from there.

Copying the Balloon directory from the VirtIO ISO in File Explorer

Copying the Balloon directory from the mounted VirtIO ISO to local storage.

C:\Balloon\2k25\amd64\blnsvr.exe -i
Command prompt showing the balloon service installed and running

The balloon service reports Service Installed and Service RUNNING.

The service can be confirmed in Task Manager, listed as blnsvr with a child process named Balloon Service.

Task Manager showing the running Balloon Service process

The balloon service visible under Background processes in Task Manager.

Configuring Network Access

Reboot the instance so that all newly installed drivers, particularly netkvm, are fully applied. After the reboot, the network adapter should be recognized and, depending on the DHCP configuration of the subnet, an IP address should be assigned automatically.

To reach the instance remotely, enable Remote Desktop from Settings -> System -> Remote Desktop, toggle it on, and confirm the prompt.

Windows Settings, enabling Remote Desktop with a confirmation dialog

Enabling Remote Desktop from Windows Settings.

Next, permit RDP traffic at the OpenStack network layer. In Horizon, open the relevant security group and add a rule using the predefined RDP template, restricting the CIDR field to the smallest range that meets your access requirements rather than leaving it fully open.

Horizon Add Rule dialog configuring an RDP security group rule

Adding an RDP rule to the instance's security group.

Finally, associate a floating IP with the instance so that it is reachable from outside the OpenStack project network, via Compute -> Instances -> Associate Floating IP.

Horizon dialog managing floating IP associations for the instance

Associating a floating IP address with the installation instance.

Peringatan

Restrict the RDP security group rule to trusted source addresses. Exposing RDP to 0.0.0.0/0 is a common attack vector and should be avoided outside of build instances.

With the security group rule and floating IP in place, connect to the instance using any RDP client and the Administrator credentials set earlier.

Installing Cloudbase-Init

Cloudbase-Init is the Windows equivalent of cloud-init: it reads instance metadata from the OpenStack metadata service on first boot and applies settings such as the administrator password, hostname, etc. Installing it is what makes the resulting image reusable for many future instances, rather than a one-off Windows installation.

From within the RDP session, download the installer.

Run the installer. On the configuration screen, set the username to Administrator, enable Use metadata password so that the password supplied by OpenStack at instance creation is applied on first boot, and confirm that the user is a member of the Administrators local group.

Cloudbase-Init Setup, configuration options screen

Cloudbase-Init configuration: username, metadata password, and local group membership.

Complete the remaining installer screens with their default values.

Generalizing the Image with Sysprep

On the final screen of the Cloudbase-Init installer, select Run Sysprep to create a generalized image and Shutdown when Sysprep terminates, then click Finish.

Cloudbase-Init Setup completion screen with Sysprep options selected

Running Sysprep and shutting the instance down on completion, as the final step of the Cloudbase-Init installer.

Sysprep removes machine-specific state (security identifiers, computer name, installed-driver associations, and so on) so that the image can be safely duplicated into many independent instances. Once Sysprep completes, the instance powers itself off; wait for Horizon to report the instance as SHUTOFF before continuing.

Catatan

Do not start the instance again after Sysprep has run and it has shut down. Booting a generalized instance would trigger a new, instance-specific specialization pass and effectively rebind the image to that one instance, defeating the purpose of the capture.

Publishing the Final Image

With the instance powered off, detach the system volume and capture it as a new Glance image so it can be reused as a boot source.

In Horizon, go to Project -> Volumes, locate the volume attached to the build instance, and choose Manage Attachments from the volume's action menu.

Volume actions dropdown showing Manage Attachments and Upload to Image

The per-volume action menu in the Volumes list.

Detach the volume from the (now shut-off) instance. Once detached, open the action menu again and select Upload to Image. Provide an image name and choose the disk format appropriate for your storage backend (for example, QCOW2 for file-based backends or RAW for Ceph-backed storage).

Upload Volume to Image dialog with image name and disk format fields

Uploading the captured volume to the Image service as a new image.

Click Upload and wait for the operation to complete; Glance reports the image status as queued, then saving, and finally active. This can take several minutes depending on volume size and storage backend performance.

The image can now be referenced directly by UUID or name in openstack server create --image ... for any subsequent Windows Server deployment, without repeating the manual installation steps described in this guide.

For further tuning of Windows guests, see Windows optimization tips.