# How to prepare your own OS image and deploy it on a cloud server (/docs/cloud-servers/prepare-os-image-deploy-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](/account/account-management/) 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 MBR 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. Grant sudo access to the target user if required.                                                                            | `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+ and provides optimal performance under KVM. | Ensure basic virtio disk and network support `virtio(4)` is available.                                                     |
| Image size                      | No size limit on the image itself; Customer Portal upload is limited to ≤ 5 GB                                                                                          | No size limit on the image itself; Customer Portal upload is limited to ≤ 5 GB                                                                                                                         | No size limit on the image itself; Customer Portal upload is limited to ≤ 5 GB                                                                                 | No size limit on the image itself; Customer Portal upload is limited to ≤ 5 GB                                             |

For the complete list of requirements, see the [OpenStack Image Guide](https://docs.openstack.org/image-guide/openstack-images.html).

### 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](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/](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](#case-2-deploying-from-a-cloud-compatible-image-with-customization)) or prepare a non-cloud-compatible ISO ([Case 3](#case-3-deploying-from-a-non-cloud-compatible-iso-image)).

If you want to deploy a vendor-provided cloud image as-is ([Case 1](#case-1-deploying-from-a-cloud-compatible-image-without-customization-as-is)), you can skip this section.

**Ubuntu/Linux**

```bash
sudo apt-get install qemu-system
```

**Fedora**

```bash
sudo dnf install qemu qemu-kvm
# or
dnf install @virtualization
```

**Red Hat/CentOS**

```bash
sudo yum install qemu qemu-kvm
```

**SUSE**

```bash
zypper install qemu
```

**Arch Linux**

```bash
sudo pacman -S qemu
```

**macOS** (Homebrew)

```bash
brew install qemu
```

**macOS** (MacPorts)

```bash
sudo port install qemu
```

**Windows** (PowerShell, run as administrator)

1. Enable Windows Hypervisor Platform:
   ```powershell
   Enable-WindowsOptionalFeature -FeatureName HypervisorPlatform -Online
   ```
2. Install QEMU:
   ```powershell
   winget install SoftwareFreedomConservancy.QEMU
   ```
   Or use a ready-made [installer for Windows](https://qemu.weilnetz.de/w64/).
3. Restart 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) in the same directory before building the ISO.

Example `user-data`:

```yaml
#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. 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`:

```yaml
local-hostname: fedora-vm
```

Specifying `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)

```bash
cloud-localds seed.iso user-data meta-data
```

Alternatives:

```bash
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**
Use the open-source ISOHelper PowerShell module to create the ISO image.

1. Open Windows PowerShell and install the ISOHelper module:
   ```powershell
   Install-Module -Name ISOHelper
   ```
2. Create the ISO image:
   ```powershell
   New-ISOImageFile -SourcePath C:\path-to-cloud-init-config-files -DestinationPath .\seed.iso
   ```

A `seed.iso` file will be created in the current 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**

```bash
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**

```bash
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 \
  -nographic
```

For subsequent boots, remove the line `-cdrom seed.iso`.

**Windows** (run from `%ProgramFiles%\qemu`)

```bat
.\qemu-system-x86_64.exe ^
-accel whpx ^
-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-: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:

```bash
ssh -p 2222 <user>@localhost
```

To verify cloud-init behavior, run:

```bash
cloud-init status --long
```

On 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:

1. Download the vendor cloud image.
2. Upload the image to the Customer Portal and create a cloud server (see [Uploading the image to the Customer Portal](#uploading-the-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](#default-users-in-vendor-provided-cloud-images)).
   For Alpine, this is `alpine`:

   ```bash
   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**

1. Switch to root:

   ```bash
   doas -s
   ```

2. Install and configure sudo:

   ```bash
   apk update
   apk add sudo
   adduser alpine wheel
   ```

3. Create a new administrative user:

   ```bash
   adduser cloud-user
   adduser cloud-user wheel
   ```

4. Configure SSH access for the new user:

   ```bash
   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` (install nano if needed: `apk add nano`) and ensure these lines are present and uncommented:

   ```
   PubkeyAuthentication yes
   PasswordAuthentication no
   ```

   Also check `/etc/shadow` and ensure the line for `cloud-user` does not contain `"!"`. If it does, replace it with:

   ```
   cloud-user:*:::::::
   ```

6. Restart the SSH service:

   ```bash
   rc-service sshd restart
   ```

7. Connect from your local machine as the new user:

   ```bash
   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.

1. Download the vendor's cloud image.
2. Create cloud-init configuration (`user-data` and `meta-data`) and generate `seed.iso` — see [Cloud-init setup](#cloud-init-setup).
3. Start the VM with QEMU, attaching `seed.iso` so cloud-init applies your configuration on first boot — see [Launching a VM with QEMU](#launching-a-vm-with-qemu).
4. Inside the VM, install and configure the guest OS as needed.
5. Verify the VM meets the [Image requirements](#image-requirements).
6. Shut down the VM and optimize the image — see [Preparing the image for upload](#preparing-the-image-for-upload).
7. Upload the image to the Customer Portal and deploy a cloud server — see [Uploading the image to the Customer Portal](#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**

```bash
qemu-img create -f qcow2 path-to-cloud-image.qcow2 <disk-size>
```

**Windows** (from `%ProgramFiles%\qemu`)

```bat
.\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](#launching-a-vm-with-qemu):

```bash
...
-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

```bash
sudo apt install openssh-server
sudo systemctl enable ssh
sudo systemctl start ssh
```

### Install cloud-init or an alternative initialization service

The initialization service is responsible for:

* Retrieving metadata and user data (SSH keys, hostname, and so on)
* Creating users
* Running first-boot initialization tasks, including automatic disk expansion

Install disk auto-expansion utilities to allow the root partition and filesystem to grow automatically when the flavor disk is larger than the original image.

On Linux distributions, cloud-init is commonly used together with utilities such as `cloud-utils` or `cloud-utils-growpart`.

Some Unix and BSD systems may use different initialization tools or require manual configuration. Refer to your operating system documentation.

Examples for Linux distributions:

**RPM-based**

```bash
sudo dnf install -y cloud-init
sudo systemctl enable cloud-init
```

**Debian-based**

```bash
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 at `/etc/cloud/cloud.cfg.d/99-datasources.cfg`.

2. 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-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 `growpart` and `resizefs` are present in the module lists in `/etc/cloud/cloud.cfg`:

```bash
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 file `70-persistent-net.rules` exists, remove it:

```bash
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
* Remove permanently baked-in SSH host keys (so new ones are generated on first boot)
* Remove user `~/.ssh/authorized_keys` files (keys will be injected through cloud-init)
* Disable the firewall

## Preparing the image for upload

### Remove cloud-init state

**Linux and FreeBSD/BSD**

```bash
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**

```bash
# 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**

```bash
sudo rm /etc/ssh/ssh_host_*
sudo rm -rf /var/db/cloud/*
```

### Shut down the VM cleanly

**Linux**

```bash
sudo shutdown -h now
```

**FreeBSD / BSD**

```bash
sudo shutdown -p now
```

### (Optional) Optimize and compress the image

The target size limit in the Customer Portal is 5 GB.

```bash
qemu-img convert -O qcow2 -c original.qcow2 optimized.qcow2
```

Check the resulting size:

```bash
qemu-img info optimized.qcow2
```

## Uploading 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](https://www.servers.com/contact-us#support).

1. Upload your image to the Customer Portal (see [How to upload your own image](/cloud-servers/service-management/)), selecting the same location where you plan to deploy the cloud server.
2. Create a cloud server (see [Getting Started](/cloud-servers/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. See [How to access the cloud server](/cloud-servers/creating-a-linux-based-cloud-server-and-accessing-it-via-an-ssh-key/) for details.

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. Usernames can vary slightly between distributions or custom vendor builds. Check the image description page for confirmation or use this table to find the default login user for common vendor 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`     |
