---
description: >
  The Libvirt Builder is able to create Libvirt volumes on your libvirt hypervisor
  by copying an image from a http source, by using already existing volumes as a backing store,
  or creating an empty one and starting a libvirt domain with these volumes attached.
page_title: Libvirt - Builders
nav_title: Libvirt
---

# Libvirt

Type: `libvirt`
Artifact BuilderId: `thomasklein94.libvirt`

The Libvirt Builder is able to create Libvirt volumes on your libvirt hypervisor
by copying an image from a http source, by using already existing volumes as a backing store,
or creating an empty one and starting a libvirt domain with these volumes attached.

Libvirt builder also supports cloudinit images during build.

Before looking at the configuration options and examples, you might also want to check out and
familiarize yourself with [libvirt's domain specification](https://libvirt.org/formatdomain.html)
and [libvirt's storage pool and volume concepts](https://libvirt.org/formatstorage.html).

### Installation

To use the pre-built release of Packer Plugin Libvirt, you can add the following snippet to your Packer project:

```hcl
packer {
  required_plugins {
    libvirt = {
      version = ">= 0.5.0"
      source  = "github.com/thomasklein94/libvirt"
    }
  }
}
```

<!-- Builder Configuration Fields -->

### Required

@include 'builder/libvirt/Config-required.mdx'

### Optional

@include 'builder/libvirt/Config-not-required.mdx'

### Aliases
Libvirt offers a way to add one alias per device. Libvirt builder uses this to find out which volume should be the
artifact in case there are multiple volume configurations present. Finding the communicator interface is similar in fashion.
There are reasonable defaults built into the builder to ease onboarding but they will produce a warning and it's 
recommended to use aliases.

The identifier must consist only of the following characters: `[a-zA-Z0-9_-]`. Libvirt also requires the identifier 
to start with `ua-`, but libvirt builder will take care of that for you and prepend every alias with `ua-` before 
applying the domain definition to libvirt.

### Graceful shutdown
To ensure all changes were synced to the artifact volume,
after a **successful provisioning**, the Libvirt plugin will try to gracefully terminate the builder instance
by sending a shutdown command to libvirt and wait up to `shutdown_timeout` before forcefully destroys the domain.
Libvirt supports multiple way to shut down a domain, which can be controlled by the `shutdown_mode` attribute.

### Volumes

Libvirt uses volumes to attach as disks, to boot from and to persist data to. Libvirt Builder treats volumes as sources
and possible artifacts. Arbitrary number of volumes can be attached to a builder domain with specifying a `volume { }` 
block for each. The volume which will be the artifact of the build has to be marked with `alias = "artifact"`.

A volume defined with a source MUST NOT EXISTS BEFORE the build and WILL BE DESTROYED at the end of the build.
The only exception is when the volume marked as an artifact in which case a successful build prevents libvirt builder from 
deleting the volume.

If a volume does not have a source defined and does not marked as an artifact,
the volume must exists before the build, and will not be destroyed at the end of the build.

@include 'builder/libvirt/volume/Volume-not-required.mdx'

#### Backing-store volume source
Backing-store source instructs libvirt to use an already presented volume as a base for this volume.

@include 'builder/libvirt/volume/BackingStoreVolumeSource-not-required.mdx'

Example

```hcl
volume {
  target_dev = "sda"
  bus        = "sata"

  pool = "default"
  name = "custom-image"

  source {
    type = "backing-store"

    pool   = "base-images"
    volume = "ubuntu-22.04-lts"
  }
  capacity = "20G"
}
```

#### Cloning a volume
If you wish to clone a volume instead of using a backing store overlay described above,
You have the option to use the `cloning` source type.

@include 'builder/libvirt/volume/CloningVolumeSource-not-required.mdx'

Example

```hcl
volume {
  target_dev = "sda"
  bus        = "sata"

  pool = "default"
  name = "custom-image"

  source {
    type = "cloning"

    pool   = "base-images"
    volume = "ubuntu-22.04-lts"
  }
}
```

#### Cloud-init volume source
Linux domains running in a libvirt environment can be set up with the 
[NoCloud cloud-init datasource](https://cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html).
This means a small ISO image with the label `CIDATA` will be assembled at the machine running packer and will be uploaded to the volume.
To assemble an ISO image, one of the following commands must be installed at the machine running packer and must be available 
in the `PATH`: `xorriso`, `mkisofs`, `hdiutil` or `oscdimg`.

For cloud-init backed sources, the size and capacity attributes can be omitted for the volume.

@include 'builder/libvirt/volume/CloudInitSource-not-required.mdx'

Example:

```hcl
volume {
  target_dev = "sdb"
  bus        = "sata"
  source {
    type = "cloud-init"

    meta_data = jsonencode({
      "instance-id" = "i-abcdefghijklm"
      "hostname"    = "my-packer-builder"
    })

    user_data =  format("#cloud-config\n%s", jsonencode({
      packages = [
            "qemu-guest-agent",
        ]
        runcmd = [
            ["systemctl", "enable", "--now", "qemu-guest-agent"],
        ]
        ssh_authorized_keys = [
            data.sshkey.install.public_key,
        ]
    }))

    network_config = jsonencode({
      version = 2
        ethernets = {
            eth = {
                match = {
                    name = "en*"
                }
                dhcp4 = true
            }
        }
    })
  }
}
```

#### External volume source

@include 'builder/libvirt/volume/ExternalVolumeSource.mdx'
@include 'builder/libvirt/volume/ExternalVolumeSource-not-required.mdx'

Example:

```hcl
volume {
  alias = "artifact"

  pool = "base-images"
  name = "ubuntu-22.04-lts"

  source {
    type     = "external"
    urls     = ["https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64-disk-kvm.img"]
    checksum = "2a0d8745dee674a7f3038d627d39ff68e5d7276f97866a4abd9ebfcb3df5fa05"
  }

  format = "qcow2"

  capacity = "1G"

  bus        = "sata"
  target_dev = "sda"
}

```

#### Files volume source

@include 'builder/libvirt/volume/FilesVolumeSource.mdx'
@include 'builder/libvirt/volume/FilesVolumeSource-not-required.mdx'

Examples:

* Create a floppy disk for unattended Windows installation *
```hcl
volume {
  alias = "unattend"

  device = "floppy"

  source {
    type = "files"
    label = "unattend"
    contents = {
      "Unattend.xml" = templatefile("${path.root}/Unattend.xml.tmpl", {ComputerName = "MyMachine"})
    }
  }
}
```

* Create a CDROM for drivers stored locally
```hcl
volume {
  alias = "drivers"
  device = "cdrom"

  source {
    type = "files"
    label = "drivers"
    files = ["./drivers/*"]
  }
}
```

### Network
Network interfaces can be attached to a builder domain by adding a `network_interface { }` block for each.
Currently only `managed` and `bridge` networks are supported.

@include 'builder/libvirt/network/NetworkInterface-required.mdx'
@include 'builder/libvirt/network/NetworkInterface-not-required.mdx'

#### Communicators and network interfaces
If an SSH or WinRM address is specified in the communicator block, packer will use that address to communicate with the
builder domain.

If no SSH or WinRM address is specified, the libvirt builder tries to find an address for initiating communications.
If only one network interface is specified for a builder domain, and there is no `communicator_interface` specified for
the domain, then that interface will be used as the interface for communication.

If more than one network interface is specified, the communicator interface must be tagged by setting the same alias as 
specified with the `communicator_interface` domain attribute (which is "communicator" by default.)

The `network_address_source` domain attribute controls how the address discovery will take place. There are three 
possible way to discover a domain's virtual address:
- `agent`: this is the most reliable method for getting an address for the domain, but it requires `qemu-guest-agent`
           running in the domain. This is the default method for domains.
- `lease`: if for some reason, the guest agent can not be started, but the communicator interface is connected to
           one of libvirt's managed networks, you can use `lease` to see what DHCP lease was offered for the interface.
- `arp`: this method relies on the libvirt host ARP table to find an IP address associated with the MAC address given to
         the domain.

Examples

For connecting to a managed network
```hcl
network_interface {
  type    = "managed"
  network = "my-managed-network"
}
```

For connecting to a bridge
```hcl
network_interface {
  type   = "bridge"
  bridge = "br0"
}
```

### Graphics and video, headless domains
Libvirt builder creates a headless domain by default with no video card or monitor attached to it. Most linux distributions
and cloud images are fine with this setup, but you might need to add a video card and a graphical interface to your machine.

You can add one (or more) graphic device to your machine by using the `graphics { }` block.
If at least one graphic device is added to the builder configuration, a video device with the model `virtio` will
automatically be added to the domain.
Currently, there is no option to specify or customize just a video device for a domain.

@include 'builder/libvirt/DomainGraphic-required.mdx'
@include 'builder/libvirt/SdlDomainGraphic-not-required.mdx'
@include 'builder/libvirt/VNCDomainGraphic-not-required.mdx'

### Debugging a build
By default, Libvirt builder assigns two serial console to the domain with the aliases `serial-console` and `virtual-console`.
You can use your virtual manager to connect to one of these consoles for debug.

If you don't have access to a virtual machine manager, if specifying the `-debug` flag and `PACKER_LOG=1` environment
variable to packer simultaneously while setting the `PACKER_LIBVIRT_STREAM_CONSOLE` to one of the console aliases, 
the builder will connect to that console and logs any message the domain sends to that console.
In no way, shape or form should this be used in any production or serious setup!

### A simple builder definition example

The following example will download and then upload an ubuntu cloud image to the default storage pool, then starts a
libvirt domain accessing the default libvirt network. Additionally, the example will create an SSH key and add 
a cloud-init image to the domain in order to enable Packer to connect to the domain and run provisioners.

```hcl
packer {
  required_plugins {
    sshkey = {
      version = ">= 1.0.1"
      source = "github.com/ivoronin/sshkey"
    }
    libvirt = {
      version = ">= 0.5.0"
      source  = "github.com/thomasklein94/libvirt"
    }
  }
}

data "sshkey" "install" {
}

source "libvirt" "example" {
  libvirt_uri = "qemu:///system"

  vcpu   = 1
  memory = 846

  network_interface {
    type  = "managed"
    alias = "communicator"
  }

  # https://developer.hashicorp.com/packer/plugins/builders/libvirt#communicators-and-network-interfaces
  communicator {
    communicator         = "ssh"
    ssh_username         = "ubuntu"
    ssh_private_key_file = data.sshkey.install.private_key_path
  }
  network_address_source = "lease"

  volume {
    alias = "artifact"

    source {
      type     = "external"
      # With newer releases, the URL and the checksum can change.
      urls     = ["https://cloud-images.ubuntu.com/releases/22.04/release-20230302/ubuntu-22.04-server-cloudimg-amd64-disk-kvm.img"]
      checksum = "3b11d66d8211a8c48ed9a727b9a74180ac11cd8118d4f7f25fc7d1e4a148eddc"
    }

    capacity   = "10G"
    target_dev = "sda"
    bus        = "sata"
    format     = "qcow2"
  }

  volume {
    source {
      type = "cloud-init"
      user_data = format("#cloud-config\n%s", jsonencode({
        ssh_authorized_keys = [
          data.sshkey.install.public_key,
        ]
      }))

    }

    target_dev = "sdb"
    bus        = "sata"
  }
  shutdown_mode = "acpi"
}

build {
  sources = ["source.libvirt.example"]
  provisioner "shell" {
    inline = [
      "echo The domain has started and became accessible",
      "echo The domain has the following addresses",
      "ip -br a",
      "echo if you want to connect via SSH use the following key: ${data.sshkey.install.private_key_path}",
    ]
  }
  provisioner "breakpoint" {
    note = "You can examine the created domain with virt-manager, virsh or via SSH"
  }
}
```
