Setting Up Proxmox Cloud-Init

A guide to setting up Cloud-Init in Proxmox for automatic VM configuration.

Cloud-Init is the de facto standard for automatically configuring virtual machines in cloud environments. In this article, I’ll show you how to create a Cloud-Init template in Proxmox and automatically deploy VMs with it.

What is Cloud-Init?

Cloud-Init is a tool that runs during the first boot of a virtual machine to automatically configure the system. This includes:

  • Setting up user accounts and SSH keys
  • Network configuration
  • Package repository configuration
  • System customizations

Prerequisites

Before we begin, make sure the following packages are installed:

1
apt install libguestfs-tools wget

Step-by-Step Guide

1. Download the Image

First, download the cloud image:

1
2
3
mkdir -p /opt/cloud_init
cd /opt/cloud_init
wget https://cloud.debian.org/images/cloud/trixie/latest/debian-13-generic-amd64.qcow2

2. Install qemu-guest-agent

The qemu-guest-agent enables communication between Proxmox and the VM:

1
virt-customize -a debian-13-generic-amd64.qcow2 --install qemu-guest-agent

3. Set Root Password

1
virt-customize -a debian-13-generic-amd64.qcow2 --root-password password:your-password

4. Clear /etc/machine-id

This is important for Cloud-Init:

1
virt-customize -a debian-13-generic-amd64.qcow2 --run-command "echo -n > /etc/machine-id"

5. Add SSH Keys

1
2
3
4
5
6
7
8
virt-customize -a debian-13-generic-amd64.qcow2 \
  --run-command "mkdir -p /root/.ssh && chmod 700 /root/.ssh" \
  --run-command "cat >> /root/.ssh/authorized_keys << 'SSHEOF'
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAAAAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx user@host-1
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAAAAyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy user@host-2
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAAAAzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz user@host-3
SSHEOF" \
  --run-command "chmod 600 /root/.ssh/authorized_keys"

6. Enable SSH Root Login (optional)

1
2
virt-customize -a debian-13-generic-amd64.qcow2 \
  --run-command "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config"

7. Create VM

Now create the VM in Proxmox:

1
2
3
4
5
qm create 9999 \
  --name debian-13 \
  --memory 2048 \
  --cores 4 \
  --net0 virtio,bridge=vmbr1

8. Import Disk

1
qm importdisk 9999 debian-13-generic-amd64.qcow2 local-zfs

9. Configure VM

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
qm set 9999 --scsihw virtio-scsi-single --scsi0 "local-zfs:vm-9999-disk-0,cache=writeback,discard=on,ssd=1"
qm set 9999 --boot c --bootdisk scsi0
qm set 9999 --scsi2 local-zfs:cloudinit
qm set 9999 --agent enabled=1
qm set 9999 --serial0 socket
qm set 9999 --vga serial0
qm set 9999 --cpu cputype=host
qm set 9999 --ostype l26
qm set 9999 --balloon 2048
qm set 9999 --ciupgrade 1
qm set 9999 --ipconfig0 ip=dhcp
qm set 9999 --nameserver 192.168.1.1
qm set 9999 --searchdomain localdomain

Explanation of qm set Commands

CommandDescription
--scsihw virtio-scsi-singleConfigure SCSI controller
--boot c --bootdisk scsi0Set boot order
--scsi2 ...:cloudinitAdd Cloud-Init disk
--agent enabled=1Enable qemu-guest-agent
--serial0 socketSerial console
--vga serial0VGA output to serial0
--cpu cputype=hostCPU type
--ostype l26Linux 2.6+ kernel
--balloonMemory ballooning
--ciupgrade 1Cloud-Init upgrade
--ipconfig0 ip=dhcpDHCP for network
--nameserverDNS server
--searchdomainSearch domain

10. Mark as Template

Finally, mark the VM as a template:

1
qm template 9999

Adjusting Cloud-Init Configuration

To start a VM from the template, you can adjust the Cloud-Init configuration:

1
2
qm set 9999 --ipconfig0 ip=192.168.1.100/24,gw=192.168.1.1
qm set 9999 --nameserver 8.8.8.8

Static IP Address

You can also configure static IP addresses with Cloud-Init:

1
qm set 9999 --ipconfig0 ip=192.168.1.100/24,gw=192.168.1.1

