Define multiple disks inside Vagrant using VirtualBox provider. It is not immediately obvious how to achieve it, but everything you need is there.
Go to second solution to see how I solved this issue.
First solution – the experimental one
Use experimental disks
feature to define multiple disks.
$ cat Vagrantfile
Vagrant.configure("2") do |config| config.vm.box = "debian/bullseye64" config.vm.provider :virtualbox config.vm.disk :disk, size: "2GB", name: "extra_storage1" config.vm.disk :disk, size: "1GB", name: "extra_storage2" end
This solution has many drawbacks. It is experimental and doesn’t work with every vagrant box. It will work when using Debian box, but not CentOS.
Debian operating system.
$ VAGRANT_EXPERIMENTAL=disks vagrant up
==> vagrant: You have requested to enabled the experimental flag with the following features: ==> vagrant: ==> vagrant: Features: disks ==> vagrant: ==> vagrant: Please use with caution, as some of the features may not be fully ==> vagrant: functional yet. Bringing machine 'default' up with 'virtualbox' provider... ==> default: Importing base box 'debian/bullseye64'... ==> default: Matching MAC address for NAT networking... ==> default: Checking if box 'debian/bullseye64' version '11.20210228.1' is up to date... ==> default: Setting the name of the VM: st_default_1615930633316_46471 ==> default: Fixed port collision for 22 => 2222. Now on port 2200. ==> default: Clearing any previously set network interfaces... ==> default: Preparing network interfaces based on configuration... default: Adapter 1: nat default: Adapter 2: hostonly ==> default: Forwarding ports... default: 22 (guest) => 2200 (host) (adapter 1) ==> default: Configuring storage mediums... default: Disk 'extra_storage2' not found in guest. Creating and attaching disk to guest... ==> default: Booting VM... ==> default: Waiting for machine to boot. This may take a few minutes... default: SSH address: 127.0.0.1:2200 default: SSH username: vagrant default: SSH auth method: private key default: default: Vagrant insecure key detected. Vagrant will automatically replace default: this with a newly generated keypair for better security. default: default: Inserting generated public key within guest... default: Removing insecure key from the guest if it's present... default: Key inserted! Disconnecting and reconnecting using new SSH key... ==> default: Machine booted and ready! ==> default: Checking for guest additions in VM... default: The guest additions on this VM do not match the installed version of default: VirtualBox! In most cases this is fine, but in rare cases it can default: prevent things such as shared folders from working properly. If you see default: shared folder errors, please make sure the guest additions within the default: virtual machine match the version of VirtualBox you have installed on default: your host and reload your VM. default: default: Guest Additions Version: 6.0.0 r127566 default: VirtualBox Version: 6.1 ==> default: Configuring and enabling network interfaces... ==> default: Mounting shared folders... default: /vagrant => /home/milosz/Projects/vagrant/st ==> default: Machine 'default' has a post `vagrant up` message. This is a message ==> default: from the creator of the Vagrantfile, and not from Vagrant itself: ==> default: ==> default: Vanilla Debian box. See https://app.vagrantup.com/debian for help and bug reports
$ vagrant ssh -c lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 20G 0 disk `-sda1 8:1 0 20G 0 part / sdb 8:16 0 2G 0 disk sdc 8:32 0 1G 0 disk Connection to 127.0.0.1 closed.
CentOS operating system.
$ VAGRANT_EXPERIMENTAL=disks vagrant up
==> vagrant: You have requested to enabled the experimental flag with the following features: ==> vagrant: ==> vagrant: Features: disks ==> vagrant: ==> vagrant: Please use with caution, as some of the features may not be fully ==> vagrant: functional yet. Bringing machine 'default' up with 'virtualbox' provider... ==> default: Importing base box 'centos/7'... ==> default: Matching MAC address for NAT networking... ==> default: Checking if box 'centos/7' version '2004.01' is up to date... ==> default: Setting the name of the VM: st_default_1615930185225_39920 ==> default: Fixed port collision for 22 => 2222. Now on port 2200. ==> default: Clearing any previously set network interfaces... ==> default: Preparing network interfaces based on configuration... default: Adapter 1: nat default: Adapter 2: hostonly ==> default: Forwarding ports... default: 22 (guest) => 2200 (host) (adapter 1) ==> default: Configuring storage mediums... There was an error while executing `VBoxManage`, a CLI used by Vagrant for controlling VirtualBox. The command and stderr is shown below. Command: ["storageattach", "a0f45474-df9e-4c6f-b1ed-326965b83525", "--storagectl", "SATA Controller", "--port", "0", "--device", "0", "--type", "hdd", "--medium", "/home/milosz/VirtualBox VMs/bullseye_default_1615923090380_12623/extra_storage1.vdi", "--comment", "This disk is managed externally by Vagrant. Removing or adjusting settings could potentially cause issues with Vagrant."] Stderr: VBoxManage: error: Could not find a controller named 'SATA Controller'
The situation is obvious as this solution requires a specific box configuration.
Second solution – the proper one
This is where it gets interesting. This solution is universal. It will work with Debian, Ubuntu, and CentOS as it uses an additional storage controller and disks created using a trigger before the up operation.
I will save you some time, do not try to create disks inside the VirtualBox provider block as it is evaluated multiple times, so it is prone to unexpected race conditions. Also, there are cases where it does work properly, but not the way you would expect.
$ cat Vagrantfile
require 'fileutils' # file operations needs to be relative to this file VAGRANT_ROOT = File.dirname(File.expand_path(__FILE__)) # directory that will contain VDI files VAGRANT_DISKS_DIRECTORY = "disks" # controller definition VAGRANT_CONTROLLER_NAME = "Virtual I/O Device SCSI controller" VAGRANT_CONTROLLER_TYPE = "virtio-scsi" # define disks # The format is filename, size (GB), port (see controller docs) local_disks = [ { :filename => "disk1", :size => 1, :port => 5 }, { :filename => "disk2", :size => 2, :port => 6 }, { :filename => "disk3", :size => 1, :port => 25 } ] Vagrant.configure("2") do |config| config.vm.box = "debian/bullseye64" #config.vm.box = "centos/7" #config.vm.box = "ubuntu/groovy64" disks_directory = File.join(VAGRANT_ROOT, VAGRANT_DISKS_DIRECTORY) # create disks before "up" action config.trigger.before :up do |trigger| trigger.name = "Create disks" trigger.ruby do unless File.directory?(disks_directory) FileUtils.mkdir_p(disks_directory) end local_disks.each do |local_disk| local_disk_filename = File.join(disks_directory, "#{local_disk[:filename]}.vdi") unless File.exist?(local_disk_filename) puts "Creating \"#{local_disk[:filename]}\" disk" system("vboxmanage createmedium --filename #{local_disk_filename} --size #{local_disk[:size] * 1024} --format VDI") end end end end # create storage controller on first run unless File.directory?(disks_directory) config.vm.provider "virtualbox" do |storage_provider| storage_provider.customize ["storagectl", :id, "--name", VAGRANT_CONTROLLER_NAME, "--add", VAGRANT_CONTROLLER_TYPE, '--hostiocache', 'off'] end end # attach storage devices config.vm.provider "virtualbox" do |storage_provider| local_disks.each do |local_disk| local_disk_filename = File.join(disks_directory, "#{local_disk[:filename]}.vdi") unless File.exist?(local_disk_filename) storage_provider.customize ['storageattach', :id, '--storagectl', VAGRANT_CONTROLLER_NAME, '--port', local_disk[:port], '--device', 0, '--type', 'hdd', '--medium', local_disk_filename] end end end # cleanup after "destroy" action config.trigger.after :destroy do |trigger| trigger.name = "Cleanup operation" trigger.ruby do # the following loop is now obsolete as these files will be removed automatically as machine dependency local_disks.each do |local_disk| local_disk_filename = File.join(disks_directory, "#{local_disk[:filename]}.vdi") if File.exist?(local_disk_filename) puts "Deleting \"#{local_disk[:filename]}\" disk" system("vboxmanage closemedium disk #{local_disk_filename} --delete") end end if File.exist?(disks_directory) FileUtils.rmdir(disks_directory) end end end end
Start box.
$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider... ==> default: Running action triggers before up ... ==> default: Running trigger: Create disks... Creating "disk1" disk 0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100% Medium created. UUID: 30580ca1-b346-47d6-bcc9-09922b66b972 Creating "disk2" disk 0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100% Medium created. UUID: 91548a05-51e6-4345-bb6b-2f2cd12dabd0 Creating "disk3" disk 0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100% Medium created. UUID: 13423cda-4934-49a2-a690-112800bf76f0 ==> default: Importing base box 'debian/bullseye64'... ==> default: Matching MAC address for NAT networking... ==> default: Checking if box 'debian/bullseye64' version '11.20210228.1' is up to date... ==> default: Setting the name of the VM: storage_default_1615931757454_98076 ==> default: Fixed port collision for 22 => 2222. Now on port 2200. ==> default: Clearing any previously set network interfaces... ==> default: Preparing network interfaces based on configuration... default: Adapter 1: nat ==> default: Forwarding ports... default: 22 (guest) => 2200 (host) (adapter 1) ==> default: Running 'pre-boot' VM customizations... ==> default: Booting VM... ==> default: Waiting for machine to boot. This may take a few minutes... default: SSH address: 127.0.0.1:2200 default: SSH username: vagrant default: SSH auth method: private key default: default: Vagrant insecure key detected. Vagrant will automatically replace default: this with a newly generated keypair for better security. default: default: Inserting generated public key within guest... default: Removing insecure key from the guest if it's present... default: Key inserted! Disconnecting and reconnecting using new SSH key... ==> default: Machine booted and ready! ==> default: Checking for guest additions in VM... default: The guest additions on this VM do not match the installed version of default: VirtualBox! In most cases this is fine, but in rare cases it can default: prevent things such as shared folders from working properly. If you see default: shared folder errors, please make sure the guest additions within the default: virtual machine match the version of VirtualBox you have installed on default: your host and reload your VM. default: default: Guest Additions Version: 6.0.0 r127566 default: VirtualBox Version: 6.1 ==> default: Mounting shared folders... default: /vagrant => /home/milosz/Projects/vagrant/storage ==> default: Machine 'default' has a post `vagrant up` message. This is a message ==> default: from the creator of the Vagrantfile, and not from Vagrant itself: ==> default: ==> default: Vanilla Debian box. See https://app.vagrantup.com/debian for help and bug reports
Inspect disks inside it.
$ vagrant ssh -c lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 1G 0 disk sdb 8:16 0 2G 0 disk sdc 8:32 0 1G 0 disk sdd 8:48 0 20G 0 disk `-sdd1 8:49 0 20G 0 part / Connection to 127.0.0.1 closed.
Stop box.
$ vagrant halt
==> default: Attempting graceful shutdown of VM...
Start box again. Disks are left alone as expected.
$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider... ==> default: Running action triggers before up ... ==> default: Running trigger: Create disks... ==> default: Checking if box 'debian/bullseye64' version '11.20210228.1' is up to date... ==> default: Clearing any previously set forwarded ports... ==> default: Fixed port collision for 22 => 2222. Now on port 2200. ==> default: Clearing any previously set network interfaces... ==> default: Preparing network interfaces based on configuration... default: Adapter 1: nat ==> default: Forwarding ports... default: 22 (guest) => 2200 (host) (adapter 1) ==> default: Booting VM... ==> default: Waiting for machine to boot. This may take a few minutes... default: SSH address: 127.0.0.1:2200 default: SSH username: vagrant default: SSH auth method: private key ==> default: Machine booted and ready! ==> default: Checking for guest additions in VM... default: The guest additions on this VM do not match the installed version of default: VirtualBox! In most cases this is fine, but in rare cases it can default: prevent things such as shared folders from working properly. If you see default: shared folder errors, please make sure the guest additions within the default: virtual machine match the version of VirtualBox you have installed on default: your host and reload your VM. default: default: Guest Additions Version: 6.0.0 r127566 default: VirtualBox Version: 6.1 ==> default: Mounting shared folders... default: /vagrant => /home/milosz/Projects/vagrant/storage ==> default: Machine already provisioned. Run `vagrant provision` or use the `--provision` ==> default: flag to force provisioning. Provisioners marked to run always will still run. ==> default: Machine 'default' has a post `vagrant up` message. This is a message ==> default: from the creator of the Vagrantfile, and not from Vagrant itself: ==> default: ==> default: Vanilla Debian box. See https://app.vagrantup.com/debian for help and bug reports
Destroy box. Remove disks.
$ vagrant destroy -f
==> default: Forcing shutdown of VM... ==> default: Destroying VM and associated drives... ==> default: Running action triggers after destroy ... ==> default: Running trigger: Cleanup operation...