VM creator that works purely from bash.


  • apt-get install libvirt-bin libguestfs-tools debootstrap virtinst python-xmltodict

  • You're using libvirt to run VMs. (Actually, that's not strictly necessary, only if you want to autostart the VMs or auto-pacemakerize them.)

  • If you're intending to use a Ceph pool to store your images, the pool needs to be configured as a libvirt storage pool (and it needs to be running).


  • VM images have a size of 15GB, partitioned using LVM.

    • 5GB root fs (ext4)
    • 2GB /var/log (ext4)
    • 8GB unassigned -- create your data LVs here.
  • Supports Debian Jessie and Ubuntu Xenial.

  • Defaults to building Debian guests when run on Debian and Ubuntu guests when run on Ubuntu (overrideable using --os).

  • Builds strictly use debootstrap. No golden images or iso installations needed.

  • While building, the image is strictly isolated from the host through libguestfs. That means, it will never ever ever show up in your host system as a loop device or through device mapper. Everything works through a single FUSE mount.

  • VMs are self-contained and fully equipped with a boot loader. No host kernel booting needed.

  • The initial debootstrap archive is cached by default and not downloaded every time.

  • The target VM image can reside in a Ceph pool.

  • VMs can be automatically registered with libvirt. In that case, they're also automatically started.

  • Network configuration is handled through a bash function that you can override in the settings. The function will receive the IP address passed in the -i argument and then return gateway, netmask and DNS settings accordingly. See the example below on how to do this.

  • Keyboard layout is de:nodeadkeys. (Non-overrideable, but I'll accept pull requests in that direction.)

  • Ubuntu's insane 5 minute boot delay when no network is available is reconfigured to 10 seconds.

  • The Puppet agent can automatically be included in the image and started on first boot. That way, you can easily provision the VM after vmaker is done with the initial setup.

  • VMs can also be automatically registered with Pacemaker.

Usage examples:

  1. Build a plain VM image, no registering and/or autobooting, using DHCP for IP:

    ./ -f /var/lib/libvirt/images/shinyvm1.img -n shinyvm1
  2. Build a plain VM image, no registering and/or autobooting, with a static IP:

    ./ -f /var/lib/libvirt/images/shinyvm2.img -n shinyvm2 -i
  3. Build a VM image using DHCP for IP and register it with libvirt:

    ./ -f /var/lib/libvirt/images/shinyvm3.img -n shinyvm3 --virt-install
  4. Build a VM image with puppet using DHCP for IP and register it with libvirt:

    ./ -f /var/lib/libvirt/images/shinyvm4.img -n shinyvm4 --puppet --virt-install
  5. Build a VM image stored in a Ceph pool with a static IP and register it with libvirt:

    root@pevh010:~/vmaker# virsh pool-dumpxml peha
    <pool type='rbd'>
      <capacity unit='bytes'>3297748451328</capacity>
      <allocation unit='bytes'>180128346714</allocation>
      <available unit='bytes'>1956380938240</available>
        <host name='' port='6789'/>
        <host name='' port='6789'/>
        <host name='' port='6789'/>
        <auth type='ceph' username='libvirt'>
          <secret uuid='121954cf-84bc-4dee-92f9-59ca3f4668de'/>
    root@pevh010:~/vmaker# ./ -f rbd:peha/peha070-n1.img -n peha070-n1 -i --puppet --virt-install

Example settings for multiple networks:

This is a example for when you need to support multiple target networks with different settings:

get_network () {
    echo "NETWORK_METHOD=static"

    echo "NETWORK_DOMAIN=local.lan"
    echo "NETWORK_NETMASK=24"

    if [ "`echo $IPADDR | cut -d. -f1-3`" = 192.168.0 ]; then
    elif [ "`echo $IPADDR | cut -d. -f1-3`" = 10.5.0 ]; then


This way, the bridge and gateway will be adapted automatically according to the IP the VM is configured with.

Known bugs and limitations:

  • Only tested thoroughly with Ubuntu. I (sadly) use the Debian version less regularly.

  • Network config adaptation only works with static IPs (there's no --network option, so the IP is the only thing you can pass in (that is, you can always use env vars)).

  • The root password is hardcoded as "init123", and root is the only login available. I use puppet to set a new password and import keys and stuff.

Further reading: