Run a tutorial with Vagrant
You can use Vagrant to run a tutorial inside one (or more) virtual machines. This approach is slow and resource intensive, but ensures maximum isolation and allows you to write and test tutorials that you would not want to run on the local system.
Getting started
Assuming you have Vagrant installed, getting started is very simple.
First, let’s write a trivial tutorial file and tell structured-tutorials to use the Vagrant runner:
configuration:
run:
runner:
path: structured_tutorials.runners.vagrant.VagrantRunner
parts:
- commands:
- command: ls
Then, in same folder as your tutorial.yaml, place a simple Vagrantfile:
Vagrant.configure("2") do |config|
config.vm.box = "hashicorp-education/ubuntu-24-04"
config.vm.box_version = "0.1.0"
end
You can now run your tutorial in Vagrant:
user@host:~$ structured-tutorial tutorial.yaml
Configure Vagrant environment
You can configure environment variables used by Vagrant invocation by using the environment option.
The most common example is to configure a different VAGRANT_CWD. The default is the same directory as your tutorial YAML file. For example, if you have the Vagrantfile in a subdirectory:
configuration:
run:
runner:
path: structured_tutorials.runners.vagrant.VagrantRunner
options:
environment:
VAGRANT_CWD: subdir
parts:
- commands:
- command: ls
Vagrant.configure("2") do |config|
config.vm.box = "hashicorp-education/ubuntu-24-04"
config.vm.box_version = "0.1.0"
end
Use multiple VMs
Vagrant allows you to define multiple VMs in a single Vagrantfile. structured-tutorials allows you to specify in which machine a particular part should run in via the options directive:
configuration:
run:
runner:
path: structured_tutorials.runners.vagrant.VagrantRunner
parts:
- commands:
- command: echo foo
run:
options:
machine: foo
- commands:
- command: echo bar
run:
options:
machine: bar
In this example the Vagrantfile specifies two VMs:
Vagrant.configure("2") do |config|
config.vm.define "foo" do |foo|
foo.vm.box = "hashicorp-education/ubuntu-24-04"
end
config.vm.define "bar" do |bar|
bar.vm.box = "hashicorp-education/ubuntu-24-04"
end
end
The first commands block will run in the foo VM:
user@host:~$ echo foo
… while the second block will run in the bar VM:
user@host:~$ echo bar
Prepare a custom base box
Sometimes you need to prepare a custom base box for the main tutorial. Known use cases are preparing a box that already has some tools installed or changing the VM so that it connects using a different user.
The following tutorial is a contrived example showing a user how to set up PostgreSQL on a “db” host and Nginx on a “web” host. VMs are started from a custom-built “base” box that already has some common tools installed and allows SSH access as root:
configuration:
run:
runner:
path: structured_tutorials.runners.vagrant.VagrantRunner
options:
# Prepare a custom box as base for boxes in the main Vagrantfile:
prepare_box:
path: box/ # where the base box Vagrantfile is
name: prepared-box
parts:
- commands:
# Works, because default user of prepared box is now root
- command: apt-get install -y nginx
run:
options:
machine: web
- commands:
- command: apt-get install -y postgresql
run:
options:
machine: web
As per the prepare_box directive, we have to specify a Vagrantfile for the base box in the box/ folder:
Vagrant.configure("2") do |config|
config.vm.box = "hashicorp-education/ubuntu-24-04"
# Do not update SSH key with generated one, provisioning script copies it to /root to allow SSH access.
config.ssh.insert_key = false
# provision base box
config.vm.provision "shell", name: "provision", path: "provision.sh"
end
And the provisioning script in the same location:
#!/bin/sh -ex
# Provisioning script for the base box:
# We want to have some tools on every VM.
apt-get update
apt-get install -y ca-certificates curl vim
# Allow the root user to log in with the same credentials.
# The tutorial assumes root access.
echo "PermitRootLogin Yes" >> /etc/ssh/sshd_config
mkdir -p /root/.ssh
cp /home/vagrant/.ssh/authorized_keys /root/.ssh/authorized_keys
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
And finally the main Vagrantfile specifying the db and web VMs from the tutorial configuration file:
Vagrant.configure("2") do |config|
config.vm.define "db" do |db|
db.vm.box = "prepared-box"
db.ssh.username = "root"
end
config.vm.define "web" do |web|
web.vm.box = "prepared-box"
web.ssh.username = "root"
end
end
The first commands block will run in the web VM:
user@host:~$ apt-get install -y nginx
… while the second block will run in the db VM:
user@host:~$ apt-get install -y postgresql