Back to Articles
proxmoxcloud-initubuntu

Proxmox VE: Cloud-Init + `cicustom` ile Provisioning ve API ile Cloud Image’den Template Üretimi (Uçtan Uca)

Jan 18, 2026
7 min read

Amaç ve Akış

Hedefimiz:

  1. Cloud image (Ubuntu/Debian/Rocky/Alma vs.) indir
  2. Proxmox’ta VM oluştur
  3. Cloud image’i VM’e import et (disk)
  4. Cloud-Init diskini ekle (cloudinit drive)
  5. Cloud-Init ayarlarını (user/meta/network) cicustom ile snippets üzerinden ver
  6. VM’i template’e çevir
  7. Template’ten clone alırken API ile parametre basıp provisioning yap
  8. Troubleshooting + best practices

Ön Koşullar

  • Proxmox VE kurulu
  • Bir storage:
    • Disk import için: local-lvm / zfspool / ceph vb.
    • Snippets için: genelde local (dir storage) daha rahattır
  • Snippets aktif olmalı (çok önemli)

1.1) Snippets’ı Storage’da Aktifleştirme

Datacenter → Storage → local (veya kullandığın dir storage)
Content içinde Snippets işaretli olmalı.

CLI ile kontrol (örnek):

cat /etc/pve/storage.cfg

local altında şunu görmek istersin:

dir: local
  path /var/lib/vz
  content iso,vztmpl,backup,snippets

Snippets dosya yolu tipik olarak:

  • /var/lib/vz/snippets/

Cloud Image İndirme

Örnek: Ubuntu 22.04 cloud image:

cd /var/lib/vz/template/iso
wget -O ubuntu-22.04-server-cloudimg-amd64.img \
  https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img

Not: “iso” klasörüne koymak şart değil. Ama düzen için iyi.


Cloud Image’den Template Üretme (CLI ile)

Aşağıdaki örnek “golden template” üretir. Sonra her VM clone ile çıkar.

VM Oluştur

Örnek VMID: 9001

VMID=9001
qm create $VMID \
  --name ubuntu-2204-cloudinit \
  --memory 2048 \
  --cores 2 \
  --cpu host \
  --net0 virtio,bridge=vmbr0 \
  --scsihw virtio-scsi-pci

Cloud Image’i Import Et

Storage örneği: local-lvm

qm importdisk $VMID /var/lib/vz/template/iso/ubuntu-22.04-server-cloudimg-amd64.img local-lvm

Import sonrası disk ismi genelde vm-9001-disk-0 gibi olur.

Import Edilen Diski VM’e Bağla

qm set $VMID --scsi0 local-lvm:vm-$VMID-disk-0
qm set $VMID --boot order=scsi0

Cloud-Init Diski Ekle

qm set $VMID --ide2 local-lvm:cloudinit

Proxmox cloud-init için çoğunlukla ide2 kullanılır. (SCSI de olur ama ide2 yaygın.)

Seri Konsol + QEMU Guest Agent (Önerilir)

qm set $VMID --serial0 socket --vga serial0
qm set $VMID --agent enabled=1

QEMU agent’ın çalışması için guest OS içinde paket de gerekebilir (Ubuntu: qemu-guest-agent).

Template’e Çevirmeden Önce Genel Ayarlar

Proxmox’un native Cloud-Init alanlarını da kullanabilirsin:

qm set $VMID --ciuser burak --sshkeys /root/.ssh/authorized_keys
qm set $VMID --ipconfig0 ip=dhcp

Ama biz asıl gücü cicustom ile alacağız.

Template’e Çevir

qm template $VMID

Artık 9001 bir template.


cicustom ile Cloud-Init (Snippets) Kullanımı

Proxmox Cloud-Init üç dosya üzerinden yönetilebilir:

  • user-data → kullanıcı, paket, runcmd, write_files…
  • meta-data → instance-id, hostname…
  • network-data → netplan/network config

Proxmox, cicustom ile bu dosyaları snippet olarak göstermenize izin verir:

Format:

