MaruOpsInfrastructure and AI notes

About (Korean)Contact (Korean)

Deploy Rocky Linux and Ubuntu Cloud Images on AHV: QCOW2 Passwords and cloud-init

A technician using a laptop beside a server rack at NERSC

Reference photo · A technician using a laptop beside a server rack at NERSC · Photo: Derrick Coetzee / Source / CC0 1.0 · Resized for listings; illustrative photo, not the tested environment

Rocky Linux and Ubuntu cloud images let you deploy a VM without running an OS installer. Download an official disk image, import it into Nutanix AHV, and provide an account and SSH key through cloud-init on first boot. You can also change passwords and configuration inside the QCOW2 file from a Linux workstation before uploading it.

The examples use x86_64 Rocky Linux 9 GenericCloud and Ubuntu 24.04 LTS. Image locations and documentation were checked on October 24, 2026. This is a configuration guide, not a report of an end-to-end deployment on a Nutanix cluster. Check guest OS support and the documentation for your AOS, AHV, and Prism versions.

On this page

Choose how to configure the VM

ItemScope used here
HypervisorNutanix AHV; distinguish this from ESXi running on Nutanix hardware
WorkstationUbuntu 24.04 or Rocky Linux 9, with disk space and sudo access
Initial networkA VLAN with DHCP or Nutanix IPAM address allocation
Example VM sizing2 vCPUs, 4 GiB RAM, 40 GiB disk; a lab example, not product minimums
Customizationcloud-init for per-VM accounts and SSH keys; offline editing for common files and recovery passwords

cloud-init configures users, hostnames, packages, and other settings during initial boot. It is a useful starting point for repeated deployments because each VM can receive its own settings. Offline customization changes files inside the disk image without booting the VM. When combining both methods, avoid assigning conflicting values to the same setting.

1. Download official cloud images

Rocky Linux 9 GenericCloud

Select the GenericCloud-Base x86_64.qcow2 file from the official Rocky Linux image directory. It is different from an installer ISO, EC2 image, or Azure VHD. The contents behind latest change, so retain the original download and its checksum file together.

mkdir -p ~/ahv-cloud-images/rocky9 ~/ahv-cloud-images/ubuntu2404
cd ~/ahv-cloud-images/rocky9

rocky_base='https://dl.rockylinux.org/pub/rocky/9/images/x86_64'
rocky_image='Rocky-9-GenericCloud-Base.latest.x86_64.qcow2'
curl -fLO "$rocky_base/$rocky_image"
curl -fLO "$rocky_base/$rocky_image.CHECKSUM"

sha256sum "$rocky_image"
cat "$rocky_image.CHECKSUM"

Continue only when the calculated SHA-256 matches the SHA256 (...) = ... value in the checksum file. Rocky supplies a BSD-style checksum in this example, so do not apply the Ubuntu sha256sum -c command to it unchanged.

Ubuntu 24.04 LTS

Download ubuntu-24.04-server-cloudimg-amd64.img from the official Ubuntu release directory. Here, amd64 means the Intel/AMD x86_64 architecture. This Ubuntu .img is QCOW2, despite its filename extension. Canonical documents this QCOW image format.

cd ~/ahv-cloud-images/ubuntu2404

ubuntu_base='https://cloud-images.ubuntu.com/releases/noble/release'
ubuntu_image='ubuntu-24.04-server-cloudimg-amd64.img'
curl -fLO "$ubuntu_base/$ubuntu_image"
curl -fLO "$ubuntu_base/SHA256SUMS"

sha256sum --check --ignore-missing SHA256SUMS

Confirm that the target file reports OK and the command succeeds. A checksum detects transfer corruption. If your process also requires publisher authentication, verify the signed checksum using the signature file in the official directory and a publisher key trusted separately. If a release changes between downloads, retrieve the image and checksum from the same build.

2. Inspect the format and create working copies

Install these tools on the Linux workstation holding the downloaded images, not inside the guest VM. On Ubuntu, use:

sudo apt update
sudo apt install -y qemu-utils libguestfs-tools

On a Rocky Linux 9 workstation, use the following packages. guestfs-tools provides tools such as virt-customize and virt-cat.

sudo dnf install -y qemu-img guestfs-tools

Use qemu-img to inspect the actual format and virtual capacity. Keep the original and create independent working copies. Edit only the copies below.

cd ~/ahv-cloud-images

qemu-img info rocky9/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2
qemu-img info ubuntu2404/ubuntu-24.04-server-cloudimg-amd64.img

