New to Kestra?
Use blueprints to kickstart your first workflows.
Automate a recurring KVM/libvirt maintenance window in Kestra: verify the domain, graceful stop with force fallback, snapshot its disk, restart, and alert ops.
Automate a safe, recurring off-hours maintenance window for a KVM (libvirt) virtual machine without hand-running virsh at 2am. This blueprint orchestrates the full domain lifecycle around a maintenance action: it inventories the hypervisor, verifies the target VM exists, gracefully shuts it down with a hard power-off fallback, takes an external disk snapshot and checks the qcow2 image health while the domain is offline, boots it back up, and emails the operations team. A flow-level error handler guarantees that a run which leaves a server down is never silent. It solves the classic problem of unattended VM patching, snapshotting, and disk maintenance on bare libvirt hosts where the hypervisor has no built-in scheduler, retries, or alerting.
inventory runs io.kestra.plugin.kvm.ListVms to capture the current domain list as an audit record before anything changes.assert_target_present uses io.kestra.plugin.core.execution.Fail with a condition that checks target_vm against the inventory, aborting before any shutdown if the domain cannot be verified.stop_window is a io.kestra.plugin.core.flow.Sequential that runs graceful_stop (io.kestra.plugin.kvm.StopVm, force: false, waitForStopped: true). If ACPI shutdown stalls, the step's local errors handler (force_stop) runs a second StopVm with force: true to hard power-off the domain.run_maintenance is a io.kestra.plugin.scripts.shell.Commands task on the io.kestra.plugin.core.runner.Process runner that takes a virsh snapshot-create-as --disk-only --atomic snapshot named from the schedule date, then reports qemu-img info for each disk.restart_vm runs io.kestra.plugin.kvm.StartVm with waitForRunning: true to bring the domain back and confirm it reaches RUNNING.notify_complete sends an io.kestra.plugin.email.MailSend summary with the final state.weekly_window io.kestra.plugin.core.trigger.Schedule fires every Sunday at 02:00 UTC, and a flow-level errors block emails ops on any failure.concurrency.limit: 1 so windows never overlap.A bare libvirt host has no scheduler, no retry logic, no conditional branching, and no alerting. Kestra adds all of it declaratively in YAML: the Schedule trigger replaces fragile cron-and-bash, per-task retry blocks absorb transient hypervisor hiccups, the stop_window step's local errors handler turns a stuck graceful stop into a force stop instead of a hung window, and the flow-level errors block guarantees ops is paged when a VM might be left down. Execution outputs give you lineage across the stop, maintain, and restart steps, and concurrency.limit: 1 prevents overlapping windows: guarantees virsh and qemu cannot give you on their own.
qemu+ssh://root@your-host/system or qemu:///system.target_vm) already defined on the hypervisor.virsh and qemu-img against the host (this flow uses the Process runner).LIBVIRT_URI: the libvirt connection URI used by the KVM tasks and the maintenance script.SMTP_HOST: the SMTP server hostname used to send the emails.SMTP_USERNAME: the SMTP account username for authentication.SMTP_PASSWORD: the SMTP account password for authentication.LIBVIRT_URI, SMTP_HOST, SMTP_USERNAME, and SMTP_PASSWORD secrets.target_vm to the domain you want to patch, and adjust the from/to addresses in pluginDefaults.run_maintenance commands with your real maintenance steps.weekly_window schedule trigger (defaults to Sunday 02:00 UTC).target_vm to an ARRAY input and wrap stop/maintain/restart in a ForEach with per-iteration error handling to patch a fleet.pause or approval task before the stop for change-controlled windows.