Create a cloud init vm using API only

Zernox

New Member
Jan 8, 2026
5
0
1
Hello there.
I am attempting to create a vm or rather vm template using the proxmox api via proxmoxxer.
But I run into multiple issues.
1. Creating a template does not work at all via api or rather creating a bootable vm with cloudinit
2. Cloud init it self from template does not work using api. (it creates a vm from the template but cloud init does not set password, ip nor any other param)

Here is how I create (or attempt to) the vm. Anyone notice an issue? I oriented myself at the help article of proxmox and clone it to api calls.
Python:
def _create_cloud_init_vm(
        proxmox: ProxmoxInterface,
        node: str,
        vmid: int,
        order: dict[str, Any],
        image: dict[str, Any],
        storage_name: str,
) -> str:
        #STORAGE:import/TEST-noble-server-cloudimg-amd64.img.raw
        image_path = image.get("imagepath")
        
        public_key, private_key_path = _generate_ssh_key_pair(
            vmid=vmid,
            system_name=order["systemName"],
        )

        connection = proxmox.connection
        vm_created = False

        try:
            # Create the VM and Cloud-Init drive
            task = connection.nodes(node).qemu.create(
                vmid=vmid,
                name=order["systemName"],
                cores=int(order["cpu"]),
                memory=int(order["ram"]) * 1024,
                cpu="x86-64-v2-AES",
                net0=_build_vm_network(order),
                agent="enabled=1",
                ostype="l26",
                description=genereate_documentation(order),
                tags=_build_tags(order),
            )
            _wait_for_task(connection, node, task)
            vm_created = True
            connection.nodes(node).qemu(vmid).config.put(
                scsihw="virtio-scsi-single",
                scsi0=(
                    f"{storage_name}:0,"
                    f"import-from={image_path},"
                    "discard=on,"
                    "ssd=1,"
                    "iothread=1"
                ),
            )

            # PUT /nodes/{node}/qemu/{vmid}/resize
            task = connection.nodes(node).qemu(vmid).resize.put(
                disk="scsi0",
                size=f"{int(order['storage'])}G",
            )
            if task:
                _wait_for_task(connection, node, task)

            # PUT /nodes/{node}/qemu/{vmid}/config
            connection.nodes(node).qemu(vmid).config.put(
                ide2=f"{storage_name}:cloudinit",
                boot="order=scsi0",
                tablet=0,
                localtime=0,
                sshkeys=quote(public_key.strip(), safe=""),
                ipconfig0="ip=dhcp",
            )


            # Start the VM
            task = connection.nodes(node).qemu(vmid).status.start.post()
            if task:
                _wait_for_task(connection, node, task)

            return private_key_path

        except Exception:
            # Remove the incomplete VM
            if vm_created:
                try:
                    task = connection.nodes(node).qemu(vmid).delete(
                        purge=1,
                        destroy_unreferenced_disks=1,
                    )
                    if task:
                        _wait_for_task(connection, node, task)
                except Exception:
                    pass
            raise

The vm of this function seems to have cloud init and a drive with right size but bootloops. (no bootable media)
And when I create a vm from a template created like in the proxmox guide. It boots alright but no cloudinit function or setup is ran.
 
Hi,
could you share the resulting VM configurations for your attempts, i.e. qm config ID? Which image do you import from?
 
Hi @Zernox,
I am attempting to create a vm or rather vm template using the proxmox api via proxmoxxer.
Your post is confusing because you seem to jump between the VM and Template but the connection is not there.

For example, in your Proxmoxer config (btw this is a 3rd party tool and if you really have questions/issues with it - you should reach out to the authors): you are not setting template flag anywhere I can see.

You are mentioning that password is not set, yet there is no indication in your post where the password would be set. An ssh-key is added.

Is the file you are importing really a RAW file or is it QCOW? (qemu-img info [file]). Additionally, sometimes resize is not handled gracefully by certain kernels/images.

I would recommend that you pare down your configuration, remove resize, keep the calls to the minimum. There is very unlikely an error in PVE in this operation. Perhaps, skip the Proxmoxer - use pvesh to get the basic configuration working.


Blockbridge : Ultra low latency all-NVME shared storage for Proxmox - https://www.blockbridge.com/proxmox
 
Hi,
could you share the resulting VM configurations for your attempts, i.e. qm config ID? Which image do you import from?
Hey there:

agent: enabled=1
boot: order=scsi0
cores: 2
cpu: x86-64-v2-AES
description: HIDDEN
ide2: STORAGE:vm-4029-cloudinit,media=cdrom
ipconfig0: ip=dhcp
localtime: 0
memory: 4096
meta: creation-qemu=11.0.3,ctime=1790831546
name: HIDDEN
net0: virtio=HIDDEN,bridge=vmbr1
ostype: l26
scsi0: STORAGE:vm-4029-disk-0,discard=on,iothread=1,size=15G,ssd=1
scsihw: virtio-scsi-single
smbios1: uuid=HIDDEN
sshkeys: HIDDEN
tablet: 0
tags: test
vmgenid: HIDDEN
 
Hi @Zernox,

Your post is confusing because you seem to jump between the VM and Template but the connection is not there.

For example, in your Proxmoxer config (btw this is a 3rd party tool and if you really have questions/issues with it - you should reach out to the authors): you are not setting template flag anywhere I can see.

