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
|
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
| Command | Description |
|---|
--scsihw virtio-scsi-single | Configure SCSI controller |
--boot c --bootdisk scsi0 | Set boot order |
--scsi2 ...:cloudinit | Add Cloud-Init disk |
--agent enabled=1 | Enable qemu-guest-agent |
--serial0 socket | Serial console |
--vga serial0 | VGA output to serial0 |
--cpu cputype=host | CPU type |
--ostype l26 | Linux 2.6+ kernel |
--balloon | Memory ballooning |
--ciupgrade 1 | Cloud-Init upgrade |
--ipconfig0 ip=dhcp | DHCP for network |
--nameserver | DNS server |
--searchdomain | Search domain |
10. Mark as Template
Finally, mark the VM as a template:
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:
| Module | Description |
|---|
users | Create user accounts |
packages | Install packages |
package_update | Update package list |
package_upgrade | Upgrade packages |
runcmd | Execute commands on first boot |
write_files | Write files |
timezone | Set timezone |
locale | Set locale |
ssh_authorized_keys | SSH keys for users |
ssh_pwauth | SSH 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"
|