Introduction to Multipass

Ubuntu Virtual Machines (VMs) On-Demand

Screenshot of Multipass homepage | Image by the Author

multipass is a Command Line Interface (CLI) tool for the quick deployment of Virtual Machines (VMs) dominated by Ubuntu but other Operating Systems and applications are available. With a general trend of migrating away from VMs to containers (with the exception of microvms like Firecracker) why would we need another VM management tool?

It’s all about the developer experience. My focus is often on Ubuntu based Open Container Initiative (OCI) or docker format containers, but how do you determine the commands to put into either a ContainerFile or DockerFile ? Often, this is achieved by running on a local host either natively (if running Linux), in a VM or integrated VM like Windows Subsystem for Linux (WSL). Multipass promises to abstract away all the manual steps for creating a VM on-the-fly — from testing out that curl | bash command or compiling something from source:

So multipass offers a better environment for honing your container file commands, building software in isolation or testing untested tools with a significantly easier interface than typical VMs. In a Linux environment the VMs are constructed with Kernel-based Virtual Machine (KVM) but that comes with traditional penalties such as disk use and allocated memory — some of which can be mitigated with advanced features described below.

Data Science Tools by Ashraf Miah | Image by the Author

The purpose of the article is to provide an introduction to multipass .

Installation

Install Snap

The primary method of installation is using the snap store:

# Install vis snap
sudo snap install multipass
sudo: snap: command not found

Unless you are running Ubuntu, its unlikely you have Snapcraft installed. Follow the Snapcraft instructions to install on your operating system:

# Install snap (if Ubuntu derived distro)
sudo apt update && sudo apt install snapd
<snip>Selecting previously unselected package snapd.
Preparing to unpack .../snapd_2.55.3+22.04ubuntu1_amd64.deb ...
Unpacking snapd (2.55.3+22.04ubuntu1) ...
Setting up squashfs-tools (1:4.5-3build1) ...
Setting up snapd (2.55.3+22.04ubuntu1) ...
Created symlink /etc/systemd/system/multi-user.target.wants/snapd.apparmor.service → /lib/systemd/system/snapd.apparmor.service.
Created symlink /etc/systemd/system/multi-user.target.wants/snapd.autoimport.service → /lib/systemd/system/snapd.autoimport.service.
Created symlink /etc/systemd/system/multi-user.target.wants/snapd.core-fixup.service → /lib/systemd/system/snapd.core-fixup.service.
Created symlink /etc/systemd/system/multi-user.target.wants/snapd.recovery-chooser-trigger.service → /lib/systemd/system/snapd.recovery-chooser-trigger.service.
Created symlink /etc/systemd/system/multi-user.target.wants/snapd.seeded.service → /lib/systemd/system/snapd.seeded.service.
Created symlink /etc/systemd/system/cloud-final.service.wants/snapd.seeded.service → /lib/systemd/system/snapd.seeded.service.
Unit /lib/systemd/system/snapd.seeded.service is added as a dependency to a non-existent unit cloud-final.service.
Created symlink /etc/systemd/system/multi-user.target.wants/snapd.service → /lib/systemd/system/snapd.service.
Created symlink /etc/systemd/system/timers.target.wants/snapd.snap-repair.timer → /lib/systemd/system/snapd.snap-repair.timer.
Created symlink /etc/systemd/system/sockets.target.wants/snapd.socket → /lib/systemd/system/snapd.socket.
Created symlink /etc/systemd/system/final.target.wants/snapd.system-shutdown.service → /lib/systemd/system/snapd.system-shutdown.service.
snapd.failure.service is a disabled or a static unit, not starting it.
snapd.snap-repair.service is a disabled or a static unit, not starting it.
Processing triggers for gnome-menus (3.36.0-1ubuntu3) ...
Processing triggers for man-db (2.10.2-1) ...
Processing triggers for dbus (1.12.20-2ubuntu4) ...
Processing triggers for dbus-broker (29-4build1) ...
Processing triggers for mailcap (3.70+nmu1ubuntu1) ...
Processing triggers for desktop-file-utils (0.26-1ubuntu3) ...

Test the snap store is working with example snap:

# Install hello-world snap
sudo snap install hello-world
2022-06-02T13:45:20+01:00 INFO Waiting for automatic snapd restart...
hello-world 6.4 from Canonical✓ installed
# List snaps
snap list
Name Version Rev Tracking Publisher Notes
core 16-2.55.5 13250 latest/stable canonical✓ core
hello-world 6.4 29 latest/stable canonical✓ -
# Test application
hello-world
Hello World!# Check path of binary
which hello-world
/snap/bin/hello-world