Cloud-Init Configuration Files

Cloud-Init uses two main configuration files stored on a separate drive (CIDATA):

user-data

The user-data file contains the user configuration. Create an ISO file with both files:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
mkdir -p /tmp/cloud-init-iso
cd /tmp/cloud-init-iso

# Create user-data
cat > user-data << 'EOF'
#cloud-config
users:
  - name: admin
    sudo: ALL=(ALL) NOPASSWD:ALL
    groups: sudo
    shell: /bin/bash
    ssh-authorized-keys:
      - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAAAAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx user@host-1
      - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAAAAyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy user@host-2
      - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAAAAzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz user@host-3

package_update: true
packages:
  - vim
  - curl
  - wget
  - git

runcmd:
  - systemctl enable sshd
  - timedatectl set-timezone Europe/Berlin
EOF

# Create meta-data
cat > meta-data << 'EOF'
instance-id: debian-13-vm
local-hostname: debian-13-vm
EOF

# Create ISO
genisoimage -output cloud-init.iso -volid cidata -rock -joliet meta-data user-data

Cloud-Init Modules Explained

Cloud-Init supports many modules. The most important ones are:

ModuleDescription
usersCreate user accounts
packagesInstall packages
package_updateUpdate package list
package_upgradeUpgrade packages
runcmdExecute commands on first boot
write_filesWrite files
timezoneSet timezone
localeSet locale
ssh_authorized_keysSSH keys for users
ssh_pwauthSSH password authentication

Creating Users with sudo Rights

By default, the default user (admin) can already use sudo. For additional users:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
users:
  - name: admin
    sudo: ALL=(ALL) NOPASSWD:ALL
    groups: sudo,adm
    shell: /bin/bash
    ssh-authorized-keys:
      - ssh-ed25519 AAAAC3N...
  - name: monitoring
    sudo: ALL=(ALL) NOPASSWD:ALL
    groups: sudo
    shell: /bin/bash

Installing Packages

To install packages on first boot:

1
2
3
4
5
6
7
packages:
  - vim
  - curl
  - wget
  - git
  - htop
  - net-tools

Configuring Package Repositories

To add custom package repositories:

1
2
3
4
5
write_files:
  - path: /etc/apt/sources.list.d/custom.list
    content: |
      deb https://repo.example.com/debian stable main
    permissions: "0644"

Or for a local mirror:

1
2
3
4
5
6
write_files:
  - path: /etc/apt/sources.list
    content: |
      deb http://192.168.1.10/debian bookworm main contrib
      deb http://192.168.1.10/debian bookworm-updates main contrib
    permissions: "0644"

Mounting Cloud-Init ISO in Proxmox

After creating the ISO, you can use it as a Cloud-Init drive:

1
2
3
4
5
# Copy ISO to Proxmox server
scp cloud-init.iso root@proxmox:/tmp/

# Configure as Cloud-Init drive
qm set 9999 --ide2 local:iso/cloud-init.iso,media=cdrom

Automatic Configuration on Clone

When cloning a VM from a template, you can adjust the Cloud-Init configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Clone VM
qm clone 9999 100 --name debian-13-new

# Set Cloud-Init configuration
qm set 100 --ipconfig0 ip=192.168.1.200/24,gw=192.168.1.1
qm set 100 --nameserver 192.168.1.1
qm set 100 --searchdomain localdomain

# SSH keys for new user
qm set 100 --sshkeys "ssh-ed25519 AAAAC3N..."

Network Configuration

For more complex network configurations, you can use network-config:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
cat > network-config << 'EOF'
version: 2
ethernets:
  ens18:
    addresses:
      - 192.168.1.100/24
    gateway4: 192.168.1.1
    nameservers:
      addresses:
        - 192.168.1.1
        - 8.8.8.8
      search:
        - localdomain
EOF

Conclusion

With Cloud-Init, you can quickly and automatically deploy VMs in Proxmox. The template only needs to be created once, and then you can clone as many VMs as you want from it and start them with individual configuration.

The main advantages are:

  • Automated VM deployment
  • Centralized configuration via Cloud-Init
  • Reusable templates
  • Flexible network configuration

Complete Script

Here is the complete script for automatically creating a Cloud-Init template:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#!/bin/bash