You are mentioning that password is not set, yet there is no indication in your post where the password would be set. An ssh-key is added.

Is the file you are importing really a RAW file or is it QCOW? (qemu-img info [file]). Additionally, sometimes resize is not handled gracefully by certain kernels/images.

I would recommend that you pare down your configuration, remove resize, keep the calls to the minimum. There is very unlikely an error in PVE in this operation. Perhaps, skip the Proxmoxer - use pvesh to get the basic configuration working.


Blockbridge : Ultra low latency all-NVME shared storage for Proxmox - https://www.blockbridge.com/proxmox
1. I jump because using a template works. I might not want to use templates as it kinda locks me to specific nodes or forces me to share all templates on all nodes.... The connection is that the official guide show a step by step to a TEMPLATE. So i used these commands to replicate as a sanity check
2. Proxmoxer is 3rd party yes. But it is only a wrapper for the official api. So it does not seem to be an 3rd party too issue at hand here... And also why is there a tag if I cannot ask for help :) Questions cannot hurt
3. Password is set in my template function (because that is a close clone of the guide). It is not my primary issue so i shared the target version that does not set password but rather ssh keys for security.
4. the image i used is from https://cloud-images.ubuntu.com/noble/current/ and ends on .img but is described by ubuntu as QCow2 UEFI/GPT Bootable disk image and allows editing by qcow2 standard too. But when I use the download from url function on my import storage in proxmox it forces the .raw ending.
5. I agree this might not or rather probably will not be a pve error but rather user error. That is why I asked here if its some kind of visible issue I am too blind for. pvesh is a toolcall for my python code and that is not... increasing performance. And it is the minimum

Hope that clears your questions and helps others help me

Oh and on that thought. Here is my from template code. That only has the issue of not setting the password
Python:
def _create_cloud_init_vm_from_template(
    proxmox: ProxmoxInterface,
    node: str,
    vmid: int,
    order: dict[str, Any],
    image: dict[str, Any],
    storage_name: str,
    root_password: str,
) -> None:
    template_vmid, source_node = image.get("imagepath").split('|')

    clone_response = (
        proxmox.connection
        .nodes(source_node)
        .qemu(int(template_vmid))
        .clone
        .post(
            newid=vmid,
            name=order["systemName"],
            full=1,
            storage=storage_name,
            target=node,
        )
    )

    
    _wait_for_task(proxmox.connection, source_node, clone_response)

    resize_response = (
        proxmox.connection
        .nodes(node)
        .qemu(vmid)
        .resize
        .put(
            disk="scsi0",
            size=f"{int(order['storage'])}G",
        )
    )

    if resize_response:
        _wait_for_task(proxmox.connection, node, resize_response)

    proxmox.connection.nodes(node).qemu(vmid).config.post(
        name=order["systemName"],
        cores=int(order["cpu"]),
        memory=int(order["ram"]) * 1024,
        net0=_build_vm_network(order),
        ipconfig0=_build_cloud_init_ipconfig(order),
        nameserver=_get_nameserver(order),
        ciuser="root",
        cipassword=root_password,
        ciupgrade=1,
        ide2=f"{storage_name}:cloudinit",
        boot=f"order={disk_name}",
        agent="enabled=1",
        onboot=1,
        tags=_build_tags(order),
        description=genereate_documentation(order),
    )

    if order.get("start", True):
        _start_guest(proxmox.connection, node, vmid, "vm")
 
4. the image i used is from https://cloud-images.ubuntu.com/noble/current/ and ends on .img but is described by ubuntu as QCow2 UEFI/GPT Bootable disk image and allows editing by qcow2 standard too. But when I use the download from url function on my import storage in proxmox it forces the .raw ending.
That might be the issue. You would have to rename your image to .qcow2. In the future, it will be possible to select the format upon download. Patches are applied in git already, but not packaged yet.
 
I jump because using a template works. I might not want to use templates as it kinda locks me to specific nodes or forces me to share all templates on all nodes
My mistake, my reading of your initial post was you are using Proxmoxer to create a PVE template VM. It appears that these are two independent flows that you are trying.

Proxmoxer is 3rd party yes. But it is only a wrapper for the official api
The wrapper is only as good as the API request it creates. If it omits the parameters , or silently ignores the malformed parameters - the resulting API will not lead to expected result.

pvesh is a toolcall for my python code and that is not... increasing performance. And it is the minimum
before chasing performance it is best to get basic functionality working. Having a well understood pvesh call/sequence that creates the expected VM is a good proof that you have all the parameters, options, and values properly specified. You can then translate that to your wrapper. If specifying the same params/values provides a different result - that helps you zoom in on differences.

Oh and on that thought. Here is my from template code. That only has the issue of not setting the password
You use "template" here as a generic term, not PVE Template?

Is the configuration you provided in comment #5 the result of the API request generated by stanza in comment #6? If the answer is yes :
- You are using an image where password login for root is DISABLED. The default username for Ubuntu images is "ubuntu". You can NOT login as root.
- if you really want to login as root you must use custom CI with many more changes to VM's configuration
- In modern Ubuntu images you may not even be able to login as root with SSH key
- You might be able to login with the specified password as root on the console

If the configuration stanzas in comments #5 and #6 are not related, you need to provide matching configs to get assistance.


Blockbridge : Ultra low latency all-NVME shared storage for Proxmox - https://www.blockbridge.com/proxmox