New to Kestra?
Use blueprints to kickstart your first workflows.
Clone a golden image, define and boot a libvirt KVM domain, wait for RUNNING, and alert Slack, with retries and idempotency in Kestra.
Provision a KVM virtual machine on demand with libvirt, without hand-running virsh
over SSH or maintaining brittle shell scripts. This flow clones a read-only golden
qcow2 image into a fresh per-VM boot disk, renders a libvirt domain XML template from
your inputs, defines the domain on the target hypervisor, boots it, blocks until
libvirt reports RUNNING, and posts the VM details to Slack. It solves the repeatable
KVM provisioning problem: turning ad hoc, manual hypervisor steps into a versioned,
retry-safe, idempotent workflow you can trigger by hand, on a schedule, or from an API.
clone_boot_disk (io.kestra.plugin.scripts.shell.Commands on the
io.kestra.plugin.core.runner.Process runner) runs virsh vol-clone to create the
per-VM boot disk from the golden base_volume, then virsh vol-resize to grow it
to disk_size_gb. It is idempotent: if the target volume already exists it is reused.create_vm (io.kestra.plugin.kvm.CreateVm with startAfterCreate: true) defines
the domain from the rendered xmlDefinition and boots it. It is idempotent by name,
so re-running keeps the definition in sync and the boot is a no-op if already running.wait_for_running (io.kestra.plugin.kvm.StartVm with waitForRunning: true and
timeToWait: PT2M) blocks until the domain reaches RUNNING.notify_provisioned (io.kestra.plugin.slack.notifications.SlackIncomingWebhook)
posts a Slack Block Kit message with the VM name, UUID, state, and resources.A flow-level errors block (alert_failure) posts a Slack alert on failure, every
remote task carries a retry, the libvirt uri is shared via pluginDefaults, and
concurrency.limit: 1 stops two runs from racing on the same domain name.
libvirt has no native scheduler, no retry logic, and no notification layer: it just
executes virsh commands when you run them. Kestra fills that gap. You get event and
schedule triggers (run on a cron, or from an API/webhook call), automatic retry on
transient SSH or hypervisor hiccups, full execution lineage and logs for every provision,
and a declarative YAML definition you can version and review. Cross-tool steps like the
Slack notification and the failure errors handler live in the same flow instead of in
a separate cron job.
qemu+ssh://root@your-host/system
for remote SSH or qemu:///system for a local hypervisor.base_volume present in the libvirt default pool.default libvirt virtual network for the VM NIC.virsh against the host (the Process runner uses the
worker's virsh and LIBVIRT_DEFAULT_URI).LIBVIRT_URI: the full libvirt connection URI used by every KVM task and the clone
step (for example qemu+ssh://root@10.0.0.10/system).SLACK_WEBHOOK: the Slack Incoming Webhook URL used by the success and failure alerts.LIBVIRT_URI and SLACK_WEBHOOK secrets to your Kestra instance.base_volume exists in the default pool.vm_name, memory_mib, vcpus, os_volume, and
disk_size_gb as needed.Schedule trigger to provision burst capacity, or a webhook trigger for
self-service requests.