Categories
Infrastructure as Code (IaC) Stayno's Notes

Vagrant for web dev environment

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.

GitHub Vagrant file code

# -*- 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

Leave a Reply

Your email address will not be published. Required fields are marked *