cicustom: user=<storage>:snippets/<user.yml>,meta=<storage>:snippets/<meta.yml>,network=<storage>:snippets/<net.yml>

Örnek:

cicustom: user=local:snippets/user-data.yml,meta=local:snippets/meta-data.yml,network=local:snippets/network-data.yml

Snippet Dosyalarını Oluşturma

Klasör:

mkdir -p /var/lib/vz/snippets

user-data.yml (örnek)

/var/lib/vz/snippets/ubuntu-user-data.yml

#cloud-config
hostname: "{{HOSTNAME}}"
manage_etc_hosts: true

users:
  - name: burak
    groups: [sudo]
    shell: /bin/bash
    sudo: ["ALL=(ALL) NOPASSWD:ALL"]
    ssh_authorized_keys:
      - "{{SSH_PUBLIC_KEY}}"

package_update: true
package_upgrade: true
packages:
  - qemu-guest-agent
  - curl
  - git

write_files:
  - path: /etc/motd
    content: |
      Provisioned by Proxmox Cloud-Init + cicustom

runcmd:
  - systemctl enable --now qemu-guest-agent
  - echo "cloud-init done" > /var/log/cloud-init-provisioning.ok

Buradaki {{...}} placeholder. Proxmox Cloud-Init doğrudan template engine değildir. Değişken basmak istiyorsan 2 yol var:

  • (A) API ile clone sonrası cicustom’u farklı snippet dosyasına pointing yap (her VM için ayrı snippet)
  • (B) Tek snippet + Proxmox’un --ciuser, --sshkeys, --ipconfig0 gibi alanlarıyla karışık kullan

meta-data.yml (örnek)

/var/lib/vz/snippets/ubuntu-meta-data.yml

instance-id: iid-local01
local-hostname: ubuntu-ci

network-data.yml (DHCP örnek)

/var/lib/vz/snippets/ubuntu-network-data.yml

version: 2
ethernets:
  ens18:
    dhcp4: true

Proxmox’ta virtio NIC genelde guest içinde ens18 olur (distroya göre değişebilir).

Template Üzerinde cicustom Tanımlama

Template VMID: 9001

qm set 9001 --cicustom "user=local:snippets/ubuntu-user-data.yml,meta=local:snippets/ubuntu-meta-data.yml,network=local:snippets/ubuntu-network-data.yml"

Cloud-init diskini yeniden üretmek için bazen “regenerate” gerekir:

qm cloudinit update 9001

Bazı sürümlerde bu komut yoksa sorun değil; clone sonrası zaten cloud-init drive yeniden üretilir.


Template’ten VM Clone Alma ve Provisioning

Basit Clone (CLI)

qm clone 9001 7878 --name testcloud --full 1 --target pve

Sonra cloud-init parametreleri:

qm set 7878 --ipconfig0 ip=dhcp
qm set 7878 --ciuser burak
qm set 7878 --sshkeys /root/.ssh/authorized_keys
qm start 7878

“Her VM’e Farklı Cloud-Init” (Snippets ile)

Her VM için ayrı snippet üret:

cat > /var/lib/vz/snippets/vm-7878-user-data.yml <<'YAML'
#cloud-config
hostname: vm-7878
users:
  - name: burak
    groups: [sudo]
    shell: /bin/bash
    sudo: ["ALL=(ALL) NOPASSWD:ALL"]
    ssh_authorized_keys:
      - "ssh-ed25519 AAAA... burak@mbp"
runcmd:
  - echo "hello from 7878" > /root/hello.txt
YAML

Sonra clone’da cicustom pointing değiştir:

qm set 7878 --cicustom "user=local:snippets/vm-7878-user-data.yml,meta=local:snippets/ubuntu-meta-data.yml,network=local:snippets/ubuntu-network-data.yml"
qm start 7878

Proxmox API ile Otomasyon (Template Üretimi + Clone + Config)

Aşağıdaki bölümler:

  • API Token ile auth
  • Cloud image’den template üretimi (API + bazı işlerde SSH/CLI gerekebilir)
  • Template clone + config + start

Auth: API Token Header

Genel format:

