How to Prepare Your Own OS Image & Deploy it on Cloud Server
Table of contents
- Prerequisites
- Image requirements
- Installing QEMU
- Cloud-init setup
- Launching a VM with QEMU
- Case 1: Deploying from a cloud-compatible image without customization (as is)
- Case 2: Deploying from a cloud-compatible image with customization
- Case 3: Deploying from a non-cloud-compatible ISO image
- Preparing the image for upload
- Uploading the image to the customer portal
- Default users and access
- Useful articles
You can deploy a cloud server from your own custom image. This is useful when you need:
- An operating system that is not yet available among our ready-to-use images in the customer portal
- A preconfigured image with your own software and settings
For the purpose of this article, images are grouped into two categories:
- Cloud-compatible images – provided by the OS vendor for cloud environments. They typically include the required drivers, cloud-init support, and OpenStack compatibility
- Non-cloud images – generic images, such as ISO-based installers, that require additional configuration before they can run properly in an OpenStack-based cloud
This guide covers how to create, customize, and prepare a virtual machine (VM) image and then upload it to the customer portal to deploy a cloud server. It walks through three scenarios:
- Deploying a cloud server using a cloud-compatible image without modifications (as-is)
- Preparing and customizing a cloud-compatible image before uploading
- Preparing a non-cloud image (for example, from an ISO file) to make it OpenStack-compatible
This article does not cover the operating system installation process.
Prerequisites
- Customer portal account
- Added SSH key to the customer portal (required only if you plan to use key-based authentication)
- Local machine (Windows, Linux, or macOS)
- Installed virtualization software (we use QEMU in examples)
- Source OS image
Image requirements
Our cloud services are built on OpenStack. You can configure all parameters and applications as needed, and ensure your VM meets the following core OpenStack image requirements:
| Requirement | Linux | FreeBSD | OpenBSD | NetBSD |
|---|---|---|---|---|
| Architecture | x64 OS | |||
| Image format | qcow2 (recommended) |
|||
| Boot firmware | Only BIOS boot firmware. UEFI is not supported | |||
| Disk layout | Use the standard disk layout provided by your distribution's cloud images. Enable automatic root expansion on first boot with cloud-init's growpart and resizefs modules. |
Use GPT with two partitions. The first partition is The root partition must be the last one on the disk. This is required for correct auto-resize during the first boot with |
Use GPT with the root partition placed as the last one on the disk. Automatic root expansion (auto-grow) is supported via growfs in OpenBSD 7.3 and later. |
Use GPT (recommended). Automatic root expansion is not supported. Resizing requires manual steps: gpt resize + growfs. |
| No hard-coded networking | Use DHCP and remove any static IP and MAC assignments from network configuration files (such as /etc/network/interfaces, /etc/netplan/*.yaml, etc). |
Use DHCP on vtnet0 and remove any static network configuration from /etc/rc.conf (or related includes). |
Use DHCP on vio0 and remove any static network configuration from /etc/hostname.* files. |
Use DHCP on vioif0 and remove any static network configuration from /etc/ifconfig.* files. |
| Configured remote access | Ensure that the SSH server is enabled and starts on boot. | Ensure that sshd is enabled. bsd-cloudinit injects SSH keys automatically. Grant sudo access to the target user if required. |
sshd is enabled by default |
Ensure that sshd is enabled. root SSH login may be disabled by default depending on the image. |
| Hypervisor/driver compatibility |
Modern Linux distributions include all necessary virtio drivers. Only very old kernels (pre-3.0) may require adding virtio and network drivers. Legacy Xen-PV is not needed. |
Ensure that virtio disk and network drivers are enabled and that comconsole is configured. Verify that the image boots correctly under KVM/QEMU. |
Ensure that virtio disk ( Virtio is fully supported in modern OpenBSD 5.3 and later, and provides optimal performance under KVM. |
Ensure that basic virtio disk and network support virtio(4) is available in the image. |
| Image size | Upload size: ≤ 5 GB | |||
For the complete list of requirements, see the OpenStack Image Guide.
Cloud-init compatibility
Cloud-init supports a wide range of Linux and BSD distributions. For the list of supported platforms, see: https://docs.cloud-init.io/en/latest/reference/availability.html.
You can find unofficial BSD images that include cloud-init and have been tested on OpenStack at: https://bsd-cloud-image.org/.
Installing QEMU
In this guide we use QEMU, a free and open-source cross-platform virtualization environment. After installing QEMU, you can start a VM with your OS image.
QEMU is required only if you plan to customize an image (Case 2) or prepare a non–cloud-compatible ISO (Case 3).
If you want to deploy a vendor-provided cloud image as is (Case 1), you can skip this section and proceed directly to uploading the image to the customer portal.
Ubuntu/Linux
sudo apt-get install qemu-system
Fedora
sudo dnf install qemu qemu-kvm
#or
dnf install @virtualization
Red Hat/CentOS
sudo yum install qemu qemu-kvm
SUSE
zypper install qemu
Arch Linux
sudo pacman -S qemu
macOS
From Homebrew:
brew install qemu
From MacPorts:
sudo port install qemu
Windows
Open PowerShell and run the following command with administrator rights:
winget install SoftwareFreedomConservancy.QEMU
Or use a ready-made installer for Windows.
Cloud-init setup
Cloud-init is a cross-platform tool used to initialize a virtual machine during its first boot and apply configuration through metadata. Typical tasks include setting the hostname, configuring networking, adding SSH keys, creating users, and running initialization commands or scripts.
Cloud-init configuration files
To provide initialization data when preparing an image locally, this guide uses a small ISO file called seed.iso. During the first boot, cloud-init reads the contents of this ISO and applies the defined settings.
The ISO contains two files:
-
user-data– defines users, SSH keys, and other initialization settings -
meta-data– specifies basic instance parameters, such as hostname
Create two plain text files named user-data and meta-data (without file extensions) and place them in the same directory before building the ISO.
Example user-data:
#cloud-config
users:
- name: cloud-user
shell: /bin/bash
groups: sudo
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
- INSERT_YOUR_SSH_KEY_HERE
- name: admin
shell: /bin/bash
groups: sudo
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
- INSERT_YOUR_SSH_KEY_HERE
ssh_pwauth: false
hostname: fedora-vmIn the example above, ssh_pwauth is set to false to allow authentication only with SSH keys. Setting it to true can be helpful during testing or when verifying that cloud-init applied your configuration.
Before uploading your prepared image to the customer portal, make sure to disable password authentication again and rely on SSH keys only.
Example meta-data:
local-hostname: fedora-vm
Specifying local-hostname in meta-data is sufficient; instance-id is generated automatically by cloud-init.
Then pack these files into an ISO image that will later be attached to the virtual machine.
Build the seed.iso
Use one of the supported tools to generate a compatible ISO. The recommended method is cloud-localds. If not available, use mkisofs or genisoimage.
Linux / macOS
Run the command directly in the directory that contains these files.
cloud-localds seed.iso user-data meta-data
Alternatives:
mkisofs -output seed.iso -volid cidata -joliet -rock user-data meta-data
or
genisoimage -output cidata.iso -volid cidata -joliet -rock -graft-points user-data=./user-data meta-data=./meta-data
Windows
- Install the Windows Assessment and Deployment Kit (Windows ADK), selecting only the Deployment Tools component
- Execute the following command:
C:\path-to-oscdimg-exe-file -j1 -lcidata C:\path-to-cloud-init-config-files .\seed.isoThe
oscdimg.exeutility is typically located at:%ProgramFiles(x86)%\Windows Kits\<Windows version>\Assessment and Deployment Kit\Deployment Tools\<architecture>\Oscdimg\oscdimg.exeWhere:
- <Windows version> is your installed Windows Kits version
- <architecture> is
amd64orarm64, depending on your system
A seed.iso file will be created in the same directory.
Launching a VM with QEMU
Start the virtual machine with your OS image and the seed.iso attached.
Before starting the VM, note the following parameter used in the launch command: -netdev user,id=net0,hostfwd=tcp::2222-:22
This option sets up port forwarding from the host to the VM. It maps host port 2222 to port 22 inside the VM. You will use this forwarded port later when connectiong the VM after it boots.
Using -m and -smp parameters, set the VM's memory and vCPUs to match the configuration you plan to use on the cloud server.
Linux
qemu-system-x86_64 \
-m 2048 -smp 2 \
-drive file=cloud-image.qcow2,format=qcow2,if=virtio \
-drive file=seed.iso,format=raw,if=virtio \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device virtio-net,netdev=net0 \
-nographicFor subsequent boots, remove the line -drive file=seed.iso,format=raw,if=virtio
macOS
qemu-system-x86_64 \
-accel tcg,thread=multi \
-m 2048 -smp 2 \
-drive file=generic_alpine.qcow2,format=qcow2,if=virtio \
-cdrom seed.iso \
-device virtio-net-pci,netdev=n1 \
-netdev user,id=n1,hostfwd=tcp::2222-:22 \
-nographicFor subsequent boots, remove the line -cdrom seed.iso
Windows
Navigate into the QEMU installation directory (%ProgramFiles%\qemu, by default) and run the VM with the following command:
.\qemu-system-x86_64.exe ^
-m 2048 -smp 2 ^
-drive file=,format=qcow2,if=virtio ^
-cdrom ^
-device virtio-net-pci,netdev=n1 ^
-netdev user,id=n1,hostfwd=tcp::2222-:22 For subsequent boots, remove the line -cdrom <C:\path-to-seed.iso>.
Check applied configuration
After the VM starts, connect to it via SSH using the command:
ssh -p 2222 <user>@localhost
To verify cloud-init behavior, run:
cloud-init status --long
On non-cloud ISO images, this command often shows status: disabled in output, because the VM does not receive any cloud metadata during local testing. This is expected. After you upload the prepared image to the customer portal and deploy a cloud server, our provisioning system provides the proper OpenStack datasource, cloud-init runs normally, and the default cloud-user is created with the SSH key you selected during deployment.
Case 1: Deploying from a cloud-compatible image without customization (as is)
You can deploy a cloud server directly from a vendor-provided cloud-compatible image without creating a local virtual machine first. This method is useful if you need to deploy a cloud server from a vendor-provided image that is not available in our catalog and you do not require any customization. To do this:
- Download the vendor cloud image
- Upload the image to the customer portal and create a cloud server (see Uploading image to the customer portal)
- Connect via SSH as the default user preinstalled in the image (see Default users in vendor-provided cloud images)
For Alpine, this isalpine:
ssh alpine@<cloud_server_public_ip>
Some cloud-compatible images come with only a minimal default user. For example, an Alpine cloud image may include only the alpine default user with limited permissions. After you deploy a cloud server from such an image and connect over SSH as the default user, create a separate privileged account (for example, an admin user with sudo access) and use it for further work with the server.
Example: Creating a privileged user after creating a cloud server
- Switch to the root user
doas -s - Install and configure sudo
apk update apk add sudo adduser alpine wheel - Create a new administrative user
adduser cloud-user adduser cloud-user wheel - Configure SSH access for the new user
mkdir -p /home/cloud-user/.ssh echo 'ssh-rsa your_public_SSH_key' > /home/cloud-user/.ssh/authorized_keys chmod 700 /home/cloud-user/.ssh chmod 600 /home/cloud-user/.ssh/authorized_keys chown -R cloud-user:cloud-user /home/cloud-user/.ssh - Edit SSH configuration
- Open
/etc/ssh/sshd_configwith vi or install nano (recommended):
apk add nano nano /etc/ssh/sshd_config - Make sure these lines are present and uncommented:
PubkeyAuthentication yes PasswordAuthentication no -
Check if the user is unlocked
Open
/etc/shadowand ensure the line for cloud-user does not contain"!"If it does, replace it with:
cloud-user:*:::::::This allows SSH login while keeping password authentication disabled.
- Open
- Restart the SSH service
rc-service sshd restart - From your local machine, connect to the server as the new user:
ssh admin@<server_ip>
The server now supports login using the admin account with key-based SSH authentication and administrative privileges via sudo.
Case 2: Deploying from a cloud-compatible image with customization
This section explains how to customize a vendor-provided cloud-compatible image before uploading it to the customer portal.
Since these images already include cloud-init, you only apply your changes and save the updated image. These images already include cloud-init, so you can configure the VM, install software, adjust system settings, create users, and then save the updated image for deployment.
- Download the vendor's cloud image
- Create cloud-init configuration (
user-dataandmeta-data) and generate the seed.iso- See Cloud-init setup
- Start the VM with QEMU
- Launch the VM using the cloud image and attach the seed.iso so cloud-init applies your configuration on the first boot
- See Launching a VM with QEMU
- Inside the VM, install and configure the guest OS as you need
- Verify the VM meets the OpenStack Image requirements
- Shut down the VM and optimize the image
- Upload the image to the customer portal and deploy a cloud server
Case 3: Deploying from a non-cloud-compatible ISO image
This section covers preparing a VM created from an ISO image, that does not include cloud-init or any cloud-specific configuration. The goal is simply to make the image boot correctly in OpenStack and accept initialization meta-data during the first launch.
Create a local virtual disk
When installing an OS from an ISO, you must first create a virtual disk, because the ISO is not a disk image. This creates the disk file.
Note: it does not mount it anywhere (QEMU will mount it when you run -drive file=…). You can choose any <disk-size> that fits your OS installation requirements (for example, 4G).
Linux, macOS etc
qemu-img create -f qcow2 path-to-cloud-image.qcow2 <disk-size>
Windows
Navigate into the QEMU installation directory (%ProgramFiles%\qemu, by default), and run the following command:
.\qemu-img.exe create -f qcow2 C:\path-to-cloud-image.qcow2 <disk-size>
Start QEMU with the OS installation ISO
The first boot must use the installation ISO (for example, ubuntu-25.10-live-server-amd64.iso). The seed.iso is not used at this stage, because cloud-init is not yet installed.
Attach the ISO as a virtual CD-ROM device. See Launching a VM with QEMU.
...
-cdrom ubuntu-25.10-live-server-amd64.iso \
-boot order=d \
...
This lets the VM boot from the OS installer instead of booting from the empty virtual disk.
Install and configure the guest OS as you need
Cloud-init does not require a "clean" system. Your ISO-based image may already contain:
- Installed packages (for example, MySQL, nginx)
- System users created by those packages
- Pre-configured settings and services that start on boot
Restart the VM without the installation ISO and the boot order=d parameter
boot order=d tells QEMU to boot from the CD-ROM. After the OS is installed, the VM must boot from its virtual disk instead.
Make sure the SSH server is installed and running
sudo apt install openssh-server
sudo systemctl enable ssh
sudo systemctl start ssh
Install cloud-init and disk auto-expansion utilities
cloud-init is a common init that will:
- Fetch metadata and user data (SSH keys, hostname, and so on)
- Create the desired user (for example, cloud-user or any user you use as the default)
- Run the growpart and resizefs modules to expand the disk
Utilities such as cloud-utils or cloud-utils-growpart provide growpart for automatically expanding the root partition when the flavor disk is larger than the original image.
RPM-based
sudo dnf install -y cloud-init
sudo systemctl enable cloud-init
Debian-based
sudo apt update
sudo apt install -y cloud-init
sudo systemctl enable cloud-init
Configure cloud-init datasources
- Create a configuration file in the VM:
/etc/cloud/cloud.cfg.d/99-openstack-datasource.cfg/etc/cloud/cloud.cfg.d/directory is the standard place for user overrides. The99-prefix in the file name makes sure this configuration is loaded last. Files are processed in alphabetical order, so99-openstack-datasource.cfgoverrides earlier datasource_list settings. - Add, for example:
This tells cloud-init to look for instance metadata and user data using standard OpenStack-compatible datasources, including NoCloud, which you will emulate via iso.datasource_list: [NoCloud, Ec2, ConfigDrive]
Apply cloud-init metadata and Start QEMU
To provide initialization data for your image, attach the previously created seed.iso (see Cloud-init setup). When the ISO is attached to the VM, cloud-init reads it as a NoCloud datasource and applies the configuration from user-data and meta-data during the first boot. After initialization completes, seed.iso is no longer required and can be removed for all subsequent VM launches.
Verify the disk layout and auto-expansion
OpenStack expects the image to expand the root partition and filesystem on first boot if the flavor disk size is larger than the original image.
Do the following:
- Make sure the disk layout is simple (a single root partition, as created by the default Ubuntu installer)
- Check that
growpartandresizefsare present in the module lists in/etc/cloud/cloud.cfg
grep -A3 -i growpart /etc/cloud/cloud.cfg
grep -A3 -i resizefs /etc/cloud/cloud.cfg
Remove hard-coded network settings
For modern Ubuntu (netplan):
- Check
/etc/netplan/*.yamland make suredhcp4: trueis used, - There must be no static IP, MAC address, or interface names tied to a specific MAC
If the system is configured for DHCP by default, you usually do not need to change anything.
If the file 70-persistent-net.rules exists, it is recommended to remove it to avoid persisting old MAC addresses:
cat /etc/udev/rules.d/70-persistent-net.rules
rm /etc/udev/rules.d/70-persistent-net.rules
Do a final cleanup
Remove VM-specific state and temporary data so that each new instance starts "clean":
- Clear cloud-init state (
/var/lib/cloud) - Optionally clear cloud-init logs and
systemdjournals - Make sure that:
- There are no permanently baked-in SSH host keys, if you want them to be generated on first boot
- There are no user
~/.ssh/authorized_keysfiles (keys will be injected through cloud-init) - The firewall is disabled
Preparing the image for upload
After you verify that the virtual machine (VM) meets the image requirements, finalize the disk so it is ready for upload to the customer portal. Some preparation steps differ depending on the operating system.
Remove cloud-init state so that the new cloud server boots as a fresh instance:
Linux and FreeBSD/BSD
sudo cloud-init clean --logs
sudo rm -rf /var/lib/cloud/*
For Ubuntu/Debian, also confirm that /etc/cloud/cloud.cfg.d/*.cfg does not contain test-specific datasource overrides.
Delete VM-specific artifacts
Linux
- Delete SSH host keys:
sudo rm -f /etc/ssh/ssh_host_* - Remove temporary authorized_keys added during local testing:
sudo rm -f /home/*/.ssh/authorized_keys sudo rm -f /root/.ssh/authorized_keys - Remove persistent network rules, if present:
sudo rm -f /etc/udev/rules.d/70-persistent-net.rules
FreeBSD / BSD
Remove host keys and cloud-init or bsd-cloudinit state:
sudo rm /etc/ssh/ssh_host_*
sudo rm -rf /var/db/cloud/*
Shut down the VM cleanly, to ensure that file system and all services write their final state
Linux
sudo shutdown -h now
FreeBSD / BSD
sudo shutdown -p now
The resulting QCOW2 disk is your OpenStack-compatible image.
(Optional) Optimize and compress the image
Use this command to optimize the image:
qemu-img convert -O qcow2 -c original.qcow2 optimized.qcow2
Check the resulting size:
qemu-img info optimized.qcow2
Now the image file is ready for upload to the customer portal.
Uploading the image to the customer portal
We recommend disabling any VPN when uploading an image. If the uploading fails, try to restart the process. If the issue persists, contact our support team for assistance.
- Upload your image to the customer portal (see How to upload your own image), selecting the the same location where you plan to deploy the cloud server
- Create a cloud server (see the Create a Cloud Server)
- In the Image section, select Other and choose your uploaded custom image. Use SSH authentication with the key that matches the public key in your
user-data.
- In the Image section, select Other and choose your uploaded custom image. Use SSH authentication with the key that matches the public key in your
Wait for cloud server deployment and get a public IP address to connect to the server in the server's details page.
Default users and access
Access to the deployed server depends on the operating system in the image. See the How to access the cloud server.
SSH access with root is disabled in most vendor images by default.
When a cloud server is provisioned from a custom image, the cloud-user is created and injects the SSH key you selected during server creation into its ~/.ssh/authorized_keys.
Default users in vendor-provided cloud images
Usernames can vary slightly between distributions or custom vendor builds – check the image description page for confirmation.
Use this table to find the default login user for common vendor cloud images.
| Ubuntu | ubuntu |
| Debian | debian |
| Fedora | fedora |
| AlmaLinux | almalinux |
| Alpine Linux | alpine |
| RHEL | cloud-user |
| openSUSE | opensuse |
| FreeBSD | freebsd |
| OpenBSD | openbsd |
| NetBSD | netbsd |