This all started by surfing around the internet when I saw something about spinning developer environments on VMs. Just a quick look around lead me to Vagramt and yes in that quick search I also saw that a lot of people asked the question “is it really useful in the age of containers?”. Well you have to answer that question for yourself I can just offer you, hopefully ,some code and comments that will help you create a quick virtual machine running Ubuntu 24.04 desktop with Apache 2, MySQL, php, phpmyadmin, composer, Google Chrome, VS Code. A very basic staging web development environment.
I must say that while playing around with this project the host machine was a laptop with Windows and I for the guest machine I chose Ubuntu mainly because Vagrant seemed not to like Windows as a guest machine as much and also Ubuntu has plenty of documentation and tutorials online. Nevertheless the vagrant file, ideas and concept of the code should be transferable to other operating systems for the better part of it.
First we start by download and installing the requirements
Create a directory holding you Vagrant project files somewhere of your choice
mkdir vagrant_project
Change to that directory and initialize the vagrant project
vagrant init
This will create a file name “Vagrantfile”. Open it with any editor and change its contents.
# -*- mode: ruby -*-
# vi: set ft=ruby :
# function to get the VirtualBox installation path
def virtualbox_install_path()
vboxinst_path = ENV["VBOX_INSTALL_PATH"] || ENV["VBOX_MSI_INSTALL_PATH"] || ENV["VBOX_INSTALLER_PATH"]
if vboxinst_path != nil
return vboxinst_path
else
return nil
end
end
# function to get the VirtualBox version not used in this Vagrantfile but nice to have for inspiration
def virtualbox_version()
vbox_manage_path = virtualbox_install_path() + "VBoxManage"
vbox_version = `"#{vbox_manage_path}" --version`
clean_version = /[0-9]+\.[0-9]+\.[0-9]+/.match(vbox_version)
puts(clean_version)
if vbox_version != nil
return clean_version[0]
else
return nil
end
end
# Variables used in the setup of MySQL database and phpMyAdmin
DBHOST='localhost'
DBNAME='vmdevagrant'
DBUSER='vagrantuser'
DBPASSWD='vagrantpass'
# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.
# Every Vagrant development environment requires a box. You can search for
# boxes at https://vagrantcloud.com/search.
# Vagrant uses so called boxes that are essentially an image of the Operating System you can discover more at the link above.
# This box is based on Ubuntu 24.04 LTS Desktop and has the following features: Mozilla Firefox, App Center, LibreOffice and is pretty stabe in my experience.
config.vm.box = "gusztavvargadr/ubuntu-desktop-2404-lts"
config.vm.box_version = "2404.0.2505"
# Set the hostname of the VM to "WebDevEnv"
config.vm.hostname = "WebDevEnv"
config.vm.define "staging"
config.vm.provider :virtualbox do |vb|
vb.name = "WebDevEnv"
end
# Set the maximum boot timeout to 1200 seconds ( 20 minutes ) to allow the VM to boot up completely.
# This is useful for slower machines or when the VM takes longer to start.
config.vm.boot_timeout = 120
# This line ensures that Vagrant checks for updates to the base box
# every time you run 'vagrant up'. If a newer version of the box is
# available, Vagrant will notify you so you can choose to update.
config.vm.box_check_update = true
# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine. In the example below,
# accessing "localhost:8080" will access port 80 on the guest machine.
# NOTE: This will enable public access to the opened port
# config.vm.network "forwarded_port", guest: 80, host: 8080
# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine and only allow access
# via 127.0.0.1 to disable public access
#config.vm.network "forwarded_port", guest: 3389, host: 3389, host_ip: "127.0.0.1"
# Create a private network, which allows host-only access to the machine
# using a specific IP.
# You can find mask dhcp ip lower and upper values in the VirtualBox network settings on the host machine.
config.vm.network "private_network", type: "dhcp", netmask: "255.255.255.0", dhcp_ip:"192.168.56.100", dhcp_lower: "192.168.56.101", dhcp_upper:"192.168.56.254"
# Create a public network, which generally matched to bridged network.
# Bridged networks make the machine appear as another physical device on
# your network.
# config.vm.network "public_network"
# Share an additional folder to the guest VM. The first argument is
# the path on the host to the actual folder. The second argument is
# the path on the guest to mount the folder. And the optional third
# argument is a set of non-required options.
# config.vm.synced_folder "../data", "/vagrant_data"
# Disable the default share of the current code directory. Doing this
# provides improved isolation between the vagrant box and your host
# by making sure your Vagrantfile isn't accessible to the vagrant box.
# If you use this you may want to enable additional shared subfolders as
# shown above.
# config.vm.synced_folder ".", "/vagrant", disabled: true
# customize the configuration of VM - see https://www.virtualbox.org/manual/ch08.html
# virtual hardware configuration
config.vm.provider "virtualbox" do |vb|
# true = GUI, false = headless
vb.gui = true
# number of cpu used
vb.customize ["modifyvm", :id, "--cpus", 2]
# memory used
vb.customize ["modifyvm", :id, "--memory", 4096]
# add video ram for gui
vb.customize ["modifyvm", :id, "--vram", 256]
# clipboard, drag & drop, notifications support
# clipboard shared between host / guest
vb.customize ["modifyvm", :id, "--clipboard", "bidirectional"]
# drag & drop between host / guest
vb.customize ["modifyvm", :id, "--draganddrop", "bidirectional"]
# disable notification mouse/keyboard capture
vb.customize ["setextradata", "global", "GUI/SuppressMessages", "all"]
# startup gui screen resolution (3/4 monitor size)
vb.customize ['setextradata', :id, 'GUI/LastGuestSizeHint','1440,900']
# enable USB and add a filter based on the desired device manufacturer / product
#vb.customize ["modifyvm", :id, "--usb", "on"] # usb 1.1 (CHCI) controller
vb.customize ["modifyvm", :id, "--usbehci", "on"] # usb 2.0 (EHCI) controller
vb.customize ["modifyvm", :id, "--usbxhci", "on"] # usb 3.0 (xHCI) controller
vb.customize ['usbfilter', 'add', '0', '--target', :id,
'--name', 'QuickCam Orbit/Sphere AF',
'--vendorid', '0x046d',
'--productid', '0x0994']
vb.customize ['usbfilter', 'add', '0', '--target', :id,
'--name', 'Adafruit Console Cable',
'--vendorid', '0x10c4',
'--productid', '0xea60']
# Enable optical drive and insert the VBoxGuestAdditions.iso which should be located in the VirtualBox installation path
vb.customize ["storageattach", :id, "--storagectl", "IDE Controller", "--port", "0", "--device", "0", "--type", "dvddrive", "--medium", virtualbox_install_path()+"\\VBoxGuestAdditions.iso"]
end
# Create an output log file so we can see the output of the provisioning
config.vm.provision :shell, name: "Creating commands output file", run: "once", inline: "sudo touch /vagrant/provision_output.txt"
# Update the system
config.vm.provision :shell, name: "Run apt-get update", run: "once", inline: "sudo apt-get -y update &>> /vagrant/provision_output.txt"
# Upgrade the system
config.vm.provision :shell, name: "Run apt-get upgrade", run: "once", inline: "sudo DEBIAN_FRONTEND=noninteractive apt-get dist-upgrade --assume-yes --no-install-recommends &>> /vagrant/provision_output.txt", reboot: true
# Create system environment timer
config.vm.provision "shell", name: "Install ntp and set timezone to Europe/Berlin", run: "once", inline: <<-SHELL
# install NTP and set your time zone
apt-get -y install ntp
timedatectl set-timezone Europe/Berlin
SHELL
# Install linux headers
config.vm.provision :shell, name: "Install linux headers", run: "once", inline: "sudo apt-get -y install linux-headers-$(uname -r) &>> /vagrant/provision_output.txt"
# Install build tools
config.vm.provision :shell, name: "Install build tools", run: "once", inline: "sudo apt-get -y install gcc make perl &>> /vagrant/provision_output.txt"
# Install your common tools
config.vm.provision "shell", name: "install common tools", run: "once", inline: <<-SHELL
# tools for viewing and manipulating image
apt-get -y install imagemagick
# load your favorate browser
wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor | sudo tee /etc/apt/keyrings/google-chrome.gpg > /dev/null
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google-chrome.list
# load microsoft visual studio code
wget -q -O - https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/keyrings/microsoft.gpg > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/microsoft.gpg] https://packages.microsoft.com/repos/code stable main" | sudo tee /etc/apt/sources.list.d/vscode.list
#run update
sudo apt-get update
#Instal Google chrome
apt-get -y install google-chrome-stable
#Install VS Code
apt-get -y install code
SHELL
# Install git
config.vm.provision :shell, name: "Install git", run: "once", inline: "sudo apt-get -y install git &>> /vagrant/provision_output.txt"
# Instal apache2 web server
config.vm.provision :shell, name: "Install apache2 web server", run: "once", inline: "sudo apt-get -y install apache2 &>> /vagrant/provision_output.txt"
# Allow apache to run on port 80
config.vm.provision :shell, name: "Allow apache to run on port 80", run: "once", inline: "sudo ufw allow in 'Apache' &>> /vagrant/provision_output.txt"
# Debconfig mysql server root password (ABSOLUTELY NOT FOR PRODUCTION USE)
config.vm.provision :shell, name: "Set root password", run: "once", inline: "sudo debconf-set-selections <<< 'mysql-server mysql-server/root_password password "+DBPASSWD+"' &>> /vagrant/provision_output.txt"
# Debconfig mysql server root password again (ABSOLUTELY NOT FOR PRODUCTION USE)
config.vm.provision :shell, name: "Set root password again", run: "once", inline: "sudo debconf-set-selections <<< 'mysql-server mysql-server/root_password_again password "+DBPASSWD+"' &>> /vagrant/provision_output.txt"
# Install mysql server
config.vm.provision :shell, name: "Install mysql server", run: "once", inline: "sudo apt-get -y install mysql-server &>> /vagrant/provision_output.txt"
# Start mysql server service
config.vm.provision :shell, name: "Start mysql server service", run: "once", inline: "sudo systemctl start mysql.service &>> /vagrant/provision_output.txt"
# Create sample database
config.vm.provision :shell, name: "Create sample database", run: "once", inline: "sudo mysql -uroot -p"+DBPASSWD+" -e 'CREATE DATABASE "+DBNAME+"' &>> /vagrant/provision_output.txt"
# Create user for the sample database
config.vm.provision :shell, name: "Create user for sample database", run: "once", inline: "sudo mysql -uroot -p"+DBPASSWD+" -e \"CREATE USER '"+DBUSER+"'@'"+DBHOST+"' IDENTIFIED WITH mysql_native_password BY '"+DBPASSWD+"'\" &>> /vagrant/provision_output.txt"
# Grant all privileges to the sample database for the user
config.vm.provision :shell, name: "Grant all priveleges", run: "once", inline: "sudo mysql -uroot -p"+DBPASSWD+" -e 'GRANT ALL PRIVILEGES ON "+DBNAME+".* TO "+DBUSER+"@"+DBHOST+" WITH GRANT OPTION' &>> /vagrant/provision_output.txt"
# Flush privileges to ensure the changes take effect
config.vm.provision :shell, name: "Flush privileges", run: "once", inline: "sudo mysql -uroot -p"+DBPASSWD+" -e 'FLUSH PRIVILEGES' &>> /vagrant/provision_output.txt"
# Install php
config.vm.provision :shell, name: "Install php", run: "once", inline: "sudo apt-get -y install php libapache2-mod-php php-mysql &>> /vagrant/provision_output.txt"
# Install php extensions
config.vm.provision :shell, name: "Install php extensions", run: "once", inline: "sudo apt-get -y install php-curl php-gd php-mbstring php-xml php-zip &>> /vagrant/provision_output.txt"
# Debconfig phpmyadmin
config.vm.provision :shell, name: "Debconfig phpmyadmin", run: "once", inline: "sudo debconf-set-selections <<< 'phpmyadmin phpmyadmin/dbconfig-install boolean true' &>> /vagrant/provision_output.txt"
# Debconfig phpmyadmin app password confirm
config.vm.provision :shell, name: "phpmyadmin app password", run: "once", inline: "sudo debconf-set-selections <<< 'phpmyadmin phpmyadmin/app-password-confirm password "+DBPASSWD+"' &>> /vagrant/provision_output.txt"
# Debconfig phpmyadmin admin password
config.vm.provision :shell, name: "phpmyadmin admin password", run: "once", inline: "sudo debconf-set-selections <<< 'phpmyadmin phpmyadmin/mysql/admin-pass password "+DBPASSWD+"' &>> /vagrant/provision_output.txt"
# Debconfig phpmyadmin app pass
config.vm.provision :shell, name: "phpmyadmin app password", run: "once", inline: "sudo debconf-set-selections <<< 'phpmyadmin phpmyadmin/mysql/app-pass password "+DBPASSWD+"' &>> /vagrant/provision_output.txt"
# Debconfig phpmyadmin reconfigure webserver
config.vm.provision :shell, name: "Debconfig phpmyadmin", run: "once", inline: "sudo debconf-set-selections <<< 'phpmyadmin phpmyadmin/reconfigure-webserver multiselect none' &>> /vagrant/provision_output.txt"
# Install phpmyadmin
config.vm.provision :shell, name: "Install phpmyadmin", run: "once", inline: "sudo apt-get -y install phpmyadmin &>> /vagrant/provision_output.txt"
# Enable mod rewrite for apache2
config.vm.provision :shell, name: "Enable mod rewrite for apache2", run: "once", inline: "sudo a2enmod rewrite &>> /vagrant/provision_output.txt"
# Allow apache override
config.vm.provision :shell, name: "Allow apache override", run: "once", inline: "sudo sed -i 's/AllowOverride None/AllowOverride All/g' /etc/apache2/apache2.conf &>> /vagrant/provision_output.txt"
# Enable php error reporting
config.vm.provision :shell, name: "Enable php error reporting", run: "once", inline: "PHPVER=$(ls /etc/php | sort -V | tail -n1); sudo sed -i 's/error_reporting = .*/error_reporting = E_ALL/' /etc/php/$PHPVER/apache2/php.ini &>> /vagrant/provision_output.txt"
# Enable display errors
config.vm.provision :shell, name: "Enable display errors", run: "once", inline: "PHPVER=$(ls /etc/php | sort -V | tail -n1); sudo sed -i 's/display_errors = .*/display_errors = On/' /etc/php/$PHPVER/apache2/php.ini &>> /vagrant/provision_output.txt"
# Download composer for php management
config.vm.provision :shell, name: "Download composer for php management", run: "once", inline: "sudo curl --silent https://getcomposer.org/installer | php &>> /vagrant/provision_output.txt"
# Install composer
config.vm.provision :shell, name: "Install composer", run: "once", inline: "sudo mv composer.phar /usr/local/bin/composer &>> /vagrant/provision_output.txt"
# Reload apache2 to load php module
config.vm.provision :shell, name: "Reload apache2 to load php module", run: "once", inline: "sudo systemctl restart apache2 &>> /vagrant/provision_output.txt"
#install vbox guest additions
config.vm.provision :shell, name: "Creating optical drive mount dir", run: "once",
inline: "sudo mkdir /media/cdrom &>> /vagrant/provision_output.txt"
# Mount the optical drive to the created directory
config.vm.provision :shell, name: "Mount optical drive to dir", run: "once",
inline: "sudo mount /dev/sr0 /media/cdrom &>> /vagrant/provision_output.txt"
# Install vbox guest additions
# seems we cant log that in the shared file because it will create a conflict
# exit code of the installation script is very unreliable, so we ignore if there is an error
# Dont worry you can initially manage without the guest additions and when you run the VM it will notify you that you better install them
config.vm.provision :shell, name: "Install vbox guest additions", run: "once",
inline: "sudo /bin/sh /media/cdrom/VBoxLinuxAdditions.run || :"
# Finally unmount the optical drive from the created directory and reboot the VM for everything to take effect
config.vm.provision :shell, name: "Unmount optical drive from dir", run: "once",inline: "sudo umount /media/cdrom", reboot: true
end
Save the file and spin up a VM created from that file by executing command
vagrant up
Watch the interface for any errors or wait for vagrant to complete its job.
Other useful vagrant commands are
- Stop Virtual Machine
vagrant halt
- Destroy and delete virtual machine
vagrant destroy
Username for the guest machine is vagrant and password is vagrant