Skip to content

Proxmox Debian Cloud-Init

After spending way too much time figuring out how to get Cloud-Init working propertly with Proxmox and Debian, I decided to write this post. This post will cover how to create a Debian Cloud-Init template in Proxmox and how to deploy a new VM with it.

Because most of my Debian VMs run Docker, the template will also install Docker.

Creating a Debian Cloud-Init template

Download the Debian Cloud-Init image from the Debian Cloud repository:

wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2

Then create a new VM in Proxmox and import the image:

template-create.sh
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

# VM ID of the template
VM_ID=9000
# VM name of the template
VM_NAME=debian-ci-template
# Path to the downloaded Debian Cloud-Init image
DISK_IMAGE=/root/debian-12-genericcloud-amd64.qcow2
# Path to the Cloud-Init configuration file
CLOUD_INIT_FILE=local:snippets/debian-cloud-init.yaml
CLOUD_INIT_USER=user
# Name of your Proxmox network bridge
NETWORK_BRIDGE=vmbr0

qm create "${VM_ID}" \
  --name "${VM_NAME}" \
  --memory 2048 \
  --cores 2 \
  --net0 "virtio,bridge=${NETWORK_BRIDGE},firewall=1" \
  --ostype l26 \
  --vga serial0 \
  --serial0 socket \
  --scsihw virtio-scsi-single \
  --ide0 local-lvm:cloudinit \
  --virtio0 "local-lvm:0,import-from=${DISK_IMAGE},cache=writeback,iothread=1" \
  --boot order=virtio0 \
  --ipconfig0 ip=dhcp \
  --ciuser "${CLOUD_INIT_USER}" \
  --cicustom "vendor=${CLOUD_INIT_FILE}" \
  --agent 1

We use VirtIO SCSI single because:

A SCSI controller of type VirtIO SCSI single and enabling the IO Thread setting for the attached disks is recommended if you aim for performance. This is the default for newly created Linux VMs since Proxmox VE 7.3. Each disk will have its own VirtIO SCSI controller, and QEMU will handle the disks IO in a dedicated thread. Linux distributions have support for this controller since 2012, and FreeBSD since 2014. For Windows OSes, you need to provide an extra ISO containing the drivers during the installation.

Getting the Docker GPG key

wget https://download.docker.com/linux/debian/gpg -O docker.gpg.pub.key
gpg --show-keys docker.gpg.pub.key

Deploying a new VM

template-deploy.sh
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

VM_ID="${1:-}"
VM_NAME="${2:-}"
FROM="${3:-}"

if [[ -z "${VM_ID}" || -z "${VM_NAME}" ]]; then
  echo "Usage: $0 <vm-id> <vm-name> [<from-vm-id>]"
  exit 1
fi

TEMPLATE_VM_ID="${FROM:-9000}"
CLOUD_INIT_FILE=local:snippets/debian-cloud-init.yaml
GITHUB_USERNAME="bram-pkg"
DISK_SIZE=32G

qm clone "${TEMPLATE_VM_ID}" "${VM_ID}" --name "${VM_NAME}"
qm set "${VM_ID}" \
  --cicustom "vendor=${CLOUD_INIT_FILE}" \
  --sshkeys <(curl -sSL "https://github.com/${GITHUB_USERNAME}.keys")
qm disk resize "${VM_ID}" virtio0 "${DISK_SIZE}"
qm start "${VM_ID}"

until qm guest cmd "${VM_ID}" ping &> /dev/null; do
  echo "Waiting for VM to report IP"
  sleep 1
done

qm guest cmd "${VM_ID}" network-get-interfaces

Then log in to the VM and check if the packages are installed:

ssh root@<ip>
tail -f /var/log/cloud-init-output.log

Destroying a VM

qm stop 100 && qm destroy 100

Cloud-Init configuration

debian-cloud-init.yaml
#cloud-config
# vim:ft=yaml

write_files:
  - path: /home/user/.bash_aliases
    owner: user:user
    permissions: "0644"
    content: |
      alias dcb="docker compose build"
      alias dcdn="docker compose down"
      alias dce="docker compose exec"
      alias dcl="docker compose logs"
      alias dclf="docker compose logs -f"
      alias dcps="docker compose ps"
      alias dcpull="docker compose pull"
      alias dcrestart="docker compose restart"
      alias dcupd="docker compose up -d"
    defer: true
  - path: /etc/apt/keyrings/docker.asc
    owner: root:root
    permissions: "0644"
    content: |
      -----BEGIN PGP PUBLIC KEY BLOCK-----

      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
      YT90qFF93M3v01BbxP+EIY2/9tiIPbrd
      =0YYh
      -----END PGP PUBLIC KEY BLOCK-----

apt:
  sources:
    docker:
      source: deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $RELEASE stable

packages:
  - ca-certificates
  - curl
  - docker-ce
  - docker-buildx-plugin
  - docker-compose-plugin
  - qemu-guest-agent

runcmd:
  - systemctl enable --now qemu-guest-agent
  - curl -sL https://get.beszel.dev -o /tmp/install-agent.sh && chmod +x /tmp/install-agent.sh && /tmp/install-agent.sh -p 45876 -k "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOgHoGAW0FJbWwEAmlWQcMTi9Up7B/9Td8VLFGBco0+6" --auto-update
  - sed -i 's/^# *\(en_US.UTF-8\)/\1/' /etc/locale.gen
  - locale-gen
  - usermod --append --groups docker user