I’ve been an avid MAMP user for many years before I moved to Vagrant. MAMP always did a pretty good job for a basic PHP/MySQL setup despite some of the downsides:
These disadvantages can cause annoyances but they are not insurmountable. It’s when you need more advanced technologies such as MongoDB, Elasticsearch, Redis, Kafka etc. that things start getting very hard to manage locally.
That’s where Vagrant comes into play…
Vagrant lets you create and run Virtual Machines and make them directly accessible from your local system. Tools like MAMP install everything locally and try to get it to work as good as possible on you system, while Vagrant simply gives you a real Linux machine that you can configure just like you’d do with your production server.
Some of the many advantages of working with such environments:
Configuration of these VMs is automated by what are called provisioners. In this beginners guide I’ll show you the easiest way to get going by using the shell provisioning strategy. As the name suggests, this strategy builds your VM based on a series of self-written shell scripts.
I do want to mention that some knowledge of Linux commands might come in handy, but is not required. I won’t always elaborate on every command so if you’d like to know what each specific command does you can always use Google or simply ask me. I do explain some of the basic stuff just in case you’ve never set up a web server of your own.
Enough of the boring, theoretical stuff, let’s start with the fun part … 🙂
Download and install Vagrant and VirtualBox and create a new project directory somewhere on your system. Within that directory create a file called Vagrantfile
:
Vagrant.configure("2") do |config| config.vm.box = "hashicorp/bionic64"
end
Then, run:
vagrant up
You’ll see a whole lot is going on. Vagrant is now searching its box catalog for a box called hashicorp/bionic64, downloads it, and creates the VM on your system. You can see these boxes as VMs containing a certain operating system and possibly some pre-installed packages. The box we’ve chosen is the “official” Vagrant Ubuntu 18.04 64-bit box which is perfect for a local LAMP development environment.
If you now open VirtualBox, you’ll see a new machine was created:
Thanks to some hidden files in your project directory Vagrant now knows which folder is associated to which machine. It is therefore possible running multiple VMs together.
So it appears to be running, how can we test it? Simple! Run:
vagrant ssh
Cool! We’ve now ssh’ed our way into our Ubuntu machine!
By the way, VirtualBox is a so called provider for Vagrant, which is the software running your VM. There are a couple of others, but until now I haven’t found any reason to move away from VirtualBox.
So all we have running now is a Linux VM. We could simply ssh to it and install our LAMP configuration manually, but someone else working on that project would then have to do the exact same thing. This wouldn’t really be much of an improvement compared to a local Mac or MAMP environment wouldn’t it?
So here’s where we’ll start using the shell script provisioners. Let’s start by installing Apache, and in the meantime also fine-tune our VM settings. Edit your Vagrantfile
so it looks like this:
Vagrant.configure("2") do |config|
config.vm.box = "hashicorp/bionic64"
# Give our VM a name so we immediately know which box this is when opening VirtualBox, and spice up our VM's resources
config.vm.provider "virtualbox" do |v|
v.name = "Our amazing test project"
v.memory = 4096
v.cpus = 1
end
# Choose a custom IP so this doesn't collide with other Vagrant boxes
config.vm.network "private_network", ip: "192.168.88.188"
# Execute shell script(s)
config.vm.provision :shell, path: "provision/components/apache.sh"
end
As you can see, we’ve added an apache.sh provisioner. We tell Vagrant to execute that script within our VM once it is created. Create provision/components/apache.sh
:
#!/bin/bash
apt-get update
apt-get install -y apache2
Now we’ll need to restart the VM in order for the changes to be in effect. There are 3 different commands you can use for that. Let me briefly explain them so you know the difference:
vagrant reload |
Re-runs your Vagrantfile, so used for changes in your Vagrantfile |
vagrant provision |
Re-runs your configured provisioners, so only used if you’ve changed any of them, or added new ones to the Vagrantfile |
vagrant reload --provision |
Re-runs your Vagrantfile AND your provisioners, so used if there were changes in both files |
Since we’ve changed our VM’s configuration and created a new provisioning file, we’ll need to run:
vagrant reload --provision
Let Vagrant do its magic, and once it’s finished, visit http://192.168.88.188/. Boom! You’re now looking at the Apache default page of your VM, which means it’s up and running!
The page you’re looking at is a simple index.html
file that’s located within our VM in the /var/www/html
directory, the so-called document root. This document root is the directory that’s available from the outside to your server.
Obviously we don’t want to develop directly into our VM’s document root using vim (or my. So we’ll need to find a way for our project files to be available within our VM. To do this, you’ll only need to add one line to your Vagrantfile
, add it right above the Apache provisioning line:
config.vm.synced_folder ".", "/var/www", create: true, nfs: true, mount_options: ["actimeo=2"]
What this does is link our whole project folder to the /var/www/
directory on our VM. Now create a html
directory and put an index.html
file in there:
<html>
<head>
<title>Wassup</title>
</head>
<body>
<p>How cool is this?</p>
</body>
</html>
Then, run:
vagrant reload
Let Vagrant do it’s things, refresh the page and … there it is! You are now looking at the page you just created.
Time to install PHP and include some generally-used PHP libraries. Create a new php.sh
file, don’t forget adding it to your Vagrantfile
:
#!/bin/bash
sudo apt-get install software-properties-common
sudo LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php
sudo apt-get update
sudo apt-get install -y php7.4 php7.4-bcmath php7.4-bz2 php7.4-cli php7.4-curl php7.4-intl php7.4-json php7.4-mbstring php7.4-opcache php7.4-soap php7.4-xml php7.4-xsl php7.4-zip libapache2-mod-php7.4
sudo service apache2 restart
Also add a phpinfo.php
file to your html
directory:
<?php
phpinfo();
Run:
vagrant provision
Once Vagrant is finished, visit http://192.168.88.188/phpinfo.php, which should successfully display the PHP info page! Easy, right?
One thing you’ll probably do in any new PHP setup is level up the max. memory usage, post size, and upload size. Again, we don’t want to do it manually, we want it to be configured correctly from the moment we clone our project and start up our VM. To do this, add these lines before sudo service apache2 restart
in php.sh
:
sed -i 's/max_execution_time = .*/max_execution_time = 60/' /etc/php/7.4/apache2/php.ini
sed -i 's/post_max_size = .*/post_max_size = 64M/' /etc/php/7.4/apache2/php.ini
sed -i 's/upload_max_filesize = .*/upload_max_filesize = 1G/' /etc/php/7.4/apache2/php.ini
sed -i 's/memory_limit = .*/memory_limit = 512M/' /etc/php/7.4/apache2/php.ini
I guess this speaks for itself, what this does is find certain lines in php.ini
and replace them with the values we want.
Run vagrant provision
again and check your phpinfo.php
file. Search for any of the 4 directives and you’ll see that the PHP settings have successfully been changed!
Next up is MySQL. Create a mysql.sh
file:
#!/bin/bash
DBHOST=localhost
DBNAME=mydb
DBUSER=myuser
DBPASSWD=password
# Install MySQL
apt-get update
debconf-set-selections <<< "mysql-server mysql-server/root_password password $DBPASSWD"
debconf-set-selections <<< "mysql-server mysql-server/root_password_again password $DBPASSWD"
apt-get -y install mysql-server-5.7
# Create the database and grant privileges
CMD="mysql -uroot -p$DBPASSWD -e"
$CMD "CREATE DATABASE IF NOT EXISTS $DBNAME"
$CMD "GRANT ALL PRIVILEGES ON $DBNAME.* TO '$DBUSER'@'%' IDENTIFIED BY '$DBPASSWD';"
$CMD "FLUSH PRIVILEGES;"
# Allow remote access to the database
sudo sed -i "s/.*bind-address.*/bind-address = 0.0.0.0/" /etc/mysql/mysql.conf.d/mysqld.cnf
sudo service mysql restart
In order to use MySQL in PHP, we also can’t forget the PHP MySQL extension, so add php7.4-mysql
to the long sudo apt-get install
line in php.sh
.
Run vagrant provision
again and while Vagrant is doing it’s thing create a db.php
file in your html
folder:
<?php
$conn = mysqli_connect("localhost", "myuser", "password", "mydb");
if (!$conn) {
die("Error: " . mysqli_connect_error());
}
echo "Connected!";
Visit http://192.168.88.188/db.php and if all went well you should be seeing the “Connected!” message.
PhpMyAdmin could always come in handy on a LAMP development environment, so create another script file called phpmyadmin.sh
and add it to your Vagrantfile
:
#!/bin/bash
sudo apt-get update
debconf-set-selections <<< "phpmyadmin phpmyadmin/dbconfig-install boolean true"
debconf-set-selections <<< "phpmyadmin phpmyadmin/app-password-confirm password $DBPASSWD"
debconf-set-selections <<< "phpmyadmin phpmyadmin/mysql/admin-pass password $DBPASSWD"
debconf-set-selections <<< "phpmyadmin phpmyadmin/mysql/app-pass password $DBPASSWD"
debconf-set-selections <<< "phpmyadmin phpmyadmin/reconfigure-webserver multiselect none"
sudo apt-get install -y phpmyadmin php-mbstring php-gettext
sudo phpenmod mbstring
sudo systemctl restart apache2
You’ve guessed it, run vagrant provision
and visit http://192.168.88.188/phpmyadmin/, log in with myuser/password and there we go… easy as pie.
Not everyone likes phpMyAdmin. To access your database with a GUI you’ll need to use a SSH connection. How to set this up depends on the software you’re using, but in general these are the things you’ll need to configure:
Host | 127.0.0.1 |
Username | myuser (which we’ve defined in mysql.sh) |
Password | password (which we’ve also defined in mysql.sh) |
SSH Host | 127.0.0.1 |
SSH User | vagrant |
SSH Key | Can be found in this location in your project directory: /.vagrant/machines/default/virtualbox/private_key |
SSH Port | 2222 |
We’re on a roll here! But I personally prefer working with real domain names instead of IP addresses. It’s much easier to remember but also necessary if your application or website should behave differently depending on the domain name (e.g. login.mydomain and www.mydomain), something we can’t achieve with a single IP address.
To get this working our Ubuntu server will need a custom Virtual Host file. Again, we’re not going to create it directly on our VM and make every user to the same thing, we’re going to automate this. Lets first create the Virtual Host file within our project: provision/config/apache/vhosts/mytestproject.local.com.conf
<VirtualHost *:80>
ServerName mytestproject.local.com
ServerAlias www.mytestproject.local.com
DocumentRoot /var/www/html
<Directory /var/www/html>
# Allow .htaccess rewrite rules
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Don’t worry too much about it if you don’t know what this file does. Simply put, it defines which folder is the document root when the server is being visited by the domain names we just invented (myproject.local.com and www.myproject.local.com). If you want multiple domain names available from different directories, you can always create more of these files.
Now, vhost files such as these need to be located in the /etc/apache2/sites-available
directory on our VM, and each of those files need to be enabled. We do this by editing our apache.sh
file:
#!/bin/bash
apt-get update
apt-get install -y apache2
# Copy the vhost config file
cp /var/www/provision/config/apache/vhosts/mytestproject.local.com.conf /etc/apache2/sites-available/mytestproject.local.com.conf
# Disable the default vhost file
a2dissite 000-default
# Enable our custom vhost file
a2ensite mytestproject.local.com.conf
# Restart for the changes to take effect
service apache2 restart
Once again run vagrant provision
. While all scripts are being executed we can do the one manual action that needs to be done on our system to make this work. We’ll need to edit our local hosts file (on Mac, it’s /etc/hosts
) and add:
192.168.88.188 mytestproject.local.com
192.168.88.188 www.mytestproject.local.com
This tells our system where to look when visiting these domain names.
Save the file, and once Vagrant is finished, visit http://www.mytestproject.local.com/ or http:/mytestproject.local.com/. Much wow! We are now accessing our server through our improvised domain names! 🤘
Although you could run Composer locally, it wouldn’t be a very good idea. You don’t want Composer to base itself on your anyone’s local system. It might throw errors when certain PHP extensions are not installed, or it might install dependencies based on a wrong PHP version. That’s why we’ll always want to install/update dependencies from within our VM.
Installing Composer is pretty easy, simply add composer
to the long sudo apt-get install
list in php.sh
and run vagrant provision
.
Let’s test if it works by installing the Money library:
vagrant ssh
cd /var/www
composer require moneyphp/money
Works like a charm!
Chances are you’ll need to compile some assets so having Yarn installed on your VM can always come in handy. Create a yarn.sh
file and add it to your Vagrantfile
:
#!/bin/bash
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update && sudo apt-get install -y yarn
Run vagrant provision
one final time. Test if it works the same way we did with Composer:
vagrant ssh
cd /var/www
yarn add --dev bootstrap
Works perfectly!
Now let’s add one last minor improvement. When ssh’ing to your VM you always need to run cd /www/var
to go to your project directory. We can easily fix that by adding to the Vagrantfile
:
config.vm.provision :shell, inline: "echo 'cd /var/www' >> /home/vagrant/.profile"
That’s it! In relatively little time you’ve created a nice boilerplate that you can use in any PHP/MySQL project and you’ve now got a basic understanding on how to easily set up a local, re-usable environment. I’ve created a repository on GitHub containing the full example.
In future posts I’ll be talking about more advanced provisioners and features. So be sure to keep checking back 🙂.