cp --reflink=auto --sparse=always \
  rocky9/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2 rocky9-ahv.qcow2
cp --reflink=auto --sparse=always \
  ubuntu2404/ubuntu-24.04-server-cloudimg-amd64.img ubuntu2404-ahv.qcow2

qemu-img check -f qcow2 rocky9-ahv.qcow2
qemu-img check -f qcow2 ubuntu2404-ahv.qcow2

Check file format: qcow2 and virtual size. Allocated file space and virtual capacity are different. A 40 GiB AHV disk must be at least as large as the source image’s virtual capacity. If a backing file is listed, the image has an external dependency; ensure that the file you upload is self-contained.

Do not edit a disk while a VM or another tool is using it. virt-customize changes the image in place. The commands below target offline local copies. Keep these tools on the workstation rather than installing them on an AHV host or CVM. See the libguestfs warning about concurrent editing.

3. Change a password directly inside QCOW2

Set a root password for console access

This example sets a root password for recovery through the VM console. Run it in Bash. For Ubuntu, replace rocky9-ahv.qcow2 with ubuntu2404-ahv.qcow2. Read the password from a restricted temporary file rather than placing it directly in a command argument.

cd ~/ahv-cloud-images

(
  set -e
  umask 077
  image_password_file=$(mktemp)
  trap 'rm -f -- "$image_password_file"' EXIT

  read -r -s -p 'New root password: ' image_password
  printf '\n'
  test -n "$image_password"
  printf '%s\n' "$image_password" > "$image_password_file"
  unset image_password

  sudo virt-customize --format qcow2 -a rocky9-ahv.qcow2 \
    --root-password "file:$image_password_file" \
    --selinux-relabel
)

In the libguestfs password selector syntax, file: reads the first line as the new password. Relabel modified files for SELinux on Rocky. Removing the temporary file does not guarantee secure erasure; use a temporary location consistent with your workstation’s handling policy.

Setting a root password does not enable root SSH access. This command does not relax PermitRootLogin or SSH authentication settings. Use the credential for recovery through the Prism console, and use a regular account with a public key for routine SSH access. First-boot cloud-init settings that change or lock the root password will also take effect.

Check whether the regular account already exists

A cloud image’s default account may be created only when cloud-init runs. --password does not create an account, so inspect /etc/passwd first. There is no need to print /etc/shadow and its password hashes into a log.

sudo virt-cat --format=qcow2 -a rocky9-ahv.qcow2 /etc/passwd
sudo virt-cat --format=qcow2 -a rocky9-ahv.qcow2 /etc/cloud/cloud.cfg

Determine the username from /etc/passwd and system_info.default_user. If that user already exists, replace --root-password "file:$image_password_file" in the previous example with --password "rocky:file:$image_password_file". For Ubuntu, use the username you verified. If it does not exist yet, create it through cloud-init or use the dedicated account example below. See also the virt-cat manual.

4. Create an SSH account with cloud-init

For normal Nutanix deployments, provide per-VM user-data such as the following. It creates an ops account with an SSH public key. Replace REPLACE_WITH_YOUR_FULL_SSH_PUBLIC_KEY with the complete single line from a file such as ~/.ssh/id_ed25519.pub on your client. Do not upload the private key.

#cloud-config
hostname: ahv-linux-01
manage_etc_hosts: true
disable_root: true
ssh_pwauth: false
users:
  - default
  - name: ops
    shell: /bin/bash
    sudo:
      - ALL=(ALL) NOPASSWD:ALL
    lock_passwd: true
    ssh_authorized_keys:
      - REPLACE_WITH_YOUR_FULL_SSH_PUBLIC_KEY
timezone: Asia/Seoul

default retains creation of the image’s default account. ops uses key-based access and receives passwordless sudo in this example. Restrict sudo as required by your operational policy, and adjust the hostname and timezone per VM. See the cloud-init user configuration examples.

Also provide a console password when needed

If the regular account also needs a console password, run openssl passwd -6 on the workstation and enter the password interactively to generate a SHA-512 crypt hash. Use the following as an alternative to the previous user-data. Replace REPLACE_WITH_SHA512_CRYPT_HASH with the generated value beginning with $6$, keeping it inside quotes.