Install Multipass

# Install multipass
sudo snap install multipass
multipass 1.9.2 from Canonical✓ installed# List snaps
Name Version Rev Tracking Publisher Notes
core 16-2.55.5 13250 latest/stable canonical✓ core
core20 20220512 1494 latest/stable canonical✓ base
hello-world 6.4 29 latest/stable canonical✓ -
multipass 1.9.2 7174 latest/stable canonical✓ -
# Check version
multipass --version
multipass 1.9.2
multipassd 1.9.2

Multipass Usage

List available Ubuntu specific appliances / images:

multipass findImage                       Aliases           Version          Description
snapcraft:core18 18.04 20201111 Snapcraft builder for Core 18
snapcraft:core20 20.04 20210921 Snapcraft builder for Core 20
snapcraft:core22 22.04 20220426 Snapcraft builder for Core 22
snapcraft:devel 20220602 Snapcraft builder for the devel series
core core16 20200818 Ubuntu Core 16
core18 20211124 Ubuntu Core 18
18.04 bionic 20220523 Ubuntu 18.04 LTS
20.04 focal,lts 20220530 Ubuntu 20.04 LTS
21.10 impish 20220309 Ubuntu 21.10
22.04 jammy 20220531 Ubuntu 22.04 LTS
daily:22.10 devel,kinetic 20220601.1 Ubuntu 22.10
appliance:adguard-home 20200812 Ubuntu AdGuard Home Appliance
appliance:mosquitto 20200812 Ubuntu Mosquitto Appliance
appliance:nextcloud 20200812 Ubuntu Nextcloud Appliance
appliance:openhab 20200812 Ubuntu openHAB Home Appliance
appliance:plexmediaserver 20200812 Ubuntu Plex Media Server Appliance
anbox-cloud-appliance latest Anbox Cloud Appliance
charm-dev latest A development and testing environment for charmers
docker latest A Docker environment with Portainer and related tools
minikube latest minikube is local Kubernetes

Going to use a basic VM for software compilation:

# Launch a 20.04 image
multipass launch 20.04
Launched: wealthy-lungfish# List images
multipass list
Name State IPv4 Image
wealthy-lungfish Running 10.137.0.110 Ubuntu 20.04 LTS
# Check version
mp exec wealthy-lungfish -- lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.4 LTS
Release: 20.04
Codename: focal

Practical Example: Compile ijq

Access the instance using the shell command:

# Connect to instance
multipass shell wealthy-lungfish
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-113-generic x86_64)* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Thu Jun 2 14:07:00 BST 2022System load: 0.01 Processes: 105
Usage of /: 28.9% of 4.67GB Users logged in: 0
Memory usage: 20% IPv4 address for ens3: 10.137.0.110
Swap usage: 0%
1 update can be applied immediately.
To see these additional updates run: apt list --upgradable
Last login: Thu Jun 2 14:04:49 2022 from 10.137.0.1
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
ubuntu@wealthy-lungfish:~$

The excellent ijq CLI tool is used to interactively explore json files and provide the string for jq .

Prepare the environment:

# Install make for building
sudo apt install make
Reading package lists... Done
Building dependency tree
Reading state information... Done
Suggested packages:
make-doc
The following NEW packages will be installed:
make
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 162 kB of archives.
After this operation, 393 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu focal/main amd64 make amd64 4.2.1-1.2 [162 kB]
Fetched 162 kB in 1s (318 kB/s)
Selecting previously unselected package make.
(Reading database ... 63807 files and directories currently installed.)
Preparing to unpack .../make_4.2.1-1.2_amd64.deb ...
Unpacking make (4.2.1-1.2) ...
Setting up make (4.2.1-1.2) ...
Processing triggers for man-db (2.9.1-1) ...
# Install C compiler
sudo apt install gcc
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
binutils binutils-common binutils-x86-64-linux-gnu cpp cpp-9 gcc-9 gcc-9-base libasan5 libatomic1
libbinutils libc-dev-bin libc6-dev libcc1-0 libcrypt-dev libctf-nobfd0 libctf0 libgcc-9-dev libgomp1
libisl22 libitm1 liblsan0 libmpc3 libquadmath0 libtsan0 libubsan1 linux-libc-dev manpages-dev
Suggested packages:
binutils-doc cpp-doc gcc-9-locales gcc-multilib autoconf automake libtool flex bison gdb gcc-doc
gcc-9-multilib gcc-9-doc glibc-doc
The following NEW packages will be installed:
binutils binutils-common binutils-x86-64-linux-gnu cpp cpp-9 gcc gcc-9 gcc-9-base libasan5 libatomic1
libbinutils libc-dev-bin libc6-dev libcc1-0 libcrypt-dev libctf-nobfd0 libctf0 libgcc-9-dev libgomp1
libisl22 libitm1 liblsan0 libmpc3 libquadmath0 libtsan0 libubsan1 linux-libc-dev manpages-dev
0 upgraded, 28 newly installed, 0 to remove and 0 not upgraded.
Need to get 34.0 MB of archives.
After this operation, 150 MB of additional disk space will be used.
Do you want to continue? [Y/n]

