Skip to main content

Painless VM management from the CLI

Project description

Minivirt

VMs should be easy.

Discord

Minivirt is a lightweight QEMU manager that provides a Docker-like user experience. The default image is based on Alpine Linux, which is tiny and fast: 50MB compressed disk image, boots to SSH in second(s).

Installation

  1. Install QEMU and other dependencies.

    • MacOS: brew install qemu socat
    • Debian: apt install qemu-kvm qemu-utils qemu-efi-aarch64 socat
    • Alpine: apk add py3-pip qemu qemu-system-x86_64 qemu-img socat tar
    • Arch: pacman -S python-pip qemu-base socat
  2. Install Minivirt and run a checkup.

    pip3 install minivirt
    miv doctor
    
  3. Pull an image and start a VM.

    miv remote add default https://f003.backblazeb2.com/file/minivirt
    miv pull default alpine-{arch} alpine  # {arch} is automatically replaced with your architecture.
    miv run alpine
    

The miv run command will create an ephemeral VM and open an SSH session into it. When you exit the session, the VM is destroyed.

Under the hood

The actual work of emulating virtual machines is done by QEMU. It runs in many environments, which means we can provide (mostly) the same features everywhere.

Virtual machines run as user processes, no root privileges necessary. The user does however need permissions for hardware virtualization (e.g. access to /dev/kvm on Linux).

It's possible to interact with the VM in three ways:

  • Serial console: this is the default for miv start.
  • Graphical display: enabled by the --display argument.
  • SSH: miv run connects through SSH, using the Vagrant well-known SSH key. Also, miv ssh can shell into a running VM.

The QEMU VM is set up with User Networking, which doesn't interfere with the host's network stack, and the guest SSH port is forwarded to a random port on localhost. You can forward more ports with the --port option.

Minivirt manages images, which are essentially read-only, reusable virtual machine qcow2 disks; and VMs, with their own copy-on-write disk, which uses the image disk as its backing file. Everything is stored in ~/.cache/minivirt/.

Doctor

The miv doctor command runs a checkup to help with troubleshooting. It checks to see if qemu-system-{arch}, qemu-img, socat and tar are installed, and if /dev/kvm is usable.

Persistent VMs

Create a VM with the create command:

miv create alpine myvm

Start the VM with the terminal attached to its serial console:

miv start myvm

Gracefully stop the VM by sending an ACPI poweroff:

miv stop myvm

Destroy the VM to remove its disk image and other resources:

miv destroy myvm

Inspect the VMs:

miv ps
miv ps -a  # also shows stopped VMs

Graphics

Start the VM in the background and connect a display to it:

miv create alpine myvm
miv start myvm --daemon --display

Log in as root, and run:

setup-xorg-base
apk add xfce4 xfce4-terminal dbus
startx

To make the screen bigger, right-click on the desktop, hover on Applications, then Settings, and click Display. Select another resolution like "1440x900" and click "apply".

Images

Minivirt maintains a database of images identified by their SHA256 checksum. They may have any number of tags.

Show images in the database:

% miv images
5446f671 1.4G ubuntu-22.04
84200bbd 115M alpine-3.15
8ad24d9f 1.4G ubuntu-20.04
c86a9115 114M alpine alpine-3.16

Building an image

Minivirt can build images from recipes, which are YAML files, with a syntax inspired by GitHub Actions workflows. Download any file from the /recipes directory and run:

miv build alpine-3.16.yaml --tag alpine -v

The -v flag directs the output of the build (serial console or SSH) to stdout.

The image is now in the database:

miv run alpine

Other image operations

Commit a VM as an image:

miv commit myvm myimage

Save the image as a TAR archive:

miv save myimage | gzip -1 > myimage.tgz

Later, load the image:

zcat myimage.tgz | miv load myimage

Database maintenance

To make sure the images and VMs are consistent, run a database check:

miv fsck

To remove an image, first untag it. This only removes the tag, not the image itself.

miv untag myimage

The image is removed during prune:

miv prune

Image repositories

Add a remote repository:

miv remote add default https://f003.backblazeb2.com/file/minivirt

Pull an image. {arch} will be interpolated to the machine architecture.

miv pull default alpine-{arch} alpine

To host an image repository, you need an object store (e.g. Amazon S3, Backblaze B2, MinIO, etc). Set the following environment variables:

  • AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY: authentication credentials.
  • AWS_ENDPOINT_URL (optional): if the object store is not hosted on the AWS public cloud, this should point to the appropriate endpoint.

The bucket name is taken from the last part of the remote's URL, e.g. minivirt for the default repository.

Run miv push to upload an image:

miv push default alpine-3.16 alpine-3.16-aarch64

Development

  1. Clone the repository:

    git clone https://github.com/mgax/minivirt
    cd minivirt
    
  2. Create a virtualenv so you don't interfere with gobally-installed packages:

    python3 -m venv .venv
    source .venv/bin/activate
    
  3. Install the repo in edit mode and development dependencies:

    pip install -e '.[devel]'
    
  4. Run the test suite:

    pytest
    pytest --runslow  # if you're not in a hurry
    

Python API

Minivirt is written in Python and offers a straightforward API:

from minivirt.cli import db

alpine = db.get_image('alpine')
myvm = VM.create(db, 'myvm', image=alpine, memory=512)
with myvm.run(wait_for_ssh=30):
    print(myvm.ssh('uname -a', capture=True))

GitHub Actions self-hosted runners

Minivirt comes with a server that launches GitHub Actions runners when a workflow job is queued. Each runner is ephemeral and runs in its own VM.

  1. Install extra dependencies:

    pip install -e minivirt[githubactions]
    
  2. Build an actions runner image:

    miv build recipes/alpine-3.15.yaml --tag alpine-3.15 -v
    miv build recipes/ci-alpine.yaml --tag ci-alpine -v
    miv build recipes/githubactions-alpine.yaml --tag githubactions-alpine -v
    
  3. Run the server. To interact with the GitHub API, it needs a GitHub PAT, and runs git credentials fill to retrieve it. It uses ngrok to listen for webhook events; to avoid the ngrok session timing out, set a token in the NGROK_AUTH_TOKEN environment variable.

    miv -v githubactions serve githubactions-alpine {repo}
    

Get in touch

For feedback, support, and contributions, visit:

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

minivirt-0.1.tar.gz (24.8 kB view hashes)

Uploaded Source

Built Distribution

minivirt-0.1-py3-none-any.whl (24.7 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page