Vagrant is a great way of managing VMs for development, testing, or anything else you might be up to. Using a handy Vagrantfile, you can customise your virtual box and share the same specs with your team. Vagrant also has several handy provisioners to set up the box during the init stage.

But Vagrant is not limited to what you can do with a single box. It can handle advanced, multi-instance use-cases as well. You can use it to test master-slave replication by bringing up 2 machines. Or test your entire stack by connecting a web server, an API server, and a backend.

I’m going to create a master-slave network by bringing up 2 vagrant boxes and connecting them. My Vagrantfile looks like this:

Vagrant.configure("2") do |config|

  config.vm.define "master" do |master|

    master.vm.hostname = "mm-master"
    master.vm.box = "ubuntu/hirsute64"
    master.vm.network "private_network", ip: "192.168.20.10"

    master.vm.provision "shell",

      inline:"apt-get update; apt-get -y install nginx"

  end

  config.vm.define "slave_1" do |slave_1|

    slave_1.vm.hostname = "mm-slave-1"
    slave_1.vm.box = "ubuntu/hirsute64"
    slave_1.vm.network "private_network", ip: "192.168.20.11"

    slave_1.vm.provision "shell",

      inline:"apt-get update; apt-get -y install nginx"

  end

end

There are a few things happening in the code above:

  1. There are 2 config.vm.define blocks: one for the master, and one for the slave. Within each block, we’re configuring the hostname, box image, IP address, and provisioning.
  2. Each block has its own scope, so variables and configs defined in master have no effect in slave_1.
  3. The order of execution is outside -> in. First the outermost commands will get processed, then the ones deeper in.

When you run vagrant up, you should see output this like:

Bringing machine 'master' up with 'virtualbox' provider...
Bringing machine 'slave_1' up with 'virtualbox' provider...
==> master: Importing base box 'ubuntu/hirsute64'...
==> master: Matching MAC address for NAT networking...
==> master: Checking if box 'ubuntu/hirsute64' version '20210820.0.0' is up to date...
==> master: Setting the name of the VM: a_master_1630213713542_21834
==> master: Clearing any previously set network interfaces...
==> master: Preparing network interfaces based on configuration...
    master: Adapter 1: nat
    master: Adapter 2: hostonly
==> master: Forwarding ports...
    master: 22 (guest) => 2222 (host) (adapter 1)
==> master: Running 'pre-boot' VM customizations...
==> master: Booting VM...
==> master: Waiting for machine to boot. This may take a few minutes...
    master: SSH address: 127.0.0.1:2222
    master: SSH username: vagrant
    master: SSH auth method: private key
    master:
    master: Vagrant insecure key detected. Vagrant will automatically replace
    master: this with a newly generated keypair for better security.
    master:
    master: Inserting generated public key within guest...
    master: Removing insecure key from the guest if it's present...
    master: Key inserted! Disconnecting and reconnecting using new SSH key...
==> master: Machine booted and ready!
==> master: Attempting graceful shutdown of VM...
==> master: Booting VM...
==> master: Waiting for machine to boot. This may take a few minutes...
==> master: Machine booted and ready!
==> master: Checking for guest additions in VM...
==> master: Setting hostname...
==> master: Configuring and enabling network interfaces...
==> master: Mounting shared folders...
    master: /vagrant => /private/tmp/a
==> master: Running provisioner: shell...
    master: Running: inline script
    ...
==> slave_1: Importing base box 'ubuntu/hirsute64'...
==> slave_1: Matching MAC address for NAT networking...
==> slave_1: Checking if box 'ubuntu/hirsute64' version '20210820.0.0' is up to date...
==> slave_1: Setting the name of the VM: a_slave_1_1630213830494_36151
==> slave_1: Fixed port collision for 22 => 2222. Now on port 2200.
==> slave_1: Clearing any previously set network interfaces...
==> slave_1: Preparing network interfaces based on configuration...
    slave_1: Adapter 1: nat
    slave_1: Adapter 2: hostonly
==> slave_1: Forwarding ports...
    slave_1: 22 (guest) => 2200 (host) (adapter 1)
==> slave_1: Running 'pre-boot' VM customizations...
==> slave_1: Booting VM...
==> slave_1: Waiting for machine to boot. This may take a few minutes...
    slave_1: SSH address: 127.0.0.1:2200
    slave_1: SSH username: vagrant
    slave_1: SSH auth method: private key
    slave_1:
    slave_1: Vagrant insecure key detected. Vagrant will automatically replace
    slave_1: this with a newly generated keypair for better security.
    slave_1:
    slave_1: Inserting generated public key within guest...
    slave_1: Removing insecure key from the guest if it's present...
    slave_1: Key inserted! Disconnecting and reconnecting using new SSH key...
==> slave_1: Machine booted and ready!
==> slave_1: Checking for guest additions in VM...
    slave_1: The guest additions on this VM do not match the installed version of
    slave_1: VirtualBox! In most cases this is fine, but in rare cases it can
    slave_1: prevent things such as shared folders from working properly. If you see
    slave_1: shared folder errors, please make sure the guest additions within the
    slave_1: virtual machine match the version of VirtualBox you have installed on
    slave_1: your host and reload your VM.
    slave_1:
    slave_1: Guest Additions Version: 6.0.0 r127566
    slave_1: VirtualBox Version: 6.1
==> slave_1: Setting hostname...
==> slave_1: Configuring and enabling network interfaces...
==> slave_1: Mounting shared folders...
    slave_1: /vagrant => /private/tmp/a
==> slave_1: Running provisioner: shell...
    slave_1: Running: inline script
    ...

Vagrant Commands

Most of the Vagrant commands work for several boxes just as they do for a single one. The usual Vagrant commands work fine, and by default they work on all boxes at once. But some only work per box and need the name of whichever box you want to target.

  • vagrant ssh <machine-name>: SSH alone won’t work any more. You’ll have to specify the name of the machine you want to ssh into.
  • vagrant up or vagrant up <machine-name>: You can bring up all machines at once or one specific machine. There is also an option to specify which machines should come up when you do up without a machine name.
  • vagrant status or vagrant status <machine-name>: Show the status of all machines or a specific one.
  • vagrant suspend or vagrant suspend <machine-name>: Pause all machines or a specific one.
  • vagrant resume or vagrant resume <machine-name>: Resume all machines or a specific one.
  • vagrant halt or vagrant halt <machine-name>: Shutdown all machines or a specific one.
  • vagrant destroy or vagrant destroy <machine-name>: Destroy all machines or a specific one.
  • vagrant snapshot <sub-command> <machine-name>: Snapshot a single machine or the entire environment.

The official Vagrant doc on multi-machine setups has more info.