Ensure build dependencies are in place by downloading and installing go :

# Create directory for go
mkdir -p ~/source/go && cd ~/source/go
# Download copy of source files
wget https://go.dev/dl/go1.18.3.linux-amd64.tar.gz
--2022-06-02 14:20:42-- https://go.dev/dl/go1.18.3.linux-amd64.tar.gz
Resolving go.dev (go.dev)... 216.239.34.21, 216.239.32.21, 216.239.38.21, ...
Connecting to go.dev (go.dev)|216.239.34.21|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://dl.google.com/go/go1.18.3.linux-amd64.tar.gz [following]
--2022-06-02 14:20:43-- https://dl.google.com/go/go1.18.3.linux-amd64.tar.gz
Resolving dl.google.com (dl.google.com)... 142.250.187.206, 2a00:1450:4009:817::200e
Connecting to dl.google.com (dl.google.com)|142.250.187.206|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 141748419 (135M) [application/x-gzip]
Saving to: ‘go1.18.3.linux-amd64.tar.gz’
go1.18.3.linux-amd64.tar.g 100%[=====================================>] 135.18M 10.5MB/s in 16s2022-06-02 14:20:59 (8.31 MB/s) - ‘go1.18.3.linux-amd64.tar.gz’ saved [141748419/141748419]# Extract and install (note the additional sudo)
rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.18.3.linux-amd64.tar.gz
# Add go binary to PATH
export PATH=$PATH:/usr/local/go/bin
# Either restart shell or source PATH variable and then test
go version
go version go1.18.3 linux/amd64

Download and configure scdoc used for the man pages:

# Create directory
mkdir -p ~/source/scdoc && cd ~/source/scdoc
# Download source
wget https://git.sr.ht/~sircmpwn/scdoc/archive/1.11.2.tar.gz
--2022-06-02 14:27:06-- https://git.sr.ht/~sircmpwn/scdoc/archive/1.11.2.tar.gz
Resolving git.sr.ht (git.sr.ht)... 173.195.146.142
Connecting to git.sr.ht (git.sr.ht)|173.195.146.142|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [application/tar+gzip]
Saving to: ‘1.11.2.tar.gz’
1.11.2.tar.gz [ <=> ] 12.45K --.-KB/s in 0.001s2022-06-02 14:27:07 (18.9 MB/s) - ‘1.11.2.tar.gz’ saved [12746]# Untar and enter directory
tar xzf 1.11.2.tar.gz && cd scdoc-1.11.2
# Compile scdoc
make
cc -std=c99 -pedantic -c -o .build/main.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/main.c
cc -std=c99 -pedantic -c -o .build/string.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/string.c
cc -std=c99 -pedantic -c -o .build/utf8_chsize.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/utf8_chsize.c
cc -std=c99 -pedantic -c -o .build/utf8_decode.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/utf8_decode.c
cc -std=c99 -pedantic -c -o .build/utf8_encode.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/utf8_encode.c
cc -std=c99 -pedantic -c -o .build/utf8_fgetch.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/utf8_fgetch.c
cc -std=c99 -pedantic -c -o .build/utf8_fputch.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/utf8_fputch.c
cc -std=c99 -pedantic -c -o .build/utf8_size.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/utf8_size.c
cc -std=c99 -pedantic -c -o .build/util.o -g -DVERSION='"1.11.2"' -Wall -Wextra -Werror -Wno-unused-parameter -Iinclude src/util.c
cc -static -o scdoc .build/main.o .build/string.o .build/utf8_chsize.o .build/utf8_decode.o .build/utf8_encode.o .build/utf8_fgetch.o .build/utf8_fputch.o .build/utf8_size.o .build/util.o
./scdoc < scdoc.1.scd > scdoc.1
./scdoc < scdoc.5.scd > scdoc.5
sed -e 's:@prefix@:/usr/local:g' -e 's:@version@:1.11.2:g' < scdoc.pc.in > scdoc.pc
# Install
sudo make install
mkdir -p //usr/local/bin //usr/local/share/man/man1 //usr/local/share/man/man5 //usr/local/share/pkgconfig
install -m755 scdoc //usr/local/bin/scdoc
install -m644 scdoc.1 //usr/local/share/man/man1/scdoc.1
install -m644 scdoc.5 //usr/local/share/man/man5/scdoc.5
install -m644 scdoc.pc //usr/local/share/pkgconfig/scdoc.pc
# Check installation
scdoc -h
Usage: scdoc < input.scd > output.roff

