Create a Rocky Linux 10 VM on Proxmox VE (CLI only: ISO download → unattended install → SSH)

This guide is written for AI agents. Every step is a copy-paste shell block that runs on a Linux workstation and talks to the Proxmox VE REST API with curl. Nothing has to be installed or run on the Proxmox host, and no console interaction is needed. The install is unattended (Kickstart on a second CD-ROM labelled OEMDRV), and the VM comes up with a static IP and key-based SSH.

目次

Result

ItemValue
OSRocky Linux 10 (latest minor; verified with 10.2 minimal ISO)
VMSeaBIOS, cpu=host, 2 vCPU, 4 GB RAM, 32 GB VirtIO SCSI disk (defaults; see Resources), VirtIO NIC
NetworkStatic IPv4 set at install time
Login$VM_USER with your SSH public key, passwordless sudo
Agent work6 command blocks and one wait loop

Requirements

  • Workstation: bash, curl, jq, genisoimage (or mkisofs), ssh, and an SSH key pair (~/.ssh/id_ed25519.pub).
  • Proxmox VE 8.x/9.x reachable on port 8006. The examples log in as root@pam. Another user needs VM.Allocate, VM.Config.*, VM.PowerMgmt, Datastore.AllocateSpace, Datastore.AllocateTemplate and Sys.Modify (required by download-url and sendkey).
  • A storage that accepts ISO images (local) and one for VM disks (local-lvm).
  • A free IP address and VMID.

Resources

CPURAMDisk
Minimum (Rocky Linux 10: minimum hardware requirements)1 GHz x86_64-v32 GB (text-mode install)10 GB
Recommended (same page)4 GB+ for GUI installs20 GB+
Defaults in this guide (verified)2 vCPU4 GiB32 GiB

With the defaults, Step 5 to SSH login took about 7 minutes. To change them after installation, run pve PUT /nodes/$PVE_NODE/qemu/$VMID/config -d cores=4 -d memory=8192; the new values take effect after the VM is stopped and started again.

Step 1. Set variables

Edit the values, then run the block. All later blocks use these variables in the same shell.

PVE_HOST=192.168.1.13         # Proxmox VE host
PVE_NODE=pve                  # node name (pvesh get /nodes, or the GUI tree)
PVE_USER=root@pam
PVE_PASS='your-root-password'
ISO_STORE=local               # storage with "ISO image" content
DISK_STORE=local-lvm          # storage for VM disks
BRIDGE=vmbr0
CORES=2                       # vCPUs (see "Resources")
MEMORY=4096                   # MiB
DISK_GB=32
VMID=805
VMNAME=rocky10
IP=192.168.1.85
NETMASK=255.255.255.0
GW=192.168.1.1
DNS=8.8.8.8
VM_USER=agent
VM_PASS='ChangeMe-2026'
SSH_PUBKEY=$(cat ~/.ssh/id_ed25519.pub)

Step 2. Log in to the API

Creates a ticket (valid 2 hours) and two helpers: pve (API call) and wait_task (waits for a Proxmox task and prints its exit status, OK on success).

PVE=https://$PVE_HOST:8006/api2/json
read -r TICKET CSRF < <(curl -sk -d "username=$PVE_USER" --data-urlencode "password=$PVE_PASS" $PVE/access/ticket | jq -r '.data|"\(.ticket) \(.CSRFPreventionToken)"')
pve(){ local m=$1 p=$2; shift 2; curl -sSk -X "$m" -b "PVEAuthCookie=$TICKET" -H "CSRFPreventionToken: $CSRF" "$PVE$p" "$@"; }
wait_task(){ local u s; u=$(jq -rn --arg u "$1" '$u|@uri'); while sleep 5; do s=$(pve GET /nodes/$PVE_NODE/tasks/$u/status | jq -r '.data|"\(.status) \(.exitstatus)"'); [ "${s%% *}" = stopped ] && { echo "${s#* }"; return; }; done; }
pve GET /version | jq -c .data

Step 3. Download the ISO to Proxmox

Proxmox downloads the latest minimal ISO itself and verifies its SHA-256. The block is skipped if the file already exists. Expected output: OK (a few minutes for about 2.6 GB).

BASE=https://download.rockylinux.org/pub/rocky/10/isos/x86_64
read -r ISO SHA < <(curl -s $BASE/CHECKSUM | sed -nE 's/^SHA256 \((Rocky-10\.[0-9]+-x86_64-minimal\.iso)\) = ([0-9a-f]+)$/\1 \2/p' | head -1)
echo "$ISO"
pve GET "/nodes/$PVE_NODE/storage/$ISO_STORE/content?content=iso" | grep -q "iso/$ISO\"" && echo exists || \
wait_task "$(pve POST /nodes/$PVE_NODE/storage/$ISO_STORE/download-url -d content=iso -d filename=$ISO --data-urlencode url=$BASE/$ISO -d checksum=$SHA -d checksum-algorithm=sha256 | jq -r .data)"