Authorization: PVEAPIToken=<user>@<realm>!<tokenid>=<secret>

Örnek (placeholder):

-H "Authorization: PVEAPIToken=root@pam!apiroot=<TOKEN_SECRET>"

zsh’de “event not found” (!) sorunu

zsh ! gördüğünde history expansion yapar. Çözümler:

  • Header’ı tek tırnak içine al:
-H 'Authorization: PVEAPIToken=root@pam!apiroot=<TOKEN_SECRET>'
  • Ya da komut öncesi history expansion kapat:
set +H

API ile Template’ten Clone (En Yaygın Workflow)

Clone Endpoint

curl -k -X POST "https://<PVE_HOST>:8006/api2/json/nodes/<NODE>/qemu/<TEMPLATE_VMID>/clone" \
  -H 'Authorization: PVEAPIToken=root@pam!apiroot=<TOKEN_SECRET>' \
  -d "newid=7878" \
  -d "name=testcloud" \
  -d "full=1" \
  -d "target=<NODE>"

Bu endpoint bir UPID döner. Task durumunu poll edebilirsin: /nodes/<NODE>/tasks/<UPID>/status

Clone Sonrası Config Güncelleme

Clone bittiğinde:

curl -k -X PUT "https://<PVE_HOST>:8006/api2/json/nodes/<NODE>/qemu/7878/config" \
  -H 'Authorization: PVEAPIToken=root@pam!apiroot=<TOKEN_SECRET>' \
  -d "ciuser=burak" \
  -d "ipconfig0=ip=dhcp" \
  --data-urlencode "sshkeys=$(cat /root/.ssh/authorized_keys)"

cicustom set etmek istersen:

curl -k -X PUT "https://<PVE_HOST>:8006/api2/json/nodes/<NODE>/qemu/7878/config" \
  -H 'Authorization: PVEAPIToken=root@pam!apiroot=<TOKEN_SECRET>' \
  --data-urlencode "cicustom=user=local:snippets/vm-7878-user-data.yml,meta=local:snippets/ubuntu-meta-data.yml,network=local:snippets/ubuntu-network-data.yml"

VM Start

curl -k -X POST "https://<PVE_HOST>:8006/api2/json/nodes/<NODE>/qemu/7878/status/start" \
  -H 'Authorization: PVEAPIToken=root@pam!apiroot=<TOKEN_SECRET>'

API ile “Cloud Image’den Template Üretimi” (Gerçekçi Yaklaşım)

Burada kritik nokta: Disk import (qm importdisk) gibi bazı işler Proxmox’ta en pratik CLI ile yapılır. API ile “tamamen sıfırdan” yapmak mümkün ama disk import aşaması çoğu zaman:

  • ya node üzerinde komut çalıştırmayı (SSH)
  • ya da storage’a uygun formatta önceden yüklemeyi gerektirir.

Bu yüzden production otomasyonda en temiz yaklaşım:

  1. API ile VM “create”
  2. Node’da (SSH/Ansible) qm importdisk + qm set (disk attach, ide2 cloudinit)
  3. API ile kalan config (cicustom, ciuser, ipconfig, sshkeys)
  4. qm template

API: VM Create

curl -k -X POST "https://<PVE_HOST>:8006/api2/json/nodes/<NODE>/qemu" \
  -H 'Authorization: PVEAPIToken=root@pam!apiroot=<TOKEN_SECRET>' \
  -d "vmid=9001" \
  -d "name=ubuntu-2204-cloudinit" \
  -d "memory=2048" \
  -d "cores=2" \
  -d "cpu=host" \
  -d "net0=virtio,bridge=vmbr0" \
  -d "scsihw=virtio-scsi-pci" \
  -d "serial0=socket" \
  -d "vga=serial0" \
  -d "agent=enabled=1"

8.2) Node Üzerinde Import + Attach (SSH/Ansible)

Node’da:

qm importdisk 9001 /var/lib/vz/template/iso/ubuntu-22.04-server-cloudimg-amd64.img local-lvm
qm set 9001 --scsi0 local-lvm:vm-9001-disk-0
qm set 9001 --ide2 local-lvm:cloudinit
qm set 9001 --boot order=scsi0