Prepare compilation of ijq :

# Create directory for source code
mkdir -p ~/source/ijq && cd ~/source/ijq
# Download copy of source files
wget https://git.sr.ht/~gpanders/ijq/archive/v0.3.8.tar.gz
--2022-06-02 14:11:18-- https://git.sr.ht/~gpanders/ijq/archive/v0.3.8.tar.gz
Resolving git.sr.ht (git.sr.ht)... 173.195.146.142
Connecting to git.sr.ht (git.sr.ht)|173.195.146.142|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [application/tar+gzip]
Saving to: ‘v0.3.8.tar.gz’
v0.3.8.tar.gz [ <=> ] 23.18K --.-KB/s in 0.001s2022-06-02 14:11:18 (26.2 MB/s) - ‘v0.3.8.tar.gz’ saved [23739]# Untar and change directory
tar xzvf v0.3.8.tar.gz && cd ijq-v0.3.8
# Compile
make
go build -ldflags="-s -w -X main.Version=0.3.8" -o ijq
go: downloading github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1
go: downloading github.com/kyoh86/xdg v1.2.0
go: downloading github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8
go: downloading golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
go: downloading github.com/gdamore/encoding v1.0.0
go: downloading github.com/lucasb-eyer/go-colorful v1.2.0
go: downloading github.com/mattn/go-runewidth v0.0.13
go: downloading golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1
go: downloading golang.org/x/text v0.3.6
go: downloading github.com/rivo/uniseg v0.2.0
scdoc < ijq.1.scd > ijq.1
# Install
sudo make install
install -d /usr/local/bin /usr/local/share/man/man1
install -m 0755 ijq /usr/local/bin
install -m 0644 ijq.1 /usr/local/share/man/man1
# Check version
ijq -V
ijq 0.3.8

As a check we should compare our binary with that of the author:

# Create location for original
mkdir ~/source/ijq/original && cd ~/source/ijq/original
# Get binary
https://git.sr.ht/~gpanders/ijq/refs/download/v0.3.8/ijq-0.3.8-linux-amd64.tar.gz
# Extract
tar xzvf ijq-0.3.8-linux-amd64.tar.gz && cd ijq-0.3.8
ijq-0.3.8/
ijq-0.3.8/COPYING
ijq-0.3.8/ijq.1
ijq-0.3.8/ijq
# Generate SHA256 for original
5c3695bf344f39fc300671811a34a26d6770427c532fd339ff737b70aa02bef5 ijq
# Generate SHA256 for compiled
cfbc281735765458623a8621a2506f1d9777821c829dc10700823cfe9b2b01be ijq

They are different and we’re unable to determine why as the build environment the author used isn’t specified. Nonetheless, we have demonstrated how quickly a Ubuntu VM can be launched and accessed, in this instance for software compilation.

Advanced Usage

Managing a VM is relatively straight forward; more advanced features include:

  • Support for cloud-init
  • Customisable VMs in terms of CPUs and RAM
  • Remote access to multipass — use more powerful / dedicated hardware for the VMs

The first feature is designed to support the development of cloud-init files prior to usein the cloud. The second feature provides more control of the VM capability and the latter allows team use on dedicated hardware, without the usual remote access challenges.

Summary

Supposing we wanted to explore a different go library for compilation or other changes, then installing these applications and dependencies on the same host would created significant issues. The problem can also be solved with other VM or container tools, so what makes multipass different? It’s the ease of use, where a single command gives you a working installation, the VMs can be networked to different networking scenarios as well as integrating into the host. For example, multipass comes with its own mount command to mount a host folder in the VM itself.

Ultimately, its biased towards users of Ubuntu rather than other Linux distributions. While there are many VM tools there are no real alternatives to how well polished multipass is in providing a seamless experience.

Attribution

All gists , notebooks and terminal casts are by the author. All of the artwork is based on assets explicitly CC0, Public Domain license or SIL OFL and is therefore non-infringing. Theme is inspired by and based on my favourite vim theme: Gruvbox.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Ashraf Miah

Ashraf Miah

Data Scientist and Chartered Aeronautical Engineer (MEng CEng EUR ING MRAeS) with over 15 years experience in the Aerospace, Defence and Rail Industry.