Step 4. Create the Kickstart ISO and upload it

Anaconda automatically loads ks.cfg from any volume labelled OEMDRV, so no boot parameter has to be typed. Expected output: OK.

mkdir -p /tmp/$VMNAME
cat > /tmp/$VMNAME/ks.cfg <<EOF
text
lang en_US.UTF-8
keyboard us
timezone Etc/UTC --utc
network --bootproto=static --device=link --ip=$IP --netmask=$NETMASK --gateway=$GW --nameserver=$DNS --hostname=$VMNAME --activate
rootpw --lock
user --name=$VM_USER --password=$VM_PASS --plaintext --groups=wheel
sshkey --username=$VM_USER "$SSH_PUBKEY"
firewall --enabled --service=ssh
ignoredisk --only-use=sda
zerombr
clearpart --all --initlabel
autopart --type=lvm
bootloader --boot-drive=sda
reboot
%packages
@^minimal-environment
%end
%post
echo "$VM_USER ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/$VM_USER
%end
EOF
genisoimage -quiet -V OEMDRV -o /tmp/$VMNAME/$VMNAME-ks.iso /tmp/$VMNAME/ks.cfg
wait_task "$(pve POST /nodes/$PVE_NODE/storage/$ISO_STORE/upload -F content=iso -F filename=@/tmp/$VMNAME/$VMNAME-ks.iso | jq -r .data)"

Step 5. Create and start the VM

cpu=host is required: Rocky Linux 10 needs an x86-64-v3 CPU (Intel Haswell / AMD Zen or newer), and the default kvm64 CPU type does not provide it. The boot order puts the empty disk first, so the VM boots the ISO now and the installed disk after the install reboots. Expected output: OK.

wait_task "$(pve POST /nodes/$PVE_NODE/qemu -d vmid=$VMID -d name=$VMNAME -d ostype=l26 -d cpu=host -d cores=$CORES -d memory=$MEMORY \
  -d scsihw=virtio-scsi-single --data-urlencode "scsi0=$DISK_STORE:$DISK_GB" --data-urlencode "net0=virtio,bridge=$BRIDGE" \
  --data-urlencode "ide2=$ISO_STORE:iso/$ISO,media=cdrom" --data-urlencode "ide3=$ISO_STORE:iso/$VMNAME-ks.iso,media=cdrom" \
  --data-urlencode "boot=order=scsi0;ide2" -d start=1 | jq -r .data)"
sleep 10; for k in up ret; do pve PUT /nodes/$PVE_NODE/qemu/$VMID/sendkey -d key=$k >/dev/null; done

The last line selects “Install Rocky Linux” in the boot menu through the sendkey API. The default entry, “Test this media & install”, first reads the whole ISO (several minutes on busy storage). If the keys arrive too early or are not permitted, nothing breaks: the menu times out after 60 seconds, runs the media check and installs anyway.

Step 6. Wait for SSH and log in

The install runs, the VM reboots into the installed system, and the loop ends at the first successful key login. Verified time from Step 5 to login: about 7 minutes.

SSHO="-o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR"
until ssh $SSHO $VM_USER@$IP true 2>/dev/null; do sleep 20; done
ssh $SSHO $VM_USER@$IP 'cat /etc/rocky-release; ip -4 -br addr; sudo -n true && echo sudo-ok'

Step 7. Clean up (optional)

Detach both CD-ROMs and delete the Kickstart ISO from storage.

pve PUT /nodes/$PVE_NODE/qemu/$VMID/config -d delete=ide3 --data-urlencode "ide2=none,media=cdrom"
pve DELETE /nodes/$PVE_NODE/storage/$ISO_STORE/content/$ISO_STORE:iso/$VMNAME-ks.iso

Troubleshooting

  • wait_task prints an error instead of OK: list the latest failed tasks and their status text with pve GET "/nodes/$PVE_NODE/tasks?errors=1" | jq -r '.data[:3][] | "\(.type) \(.id) \(.status)"'.
  • Permission check failed on create: a non-root user must create the VM in a pool it has rights on (add -d pool=<pool>).
  • SSH never opens after 20 minutes: open the VM console in the Proxmox web UI. A Kickstart syntax error is shown on screen.
  • To start over: pve POST /nodes/$PVE_NODE/qemu/$VMID/status/stop, then pve DELETE "/nodes/$PVE_NODE/qemu/$VMID?purge=1&destroy-unreferenced-disks=1".
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

目次