New to Kestra?
Use blueprints to kickstart your first workflows.
Watch a KVM libvirt domain and auto-restart it on SHUTOFF with flap protection, maintenance-aware skips, and loud failure alerts, all in Kestra.
This blueprint turns Kestra into a self-healing watchdog for a critical KVM (libvirt) virtual machine. A polling trigger checks the domain state once a minute and only fires when the VM is SHUTOFF, so a healthy host never floods your execution history with no-op runs. When the VM does go down unexpectedly, the flow decides whether recovery is safe, restarts the domain, and notifies Slack. It avoids the two classic self-healing anti-patterns: silent no-op spam and blindly rebooting a crash-looping VM forever. Keywords: KVM monitoring, libvirt auto-restart, VM self-healing, flap protection, infrastructure orchestration.
watch_vm trigger (io.kestra.plugin.kvm.VmEventTrigger) polls the domain every PT1M. An io.kestra.plugin.core.condition.Expression gates execution creation on {{ trigger.state == 'SHUTOFF' }}, so a healthy VM produces zero executions.read_maintenance_flag and read_restart_counter (io.kestra.plugin.core.kv.Get, both errorOnMissing: false) load per-VM state from the KV store: an optional maintenance flag and the restart count in the current window.decide (io.kestra.plugin.core.flow.If) recovers only when the maintenance flag is not true AND the count is below max_restarts_per_window.bump_restart_counter (io.kestra.plugin.core.kv.Set, kvType: NUMBER) increments the counter with a ttl of flap_window, recover_vm (io.kestra.plugin.kvm.StartVm) boots the domain with waitForRunning: true, and alert_recovered posts a Slack summary.classify either logs a maintenance skip or, on the restart ceiling, posts a <!channel> escalation and deliberately does NOT restart.errors block (alert_recovery_failed) pages on-call if the restart itself fails.The libvirt uri lives in pluginDefaults keyed on io.kestra.plugin.kvm, every remote call carries a retry, and concurrency.limit: 1 keeps a single recovery acting on the VM at a time.
A bare libvirt host has no native scheduler that can poll state, count restarts across a window, branch on a maintenance flag, and escalate to Slack. Kestra adds an event-style polling trigger, declarative YAML logic, per-task retry, a built-in KV store for stateful flap counting with TTL, concurrency control, and full execution lineage. The KV store turns a stateless restart into a rate-limited, self-resetting recovery loop, something a cron job or a watchdog script cannot do cleanly.
qemu+ssh://root@your-host/system or qemu:///system.monitored_vm) already defined on the host.LIBVIRT_URI: the libvirt connection URI used by the trigger and the StartVm recovery task.SLACK_WEBHOOK: the Slack Incoming Webhook URL used for recovery, escalation, and failure alerts.LIBVIRT_URI and SLACK_WEBHOOK secrets to your Kestra instance.monitored_vm input to the domain you want watched.max_restarts_per_window and flap_window to your flap tolerance.interval if you need faster or slower polling.maintenance_<monitored_vm> to true with io.kestra.plugin.core.kv.Set, then clear it afterward.recover_vm, since RUNNING is a libvirt state, not a guest-ready signal.ForEach over a VM list, or swap Slack for PagerDuty or email on the escalation path.