#cloud-config
hostname: ahv-linux-01
manage_etc_hosts: true
disable_root: true
ssh_pwauth: false
users:
  - default
  - name: ops
    shell: /bin/bash
    sudo:
      - ALL=(ALL) ALL
    lock_passwd: false
    hashed_passwd: 'REPLACE_WITH_SHA512_CRYPT_HASH'
    ssh_authorized_keys:
      - REPLACE_WITH_YOUR_FULL_SSH_PUBLIC_KEY
timezone: Asia/Seoul

This example uses a password at the console and for sudo while retaining SSH key authentication. Set ssh_pwauth: true only if SSH password authentication is also required, then check the effective sshd configuration and authentication policy. hashed_passwd also applies to existing accounts, so it can replace an offline password change. Protect user-data containing password hashes. See the user and password module reference.

Save the edited YAML as user-data.yaml and validate it on a Linux machine with cloud-init installed. Schema validation does not verify that your public key or hostname is correct.

cloud-init schema --config-file user-data.yaml

5. Import the image and create an AHV VM

Import it into Images as a disk image

In Prism Central, open Infrastructure, then Compute → Images, and start image creation. Some Prism Element versions expose this through Settings → Image Configuration. Follow the labels in your installed release and import QCOW2 as a disk image, not an ISO. The Nutanix image management guide lists QCOW2 as a supported format.

  1. Use a name such as rocky9-cloud-base and retain the source and build information.
  2. Select the disk image type, commonly DISK, and upload rocky9-ahv.qcow2 or ubuntu2404-ahv.qcow2.
  3. For URL import, provide an HTTP(S) URL reachable by the image service. A workstation’s local path is not a download URL.
  4. Wait for import and placement on the target cluster to complete before creating the VM.

If an upload screen rejects Ubuntu’s original .img extension, the working copy in this guide already uses .qcow2. Its internal format is QCOW2, so changing the filename does not require a RAW conversion. Editing the local file after import does not update the imported image or disks of VMs already created from it.

Clone the OS disk from the imported image

  1. Create a VM on the target cluster and assign CPU and memory.
  2. Add the OS disk by cloning the imported image, using an option such as Clone from Image Service, rather than adding only an empty disk.
  3. Use a supported VirtIO SCSI disk and VirtIO NIC, and select the OS disk as the boot device. Its capacity must be at least the image’s virtual capacity.
  4. Connect the NIC to the intended VLAN or subnet and confirm DHCP or IPAM allocation is available.
  5. Match BIOS or UEFI to the image’s bootloader and AHV support. A UEFI-only image requires UEFI; verify Secure Boot support separately.
  6. Provide user-data through the equivalent of Guest Customization → Cloud-init → Custom Script. Paste or upload plain YAML in the UI rather than a Base64 value intended for an API.
  7. Save the configuration before the first power-on.

The VM creation procedure in the Prism Central Infrastructure Guide documents cloud-init selection and script entry. Menu locations and disk bus choices vary by release, so use the documentation matching your installation.

The initial example uses DHCP. Simply adding a top-level network: section to user-data does not provide arbitrary static network configuration. cloud-init handles networking through separate configuration sources. For static addressing, verify how your Prism release passes network configuration to the datasource, or configure networking inside the guest after initial access. See cloud-init’s network configuration sources.

6. Log in and check the first boot

Find the assigned IP through Prism or your DHCP server, then connect as ops. Replace the documentation address 192.0.2.50 below with the VM’s actual IP. Verify the SSH host key fingerprint through a separate channel such as the console.

ssh -i ~/.ssh/id_ed25519 ops@192.0.2.50

Run the following inside the booted guest. Wait for cloud-init to finish, then inspect errors, addressing, routes, name resolution, and disk capacity.

cloud-init status --wait
sudo cloud-init status --long
ip -br address
ip route
getent hosts download.rockylinux.org
lsblk -f
df -hT /
sudo tail -n 80 /var/log/cloud-init.log
sudo tail -n 80 /var/log/cloud-init-output.log

Read any warnings or errors before proceeding. The cloud-init CLI reference covers status and validation commands. A DHCP lease alone does not confirm working DNS, a default route, or connectivity beyond the subnet.

Increasing the AHV disk size does not guarantee that the partition and filesystem have grown. Compare lsblk with df, then choose the expansion procedure for the actual LVM and XFS/ext4 layout. See the non-LVM root expansion guide or the LVM, XFS, and ext4 root expansion guide for your layout.

7. Make other offline changes

Add a dedicated account with an SSH key

If your distribution workflow cannot use cloud-init, you can create a dedicated account and inject its public key offline. This example creates labadmin if absent, then adds its key and sudo configuration. The account must exist before --ssh-inject runs. Keep the image offline throughout.

