How to prepare your own OS image and deploy it on a cloud server
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 | x64 OS | x64 OS | x64 OS |
| Image format | qcow2 (recommended) | qcow2 (recommended) | qcow2 (recommended) | qcow2 (recommended) |
| Boot firmware | Only BIOS (UEFI not supported) | Only BIOS (UEFI not supported) | Only BIOS (UEFI not supported) | Only BIOS (UEFI not supported) |
| Disk layout | Use the standard disk layout from 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. First: freebsd-boot (≈64 KB, no mount point). Second: freebsd-ufs mounted as /. Root partition must be last on disk. Required for auto-resize with bsd-cloudinit. | Use GPT with the root partition placed last on disk. Auto-grow supported via growfs in OpenBSD 7.3+. | Use GPT (recommended). Automatic root expansion is not supported; resizing requires manual steps: gpt resize + growfs. |
| No hard-coded networking | Use DHCP; remove any static IP and MAC assignments from network config files (e.g., /etc/network/interfaces, /etc/netplan/*.yaml). | Use DHCP on vtnet0; remove static network config from /etc/rc.conf. | Use DHCP on vio0; remove static config from /etc/hostname.* files. | Use DHCP on vioif0; remove static config from /etc/ifconfig.* files. |
| Configured remote access | Ensure SSH server is enabled and starts on boot. | Ensure sshd is enabled. bsd-cloudinit injects SSH keys automatically. | sshd is enabled by default. | Ensure sshd is enabled. Root SSH login may be disabled by default. |
| Hypervisor/driver compatibility | Modern Linux distributions include all necessary virtio drivers. Very old kernels (pre-3.0) may require adding virtio and network drivers. Legacy Xen-PV is not needed. | Ensure virtio disk and network drivers are enabled and comconsole is configured. Verify the image boots correctly under KVM/QEMU. | Ensure virtio disk (vioblk) and network (vio) drivers are supported. Virtio is fully supported in OpenBSD 5.3+. | Ensure basic virtio disk and network support virtio(4) is available. |
| Image size | Upload size: ≤ 5 GB | Upload size: ≤ 5 GB | Upload size: ≤ 5 GB | 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.
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.
Ubuntu/Linux
sudo apt-get install qemu-systemFedora
sudo dnf install qemu qemu-kvm
# or
dnf install @virtualizationRed Hat/CentOS
sudo yum install qemu qemu-kvmSUSE
zypper install qemuArch Linux
sudo pacman -S qemumacOS (Homebrew)
brew install qemumacOS (MacPorts)
sudo port install qemuWindows (PowerShell, run as administrator)
winget install SoftwareFreedomConservancy.QEMUOr 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 settingsmeta-data— specifies basic instance parameters, such as hostname Create two plain text files nameduser-dataandmeta-data(without file extensions) 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. 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-vmSpecifying local-hostname in meta-data is sufficient; instance-id is generated automatically by cloud-init.
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 in the directory containing the files)
cloud-localds seed.iso user-data meta-dataAlternatives:
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-dataWindows
-
Install the Windows Assessment and Deployment Kit (Windows ADK), selecting only the Deployment Tools component.
-
Run the following command:
C:\path-to-oscdimg-exe-file -j1 -lcidata C:\path-to-cloud-init-config-files .\seed.isooscdimg.exeis typically located at:%ProgramFiles(x86)%\Windows Kits\<Windows version>\Assessment and Deployment Kit\Deployment Tools\<architecture>\Oscdimg\oscdimg.exe
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 seed.iso attached.
Note the parameter -netdev user,id=net0,hostfwd=tcp::2222-:22 — it maps host port 2222 to port 22 inside the VM. You will use this forwarded port to connect to the VM after it boots.
Use -m and -smp to set 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 (run from %ProgramFiles%\qemu)
.\qemu-system-x86_64.exe ^
-m 2048 -smp 2 ^
-drive file=<C:\path-to-cloud-image.qcow2>,format=qcow2,if=virtio ^
-cdrom <C:\path-to-seed.iso> ^
-device virtio-net-pci,netdev=n1 ^
-netdev user,id=n1,hostfwd=tcp::2222-:22For subsequent boots, remove the line -cdrom <C:\path-to-seed.iso>.
Check applied configuration
After the VM starts, connect to it via SSH:
ssh -p 2222 <user>@localhostTo verify cloud-init behavior, run:
cloud-init status --longOn non-cloud ISO images, this command often shows status: disabled — this is expected during local testing. After uploading the image and deploying a cloud server, our provisioning system provides the proper OpenStack datasource, and cloud-init runs normally.
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 VM first. To do this:
-
Download the vendor cloud image.
-
Upload the image to the Customer Portal and create a cloud server (see Uploading the 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 is
alpine:ssh alpine@<cloud_server_public_ip>
Some cloud-compatible images come with only a minimal default user. After deploying, create a separate privileged account for further work.
Example: Creating a privileged user after deploying from an Alpine cloud image
-
Switch to root:
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_config(install nano if needed:apk add nano) and ensure these lines are present and uncommented:PubkeyAuthentication yes PasswordAuthentication noAlso check
/etc/shadowand ensure the line forcloud-userdoes not contain"!". If it does, replace it with:cloud-user:*::::::: -
Restart the SSH service:
rc-service sshd restart -
Connect from your local machine as the new user:
ssh admin@<server_ip>
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.
- Download the vendor's cloud image.
- Create cloud-init configuration (
user-dataandmeta-data) and generateseed.iso— see Cloud-init setup. - Start the VM with QEMU, attaching
seed.isoso cloud-init applies your configuration on first boot — see Launching a VM with QEMU. - Inside the VM, install and configure the guest OS as needed.
- Verify the VM meets the Image requirements.
- Shut down the VM and optimize the image — see Preparing the image for upload.
- Upload the image to the Customer Portal and deploy a cloud server — see Uploading the image to the Customer Portal.
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 to make the image boot correctly in OpenStack and accept initialization metadata during the first launch.
If you already have the OS installed in your VM, skip ahead to verifying that the SSH server is installed and running.
Create a local virtual disk
When installing from an ISO, you must first create a virtual disk (the ISO is not a disk image). Choose a <disk-size> that fits your OS installation requirements (for example, 4G).
Linux / macOS
qemu-img create -f qcow2 path-to-cloud-image.qcow2 <disk-size>Windows (from %ProgramFiles%\qemu)
.\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). Attach it as a virtual CD-ROM device — see Launching a VM with QEMU:
...
-cdrom ubuntu-25.10-live-server-amd64.iso \
-boot order=d \
...Install and configure the guest OS as needed
Cloud-init does not require a "clean" system. Your ISO-based image may already contain installed packages, system users, pre-configured services, etc.
Restart the VM without the installation ISO and boot order=d
After the OS is installed, the VM must boot from its virtual disk instead of the CD-ROM.
Make sure the SSH server is installed and running
sudo apt install openssh-server
sudo systemctl enable ssh
sudo systemctl start sshInstall cloud-init and disk auto-expansion utilities
RPM-based
sudo dnf install -y cloud-init
sudo systemctl enable cloud-initDebian-based
sudo apt update
sudo apt install -y cloud-init
sudo systemctl enable cloud-initConfigure cloud-init datasources
-
Create a configuration file in the VM at
/etc/cloud/cloud.cfg.d/99-openstack-datasource.cfg. -
Add the following content:
datasource_list: [NoCloud, Ec2, ConfigDrive]
Apply cloud-init metadata and start QEMU
Attach the previously created seed.iso — see Cloud-init setup. Cloud-init reads it as a NoCloud datasource and applies the configuration from user-data and meta-data during the first boot. After initialization, seed.iso can be removed for all subsequent VM launches.
Verify the disk layout and auto-expansion
- 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.cfgRemove 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 file 70-persistent-net.rules exists, remove it:
rm /etc/udev/rules.d/70-persistent-net.rulesDo a final cleanup
Remove VM-specific state and temporary data:
- Clear cloud-init state (
/var/lib/cloud) - Remove permanently baked-in SSH host keys (so new ones are generated on first boot)
- Remove user
~/.ssh/authorized_keysfiles (keys will be injected through cloud-init) - Disable the firewall
Preparing the image for upload
Remove cloud-init state
Linux and FreeBSD/BSD
sudo cloud-init clean --logs
sudo rm -rf /var/lib/cloud/*For Ubuntu/Debian, 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.rulesFreeBSD / BSD
sudo rm /etc/ssh/ssh_host_*
sudo rm -rf /var/db/cloud/*Shut down the VM cleanly
Linux
sudo shutdown -h nowFreeBSD / BSD
sudo shutdown -p now(Optional) Optimize and compress the image
The target size limit in the Customer Portal is 5 GB.
qemu-img convert -O qcow2 -c original.qcow2 optimized.qcow2Check the resulting size:
qemu-img info optimized.qcow2Uploading the image to the Customer Portal
We recommend disabling any VPN when uploading an image. If uploading fails, try restarting the process. If the issue persists, contact our support team.
- Upload your image to the Customer Portal (see How to upload your own image), selecting the same location where you plan to deploy the cloud server.
- Create a cloud server (see Getting Started). 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.
Wait for the cloud server to deploy and obtain a public IP address from the server's details page.
Default users and access
Access to the deployed server depends on the operating system in the image. 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 the SSH key you selected during server creation is injected into ~/.ssh/authorized_keys.
Default users in vendor-provided cloud images:
| Distribution | Default user |
|---|---|
| Ubuntu | ubuntu |
| Debian | debian |
| Fedora | fedora |
| AlmaLinux | almalinux |
| Alpine Linux | alpine |
| RHEL | cloud-user |
| openSUSE | opensuse |
| FreeBSD | freebsd |
| OpenBSD | openbsd |
| NetBSD | netbsd |