Creating a PHP 8 Development Environment Using Vagrant and Ubuntu 20.04

We’ve spent a lot of time discussing how to use Vagrant to create our development environment so now it’s time to bring everything we’ve learned together so we can finally develop some code.

Vagrant Provision

One of the pieces of Vagrant we haven’t discussed yet is the ability for Vagrant to install and setup packages in our VMs using its provisioning system. Vagrant will automatically run these commands when we vagrant up our environment the first time and it also provides the vagrant provision command to have it run the provisioning manually.

We define this set of commands using the config.vm.provision directive in our Vagrantfile. To get started we’re going to use the “inline” mode of the “shell” provisioner. There are several different provisioners supported by Vagrant but the shell mode is nice because it allows us to keep all of the provisioning steps inside the Vagrantfile and doesn’t require any additional software. We’ve found that as the complexity of the provisioning increases it’s best to move to a solution like Ansible, Puppet, or Chef.

Vagrant.configure("2") do |config|
  config.vm.box = "generic/ubuntu2004"
  config.vm.network "private_network", ip: "192.168.33.10"

  config.vm.synced_folder ".", "/home/vagrant/our-awesome-project"

  config.vm.provision "shell", inline: <<-SHELL 
    echo "our commands to here"`
  SHELL
end

The downside to using the inline mode is that as the number of steps grows or as we need to support multiple VMs in our environment it gets harder to maintain. When this happens we can extract the setup portion of the script into a shell script and call it using the method below.

Vagrant.configure("2") do |config|
  config.vm.box = "generic/ubuntu2004"
  config.vm.network "private_network", ip: "192.168.33.10"

  config.vm.synced_folder ".", "/home/vagrant/our-awesome-project"

  config.vm.provision "shell" path: "setup.sh"
end

Basic Steps to Setup Our LAMP Stack

Let’s take a step back and discuss what needs to happen to set up a LAMP stack using Ubuntu, Apache, MySQL, and PHP 8. To get our installation working there are several things we need to do.

1. Setup Our VM To Use An Alternative Repository

For this setup, we’re using the Long Term Support (LTS) release of Ubuntu so we have 5 years of support. The downside to that decision is that we’ll be “stuck” at version 7.4 of PHP even though the most current version of PHP is 8.0. Thankfully, there is an alternative repository maintained by Ondřej Surý (https://launchpad.net/~ondrej/+archive/ubuntu/php/) that allows us to install newer versions of PHP on the LTS versions of Ubuntu. Compiling PHP from source is such a pain and if we ever meet Ondřej we owe him a drink.

To get the Ondřej repository setup we need to run the commands below to install the prerequisite packages and then add the repository.

apt-get update
apt-get install -y lsb-release ca-certificates apt-transport-https software-properties-common 
add-apt-repository ppa:ondrej/php

2. Install Apache, MySQL, and PHP 8

Now we can install the rest of our LAMP stack.

apt-get update
apt-get install -y apache2 mysql-server php8.0 php8.0-mysql

3. Create A Virtual Host For Apache And Enable It

Our next step is to create a virtual host for Apache so we can serve files out of the /home/vagrant directory.

To do this we need to create a basic configuration file. We’re going to create a “setup” directory in our project directory and create a file named “development.conf” with the contents below.

<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    DocumentRoot /home/vagrant/our-awesome-project/public

    <Directory /home/vagrant/our-awesome-project/public>
        Options Indexes FollowSymLinks
        AllowOverride None
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

This will set up a basic virtual host to serve whatever we put in the “public” directory of our project.

The commands we’re going to add to our Vagrantfile will copy this file to the appropriate directory inside our VM, enable it, disable the default site, and reload Apache so it notices the change.

cp /home/vagrant/our-awesome-project/setup/development.conf /etc/apache2/sites-available/development.conf
a2ensite development.conf
a2dissite 000-default.conf 
systemctl reload apache2

4. Create A Development Database And User

The last step is to create a development database and user. We almost always need to create at least one database per project and we recommend creating a specific user per project. We could set up the connection to use the root user but it’s a bad practice as it increases the attack surface of our database.

echo "create database development" | mysql 
echo "CREATE USER 'development'@'localhost' IDENTIFIED BY 'development'" | mysql 
echo "GRANT ALL PRIVILEGES ON development.* TO 'development'@'localhost';" | mysql 
echo "flush privileges" | mysql 

Bringing It All Together

Here is the completed Vagrantfile which includes a static IP address and a synced folder.

Vagrant.configure("2") do |config|
  config.vm.box = "generic/ubuntu2004"
  config.vm.network "private_network", ip: "192.168.33.10"

  config.vm.synced_folder ".", "/home/vagrant/our-awesome-project"

  config.vm.provision "shell", inline: <<-SHELL
    apt-get update
    apt-get install -y lsb-release ca-certificates apt-transport-https software-properties-common 
    add-apt-repository ppa:ondrej/php
    apt-get update
    apt-get install -y apache2 mysql-server php8.0 php8.0-mysql
    cp /home/vagrant/our-awesome-project/setup/development.conf /etc/apache2/sites-available/development.conf
    a2ensite development.conf
    a2dissite 000-default.conf 
    systemctl reload apache2

    echo "create database development" | mysql 
    echo "CREATE USER 'development'@'localhost' IDENTIFIED BY 'development'" | mysql 
    echo "GRANT ALL PRIVILEGES ON development.* TO 'development'@'localhost';" | mysql 
    echo "flush privileges" | mysql 
  SHELL
end

Now we can run vagrant provision to have it run the steps we’ve outlined in our Vagrantfile.

vagrant provision
==> default: Running provisioner: shell...
    default: Running: inline script
    default: Hit:1 http://archive.ubuntu.com/ubuntu focal InRelease
    default: Get:2 http://security.ubuntu.com/ubuntu focal-security InRelease [109 kB]
    default: Get:3 http://archive.ubuntu.com/ubuntu focal-updates InRelease [114 kB]
    default: Get:4 http://archive.ubuntu.com/ubuntu focal-backports InRelease [101 kB]
    default: Fetched 324 kB in 3s (106 kB/s)
    default: Reading package lists...

To test our setup we can create a very basic phpinfo.php file in our public directory.

<?php
phpinfo();

Conclusion

In this article, we discussed how to use Vagrant’s provisioning system to configure a basic LAMP development environment so we can finally start working on our project.