Vagrant from Hashicorp is a program that provides a declarative interface to create and manage Virtual Machines (VM). It defines the state of a VM and instructs VM providers (ex: VirtualBox, VMWare) how to create/update/manage it.
Why is this needed?
Creating a simple VM is relatively easy thanks to the VM providers. But managing VMs and sharing their state with other users is very complex and this is where Vagrant does it’s magic.
- Provisioning: Instead of manually specifying the configuration of a machine, the state is declared in a
Vagrantfile. These files allow the user to define configuration such as IP addresses, shared folders and even accept initialization scripts to install necessary packages when the VM is created.
- Replication: Once a
Vagrantfileis defined, this file can be shared with other users/developers who can then simply create a replica of the machine by simply running a command.
How Does It Work?
Note: Vagrant doesn’t actually create virtual machines. It instructs the VM provider to create/update/delete a VM based on the configuration.
To follow along this tutorial, the following are required
- Basic knowledge of shell/command line.
- An machine with a compatible OS and shell access with enough resources to run VMs.
- A VM provider. My personal recommendation is VirtualBox.
- A Vagrant installation.
|Virtual Machine (VM) / Guest||A software emulation of a real machine.|
|Host (machine/OS)||The environment which hosts the Virtual Machine (ex: a laptop or even a hypervisor).|
|VM Provider||A program such as VMWare, VirtualBox etc., that creates/manages VMs.|
|Vagrant Box||A base image of an Operating system (with additional packages) that’s pre-built for convenience. These are listed in the Vagrant Box Catalog.|
The following is a barebones
Vagrant.configure("2") do |config| config.vm.box = "ubuntu/bionic64" config.vm.provider "virtualbox" do |v| v.memory = 2048 v.cpus = 1 end end
"2"stands for the version of configuration object. At the time of writing this post, values
config.vm.boxis the Vagrant Box corresponding to the OS (+additional packages) that will run on this VM. This box will be downloaded on the first run of this VM. The full list of supported VMs is on the Vagrant Box Catalog.
config.vm.providerconfigures the VM provider (
virtualboxin this case).
v.memory: the amount of RAM in bytes to allocate to this VM.
v.cpus: the number of virtual CPUs to allocate to this VM.
v.name: an optional name for this VM.
Place this file in a folder of choice and run vagrant
$ mkdir Documents/vagrant $ cd Documents/vagrant $ vagrant up
This will trigger vagrant to download the box (if not already locally available), configure the VM provider and start up the VM.
Note: All the following bash commands are executed in the same directory as the vagrant file unless otherwise specified.
Vagrant also creates an
SSH key pair for this machine and sets up an ssh-agent on the VM. To check the ssh config
$ vagrant ssh-config # This is a sample output Host default HostName 127.0.0.1 User vagrant Port 2222 UserKnownHostsFile /dev/null StrictHostKeyChecking no PasswordAuthentication no IdentityFile <path>/.vagrant/machines/default/virtualbox/private_key IdentitiesOnly yes LogLevel FATAL
To ssh into the machine
$ vagrant ssh
One of the key features of Vagrant is declarative configuration of the VM state. This usually involves installing/updating necessary packages, configuring users and/or running custom scripts, a process dubbed provisioning.
There are multiple ways of defining the provisioning script. The following example shows the inline method.
Anything defined within the
<<SCRIPT...SCRIPT block is interpreted as a shell script by Vagrant.
$basicscript= <<SCRIPT sudo apt update sudo apt -y dist-upgrade sudo apt -y autoremove SCRIPT Vagrant.configure("2") do |config| config.vm.box = "ubuntu/bionic64" config.vm.provider "virtualbox" do |v| v.memory = 2048 v.cpus = 1 end config.vm.provision "shell" do |s| s.inline = $basicscript end end
config.vm.provision defines a
shell provisioner block and
inline indicates that it’s an inline script.
Vagrantfile, Vagrant will create the VM with the specified box and once the basic setup is complete, the inline provisioning script will be executed (updating packages in this case).
Provisioning scripts are only run on the first instantiation of
$ vagrant up, i.e, while creating the VM for the first time. For an existing VM, use
$ vagrant provision to execute the provisioning script.
Alternatively, a separate script file can be used for more complex actions (in order to keep the
Vagrantfile simple). This is done using the
Vagrant.configure("2") do |config| ... config.vm.provision "shell" do |s| s.path = "somescript.sh" end end
Vagrant copies the script file to the VM and executes it while provisioning. A remote path where the script can be fetched can also be used.
s.path = "https://example.com/somescript.sh"
Vagrant makes it easier than ever to shares files/folders between the host and the guest.
synced_folder instruction is used for this.
config.vm.synced_folder "path on the host" , "path on the guest"
In the following example, the contents of the host’s
Documents folder can be accessed in the VM at the
Vagrant.configure("2") do |config| ... config.vm.synced_folder "~/Documents" , "/Docs" end
It’s important to note that by default, the folder in which the
Vagrantfile exists is shared with the VM at the
/Vagrant directory. This can be disabled if necessary.
config.vm.synced_folder ".", "/vagrant", disabled: true
In order to be able to access services on the VM externally (ex: a web server) Vagrant supports basic network configuration.
An IP address can be assigned to the VM.
config.vm.network "public_network", ip: "192.168.50.3"
If a web server is running on the VM at port
80 then it can be accessed at
A hostname can also be used.
config.vm.hostname = "myvm.local" config.vm.network "public_network", ip: "192.168.50.3", hostname: true
This adds the host entry to the
/etc/hosts file of the host machine.
192.168.50.3 myvm myvm.local
If a web server is now running on the VM at port
80 then it can be accessed at
http://myvm.local or simply
Vagrant also supports port forwarding
config.vm.network "forwarded_port", guest: 80, host: 8080
And finally, all the configuration options mentioned above can be replicated to create N machines using the same
define keyword is used to group options for a single VM.
$basicscript= <<SCRIPT sudo apt update sudo apt -y dist-upgrade sudo apt -y autoremove SCRIPT $vm1script= <<SCRIPT # Add custom commands to be run only for VM 1 SCRIPT $vm2script= <<SCRIPT # Add custom commands to be run only for VM 2 SCRIPT Vagrant.configure("2") do |config| config.vm.box = "ubuntu/bionic64" # Common settings config.vm.synced_folder "./", "/srv" config.vm.provision :shell, inline: $basicscript # Settings for VM 1 config.vm.define "vm1" do |master| vm1.vm.network "private_network", ip: "192.168.50.3" vm1.vm.provision "shell" do |s| s.inline: $vm1script, #run the vm1script defined above end end # Settings for VM 2 config.vm.define "vm2" do |minion| vm2.vm.network "private_network", ip: "192.168.50.2" vm2.vm.provision "shell" do |s| s.inline: $vm2script, #run the vm2script defined above end end end
- Official Vagrantfile reference.