cd ~/ahv-cloud-images
test -s "$HOME/.ssh/id_ed25519.pub"

sudo virt-customize --format qcow2 -a rocky9-ahv.qcow2 \
  --run-command 'id -u labadmin >/dev/null 2>&1 || useradd -m -s /bin/bash labadmin' \
  --ssh-inject "labadmin:file:$HOME/.ssh/id_ed25519.pub" \
  --write '/etc/sudoers.d/90-labadmin:labadmin ALL=(ALL) NOPASSWD:ALL' \
  --chmod '0440:/etc/sudoers.d/90-labadmin' \
  --run-command 'visudo -cf /etc/sudoers.d/90-labadmin' \
  --selinux-relabel

This assumes sudo is installed in the guest image. Provide only a public key, and decide whether a personal key and broad sudo privileges belong in an image shared with other users. If cloud-init also configures this account, keep its keys, password policy, and sudo settings consistent. See libguestfs SSH key selectors.

Set a hostname, timezone, files, or packages

You can also set the hostname and timezone or place a file in the image. Prepare motd.txt on the workstation as the login notice you want to install. --upload replaces the destination, so inspect or save its existing content when needed.

sudo virt-customize --format qcow2 -a rocky9-ahv.qcow2 \
  --hostname ahv-rocky-01 \
  --timezone Asia/Seoul \
  --upload motd.txt:/etc/motd \
  --selinux-relabel

Packages can be installed as shown below. This uses the guest’s package manager and requires network and DNS access to its repositories. In a restricted network, configure reachable internal repositories first or install the packages after the VM boots.

sudo virt-customize --format qcow2 -a ubuntu2404-ahv.qcow2 \
  --install qemu-guest-agent

qemu-guest-agent is different from Nutanix Guest Tools (NGT). Check whether it is needed and which features your environment supports. For an interactive file edit, you can also use sudo virt-edit --format=qcow2 -a rocky9-ahv.qcow2 /etc/hosts. See virt-customize for customization options and virt-edit for interactive editing.

8. Troubleshoot access and boot problems

SymptomInspectNext action
No bootable deviceImage clone, boot disk, BIOS/UEFI, import taskConfirm the VM is not booting an empty disk and match the firmware to the image
No IP addressVLAN, NIC connection, DHCP/IPAM, guest link and routesUse the console to check ip -br address
Blank consoleVM state and graphical versus serial console outputCheck SSH and the image’s console configuration before assuming it failed to boot
Permission denied (publickey)Existing username, public key, permissions, cloud-init logsCheck that you are not relying on a root password change to enable SSH
New password does not workcloud-init overrides, lock/expiry, effective sshd settingsInspect passwd -S separately from SSH authentication policy
Root filesystem remains smallDisk, partition, LVM, and filesystem capacitiesIdentify the layout and expand only the required layers

If you have administrative console access, inspect ops and the effective SSH configuration with the commands below. passwd -S does not print the password hash. sshd -T is more useful than reading one configuration line, although environments using Match blocks also need evaluation for the relevant connection conditions. See the OpenSSH configuration manual.

sudo passwd -S ops
sudo /usr/sbin/sshd -T | grep -E \
  '^(passwordauthentication|kbdinteractiveauthentication|permitrootlogin|pubkeyauthentication|usepam) '
sudo journalctl -b -u ssh -u sshd --no-pager -n 80

Identify the failing layer before relaxing root SSH access or disabling SELinux. If virt-customize itself cannot start, use libguestfs-test-tool to diagnose the workstation. A system without hardware virtualization acceleration may run slowly; that alone does not establish an image defect.

Before reusing the image as a template

An offline-customized official image and a template captured from a previously booted VM need different handling. A booted VM can retain cloud-init state, machine-id, SSH host keys, DHCP state, and instance credentials. Avoid a shared fixed password and return to per-VM keys and initial credentials for a shared template.

For a cloud-init-managed template, consider running sudo cloud-init clean --logs --machine-id in the prepared source VM, shutting it down, and then capturing it. This causes cloud-init to run again at the next boot; do not apply it indiscriminately to troubleshoot a running instance. Check SSH host keys, user keys, and other state separately. If using virt-sysprep, select the required operations explicitly so default cleanup does not remove keys or settings you need.

Start with one VM and verify initialization, SSH access, and networking and storage after a reboot before deploying more. For additional per-VM configuration, see the practical cloud-init examples.