WORK_DIR="/opt/cloud_init"
IMAGE_URL="https://cloud.debian.org/images/cloud/trixie/latest/debian-13-generic-amd64.qcow2"
IMAGE_NAME="debian-13-generic-amd64.qcow2"
VM_ID="9999"
VM_NAME="debian-13"
VM_MEMORY="2048"
VM_CORES="4"
VM_BRIDGE="vmbr1"
STORAGE_LOCAL="local-zfs"
ROOT_PASSWORD="xxx"
NAMESERVER="192.168.1.1"
SEARCHDOMAIN="localdomain"
ENABLE_ROOT_SSH="true"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

error() {
    echo "[ERROR] $1" >&2
    exit 1
}

log "Starte Cloud-Init Template Erstellung..."

mkdir -p "$WORK_DIR" || error "Konnte Verzeichnis nicht erstellen"
cd "$WORK_DIR" || error "Konnte Verzeichnis nicht wechseln"

if [ ! -f "$IMAGE_NAME" ]; then
    log "Lade Cloud-Image herunter: $IMAGE_URL"
    wget "$IMAGE_URL" || error "Download fehlgeschlagen"
else
    log "Image existiert bereits: $IMAGE_NAME"
fi

log "Installiere qemu-guest-agent..."
virt-customize -a "$IMAGE_NAME" --install qemu-guest-agent || error "qemu-guest-agent Installation fehlgeschlagen"

log "Setze root-Passwort..."
virt-customize -a "$IMAGE_NAME" --root-password password:"$ROOT_PASSWORD" || error "Passwort-Setzung fehlgeschlagen"

log "Leere /etc/machine-id..."
virt-customize -a "$IMAGE_NAME" --run-command "echo -n > /etc/machine-id" || error "machine-id Leerung fehlgeschlagen"

log "Füge SSH-Keys zu root hinzu..."
virt-customize -a "$IMAGE_NAME" \
  --run-command "mkdir -p /root/.ssh && chmod 700 /root/.ssh" \
  --run-command "cat >> /root/.ssh/authorized_keys << 'SSHEOF'
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAAAAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx user@host-1
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAAAAyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy user@host-2
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAAAAzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz user@host-3
SSHEOF" \
  --run-command "chmod 600 /root/.ssh/authorized_keys" || error "SSH-Keys hinzufügen fehlgeschlagen"

if [ "$ENABLE_ROOT_SSH" = "true" ]; then
    log "Aktiviere SSH Root-Login..."
    virt-customize -a "$IMAGE_NAME" \
      --run-command "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config" || error "SSH-Konfiguration fehlgeschlagen"
fi

log "Erstelle VM mit ID $VM_ID..."
qm create "$VM_ID" \
  --name "$VM_NAME" \
  --memory "$VM_MEMORY" \
  --cores "$VM_CORES" \
  --net0 virtio,bridge="$VM_BRIDGE" || error "VM-Erstellung fehlgeschlagen"

log "Importiere Disk..."
qm importdisk "$VM_ID" "$IMAGE_NAME" "$STORAGE_LOCAL" || error "Disk-Import fehlgeschlagen"

log "Konfiguriere VM..."
qm set "$VM_ID" --scsihw virtio-scsi-single --scsi0 "$STORAGE_LOCAL:vm-$VM_ID-disk-0,cache=writeback,discard=on,ssd=1"
qm set "$VM_ID" --boot c --bootdisk scsi0
qm set "$VM_ID" --scsi2 "$STORAGE_LOCAL:cloudinit"
qm set "$VM_ID" --agent enabled=1
qm set "$VM_ID" --serial0 socket
qm set "$VM_ID" --vga serial0
qm set "$VM_ID" --cpu cputype=host
qm set "$VM_ID" --ostype l26
qm set "$VM_ID" --balloon "$VM_MEMORY"
qm set "$VM_ID" --ciupgrade 1
qm set "$VM_ID" --ipconfig0 ip=dhcp
qm set "$VM_ID" --nameserver "$NAMESERVER"
qm set "$VM_ID" --searchdomain "$SEARCHDOMAIN"

log "Markiere VM als Template..."
qm template "$VM_ID" || error "Template-Markierung fehlgeschlagen"

log "✓ Cloud-Init Template erfolgreich erstellt!"
log "Template ID: $VM_ID"
log "Template Name: $VM_NAME"