API: cicustom Set

curl -k -X PUT "https://<PVE_HOST>:8006/api2/json/nodes/<NODE>/qemu/9001/config" \
  -H 'Authorization: PVEAPIToken=root@pam!apiroot=<TOKEN_SECRET>' \
  --data-urlencode "cicustom=user=local:snippets/ubuntu-user-data.yml,meta=local:snippets/ubuntu-meta-data.yml,network=local:snippets/ubuntu-network-data.yml"

Template’e Çevirme

Template’e çevirme genelde CLI ile:

qm template 9001

API’de template flag’i set etmek mümkün olsa da pratikte qm template en stabil yoldur.


Cloud-Init İçerikleri: Örnek Şablonlar

Statik IP (Netplan) Örneği

/var/lib/vz/snippets/net-static.yml

version: 2
ethernets:
  ens18:
    dhcp4: false
    addresses:
      - 10.0.0.50/24
    gateway4: 10.0.0.1
    nameservers:
      addresses: [1.1.1.1, 8.8.8.8]

Docker Kurulumlu user-data Örneği

#cloud-config
packages:
  - ca-certificates
  - curl
  - gnupg

runcmd:
  - install -m 0755 -d /etc/apt/keyrings
  - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
  - chmod a+r /etc/apt/keyrings/docker.gpg
  - bash -lc 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list'
  - apt-get update
  - apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  - systemctl enable --now docker

İnternete erişim yoksa bu paket kurulumları fail eder. Offline repo gerekiyorsa farklı yaklaşım gerekir.


Sık Karşılaşılan Sorunlar ve Çözümler

ide2 ... cloudinit iki kez görünüyor

Bazen config update’leri yanlışlıkla iki kez basınca olur. Çözüm:

  • qm config <vmid> ile kontrol et
  • Fazlalığı kaldır (örnek: ide2’yi resetleyip tekrar set):
qm set 7878 --delete ide2
qm set 7878 --ide2 local-lvm:cloudinit

Cloud-Init çalışmıyor / değişiklikler yansımıyor

  • VM içinde kontrol:
cloud-init status --long
cloud-init query
tail -n 200 /var/log/cloud-init.log
tail -n 200 /var/log/cloud-init-output.log
  • Gerekirse reset:
cloud-init clean --logs
reboot

Hostname değişmiyor

Bazı distro’larda NetworkManager/hostnamectl davranışı farklı. user-data içinde hostname + manage_etc_hosts: true iyi pratik.

NIC adı ens18 değil

  • ip link ile doğru interface adını bul
  • network-data’da onu kullan

API ile sshkeys basarken bozuluyor

--data-urlencode kullan:

--data-urlencode "sshkeys=$(cat ~/.ssh/id_ed25519.pub)"

Best Practices (Production)

  • Template içinde mümkün olduğunca “genel” bırak:
    • Paket update/upgrade template boot’unda değil, VM provisioning’de yapılsın (isteğe bağlı)
  • Snippets dosyalarını:
    • snippets/templates/ubuntu-2204/ gibi klasörleyerek düzenle
  • Her VM için farklı user-data istiyorsan:
    • VMID bazlı snippet üret (ör. vm-<id>-user.yml)
    • Sonra API ile cicustom pointing değiştir
  • Secret’ları user-data içine plaintext gömmek yerine:
    • Vault/CyberArk/SSM benzeri sistemden runtime çek
    • veya cloud-init içinde “one-time token” yaklaşımı kullan

Minimal “Tam Otomasyon” Pseudocode (Akıl Haritası)

  1. (Node) cloud image indir / doğrula
  2. API: VM create (9001)
  3. SSH: qm importdisk + disk attach + cloudinit drive
  4. API: cicustom set
  5. SSH: qm template 9001
  6. API: clone (newid)
  7. API: config set (ciuser, ipconfig0, sshkeys, cicustom override)
  8. API: start
  9. (Ops) VM içinde cloud-init status health-check