Back

How to Prepare Your Own OS Image & Deploy it on 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:

  1. Deploying a cloud server using a cloud-compatible image without modifications (as-is)
  2. Preparing and customizing a cloud-compatible image before uploading
  3. 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 freebsd-boot (≈64 KB, no mount point). The second root partition is freebsd-ufs mounted as /.

The root partition must be the last one on the disk. This is required for correct auto-resize during the first boot with bsd-cloudinit.

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 (vioblk) and network (vio) drivers are supported.

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-vm

In 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
  1. Install the Windows Assessment and Deployment Kit (Windows ADK), selecting only the Deployment Tools component
  2. Execute the following command:
    C:\path-to-oscdimg-exe-file -j1 -lcidata C:\path-to-cloud-init-config-files .\seed.iso

    The oscdimg.exe utility is typically located at:

    %ProgramFiles(x86)%\Windows Kits\<Windows version>\Assessment and Deployment Kit\Deployment Tools\<architecture>\Oscdimg\oscdimg.exe

    Where:

    • <Windows version> is your installed Windows Kits version
    • <architecture> is amd64 or arm64, 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 \
  -nographic

For 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 \
  -boot once=d \
  -drive file=generic_alpine.qcow2,if=virtio,format=qcow2 \
  -cdrom seed.iso \
  -device virtio-net-pci,netdev=n1 \
  -netdev user,id=n1,hostfwd=tcp::2222-:22 \
  -nographic

For subsequent boots, remove lines -boot once=d and -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 ^
  -boot once=d ^ 
  -drive file=,if=virtio,format=qcow2 ^ 
  -cdrom  ^ 
  -device virtio-net-pci,netdev=n1 ^ 
  -netdev user,id=n1,hostfwd=tcp::2222-:22

For subsequent boots, remove the lines -boot once=d and -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:

  1. Download the vendor cloud image
  2. Upload the image to the customer portal and create a cloud server (see Uploading image to the customer portal)
  3. 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. 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

  1. Switch to the root user
    doas -s
  2. Install and configure sudo
    apk update
    apk add sudo
    adduser alpine wheel
  3. Create a new administrative user
    adduser cloud-user
    adduser cloud-user wheel
  4. 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
  5. Edit SSH configuration
    • Open /etc/ssh/sshd_config with 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/shadow and 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.

  6. Restart the SSH service
    rc-service sshd restart
  7. 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.

  1. Download the vendor's cloud image
  2. Create cloud-init configuration (user-data and meta-data) and generate the seed.iso
  3. 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
  4. Inside the VM, install and configure the guest OS as you need
  5. Verify the VM meets the OpenStack Image requirements
  6. Shut down the VM and optimize the image
  7. 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.

If you already have the operating system installed in your VM, skip ahead to the step where you verify that the SSH server is installed and running. Otherwise, start by creating a VM from the installation ISO.

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

  1. 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. The 99- prefix in the file name makes sure this configuration is loaded last. Files are processed in alphabetical order, so 99-openstack-datasource.cfg overrides earlier datasource_list settings.
  2. Add, for example:
    datasource_list: [NoCloud, Ec2, ConfigDrive]
    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.

Apply cloud-init metadata and Start QEMU

Non-cloud ISO installations do not include a metadata service, so cloud-init must read initialization data from a local source.

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 growpart and resizefs are 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/*.yaml and make sure dhcp4: true is 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 systemd journals
  • 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_keys files (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

The target size limit in the customer portal is 5 GB.

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.

  1. 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
  2. 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.

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

Useful articles

Suggested Articles

  • Cloud servers

    How-tos

  • Linux administration

    How to add new SSH key to a cloud server