Skip to main content

In this guide, you will learn how to package your Laravel application into a machine image that can be loaded directly into Amazon Web Services EC2 instances. This is a different approach to the traditional way of choosing an instance image and type (for example Ubuntu on a t2.micro) and then adding your code onto the machine.

We will be reusing some of the code from my previous guide – reusing some of the code from my previous guide – reusing some of the code from my previous guide – reusing some of the code from my previous guide – Provisioning Laravel with Vagrant. We will be mainly looking at the provisioning part, where we use a bash script to update the system, install packages, and configure software.

Please note

Cost – This guide uses Amazon Web Services. Many of the processes we run in this guide require the AWS platform and this does mean that you may incur some costs on your AWS account. These should be very minor and we also try to stick to the ‘free tier’ to keep costs as low as possible. Unless you keep resources running, we are talking about a few dollars or less.

What is Packer?

Packer is an open source tool created by Hashicorp that allows you to create machine images or containers for multiple platforms from a single configuration. This means that you can write your configuration and easily create images that can run on Amazon Web Services, Google Cloud and Azure.

Installing Packer

To install Packer, we need to head over to the official website downloads section here: . You should see a list of Operating Systems and CPU architecture options. I’ll be using the Mac OS X 64bit version. Once you click the link then your download should start automatically.

Packer file

The download will be in the .zip format and contains a single executable binary. After extracting the binary file, I came across a few permission issues with this file. To overcome these issues and to allow us to call the ‘packer’ command system-wide, we need to move it using the following command:

sudo mv /Downloads/packer /usr/bin/

Note: If you come across an error, depending on your system you may have to run the following command instead;

sudo mv /Downloads/packer /usr/local/bin

You should now be able to run the ‘packer’ command in your terminal and see a similar output:


Congratulations, you have now installed Packer on your system. Let’s get started! Before we create a machine image using Packer, we need to learn about one of the core parts – the configuration template.

Configuration template

Packer requires a configuration template to create its images or containers, it uses this as its single source of truth. The configuration template is in the JSON format and is made up of multiple sections:


Code: builders

Required: Yes

The ‘builders’ section tells Packer which builder will be used to create the machine image or template. An example for AWS would be to use the “amazon-ebs” builder option which creates an Amazon Machine Image (AMI) backed by an EBS volume to be used by an EC2 instance.


Code: description

Required: No

The ‘description’ section allows you to write some information about the Packer template. This information can be seen when the “packer inspect” command is run. Using a description can be helpful if you have many Packer configurations or a team of developers / engineers, so that everyone understands.developers / engineers, so that everyone understands.

Min Packer Version

Code: min_packer_version

Required: No

The ‘min_packer_version’ section allows you to specify a minimum version of Packer that can parse the template. It may be that you have tested your configuration with Packer version 0.12.0, and it works, but there are issues with Packer version 0.11.0, so in this case you can set the minimum version to 0.12.0._version’ section allows you to specify a minimum version of Packer t

Post Processors

Code: post-processors

Required: No

The ‘post-processors’ section allows you to tell Packer what to do after the machine images or containers have been created. The ‘post-processors’ section can be seen as the housekeeping part. For example, you may need to compress a file or possibly upload any artifacts that the i

mage relies upon.


Code: provisioners

Required: No

The ‘provisioners’ section allows you to tell Packer how to provision the machine image or container. An example of a provisioner would be a shell script that updates the system then installs and configures software.


Code: variables

Required: No

The ‘variables’ section allows you to define variables to be used. An example of this could b

e creating a variable for your AWS access key and AWS secret key which will be used later by the AWS builder.

Creating a configuration template

Let’s start building our template. The template config file needs to be in the root of your folder. So if we have a folder called ‘packer-test’ and we want to run packer inside that folder, we will need to make sure the ‘config.json’ file is saved in there. Let’s continue by creating a basic config.json file;file is saved in there. Let’s continue by creating a basic config.json file;

"variables": {
"aws_access_key": "",
"aws_secret_key": ""

This is a really simple configuration and won’t actually achieve anything, but Packer has a tool built-in called ‘validate’, and we can utilize this tool by running the following command:

packer validate config.json

And you should see a similar output:

[~] packer validate config.json
Error initializing core: 1 error(s) occurred:

* at least one builder must be defined

How great is that? Packer has checked our config.json file, found an error, and told us what the error is. Let’s continue building our template file by adding our ‘builders’:

 "builders": [{
"type": "amazon-ebs",
"access_key": "{{user `aws_access_key`}}",
"secret_key": "{{user `aws_secret_key`}}",
"region": "us-east-1",
"source_ami": "ami-fce3c696",
"instance_type": "t2.micro",
"ssh_username": "ubuntu",
"ami_name": "packer-example {{timestamp}}"

Let’s learn more about what we’ve just added in;


Let’s check to make sure our file passes Packer’s validation:


Ok, now we know the configuration template is good to go, so we can actually build our image. We can do this by running the following command;

packer build -var “aws_access_key=[YOUR ACCESS KEY]” -var “aws_secret_key=[YOUR SECRET KEY]” config.json

This command can be broken down into three sections;

  1. We instruct Packer to run its ‘build’ function
  2. We pass two variables into the function using the -var flag followed by the variable name and its value. We set those values earlier in the configuration template.
  1. We finish off the command by telling packer which configuration template we want to use, and in this case it is ‘config.json’.

You will now see some green output in your terminal and see Packer’s step by step process. If everything is successful, then you should see a similar message:


Great! You have successfully built your first machine image using Packer. This is an Amazon machine image to be used with the Amazon Web Services platform.

This image does not really do anything at the moment so let’s add in a provisioner and get our Laravel application setup. Add the following provisioners block into your config.json file:

"provisioners": [{
"type": "shell",
"execute_command": "sudo -S bash '{{.Path}}'",
"script": ""

We will set the type to ‘shell’, and we will be running the command as a ‘sudo’ user or else we will get lots of permission failure errors and warnings. We will be using the ‘’ file as our script. Let’s look at our script and see what software we will be installing and configuring:


echo "Starting provision..."
sleep 30

echo "Updating system..."
sudo apt-get update

echo "Installing essentials..."
sudo apt-get install -y zip unzip python-software-properties curl git

echo "Installing and configuring MySQL database..."
debconf-set-selections <<< "mysql-server mysql-server/root_password password secret"
debconf-set-selections <<< "mysql-server mysql-server/root_password_again password secret"
apt-get install -y mysql-server mysql-client

mysql -u root -psecret -e "CREATE DATABASE homestead;"
mysql -u root -psecret -e "CREATE USER 'homestead'@'localhost' IDENTIFIED BY 'secret';"
mysql -u root -psecret -e "GRANT ALL PRIVILEGES ON homestead.* TO 'homestead'@'localhost';"
mysql -u root -psecret -e "FLUSH PRIVILEGES;"

echo "Installing Apache web server..."
sudo apt-get install -y apache2

echo "Installing PHP5 and components..."
sudo apt-get install -y php5 libapache2-mod-php5 php5-mcrypt php5-curl php5-mysql php5-cli php5-json php5-mcrypt
sudo php5enmod mcrypt
sudo a2enmod rewrite

VHOST=$(cat <<EOF
<VirtualHost *:80>
DocumentRoot "/var/www/html/latest/public"
<Directory "/var/www/html/latest/public">
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Require all granted

echo "${VHOST}" > /etc/apache2/sites-available/000-default.conf

sudo service apache2 restart

echo "Installing and configuring composer..."
curl -sS | php
sudo mv composer.phar /usr/local/bin/composer
composer self-update --update-keys

echo "Installing and configuring Laravel..."

mkdir /var/www/html/latest
composer create-project --prefer-dist laravel/laravel /var/www/html/latest "5.2.*"
sudo chmod -R 777 /var/www/html/latest
sudo chmod -R 777 /var/www/html/latest/storage

echo "Finished provisioning"

In the script above we are installing and configuring the software required to run Laravel by installing a web server (Apache), a database (MySQL) and any dependencies required (Composer). Now that we have created our ‘’ script, let’s start Packer’s build process, which we can do by running some of our previous commands:

packer validate config.json


packer build -var “aws_access_key=[YOUR ACCESS KEY]” -var “aws_secret_key=[YOUR SECRET KEY]” config.json

During the build process you should see any output from the system, including the echo statements used in the ‘’ file, so you will be able to see which step the process is currently at and where any errors may occur. Now that we have built our image, at the end of the process you will see a similar output:

==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs: AMIs were created:

us-east-1: ami-a69596b1

Your AMI ID will be different but remember this as we will be using it soon.

Using our AMI in Amazon Web Services

Login to your AWS dashboard, open up the “Services” tab, and navigate to the “Compute” section and click on the “EC2” link:


Once in the EC2 dashboard, on the left-hand side you should see an “IMAGES” menu, click on the “+” button to show its submenu. Click on the “AMIs” link, as shown:


You should now see a list of AMIs, and you may only see 2 if you have never created an AMI before or possibly run an EC2 instance. That’s ok as we have just created two images in this guide. Remember that AMI ID we received earlier from the Packer build process (mine was ami-a69596b1). Find this AMI in the list and let’s select it:


Click on the blue “Launch” button and a new screen will load. This screen will ask what sort of instance type you would like to use. I will be selecting the “t2.micro” instance as our application is not resource heavy and it’s part of the AWS free trial:


When you are ready, click the blue “Review and Launch” button which can be found at the bottom of the page. Our next step is looking into the security of our instance. Security is out of scope for this guide as it can add complexity to the process so we will be keeping the security settings simple.

Navigate down to the “Security Groups” section and click on the blue “Edit security groups” section. We will be using SSH and HTTP in this guide so let’s add them both, but restrict the access to our IP address only:


The instance should only allow you to use the HTTP and SSH services running on ports 22 and 80 respectively. Let’s continue by clicking the blue “Review and Launch” button. You should now see an overview of the instance we are about to launch. You can make any last changes or click on the blue “Launch” button.

You will now be asked about using a key pair (public key and private key) to be able to connect to your instance:


I’ve previously set up a key pair so I’ll select the “Choose an existing key pair” option and then choose my key pair which is “packer-test”. It’s really easy to create a new key pair by selecting the “Create a new key pair” option and downloading the .pem file. You will then have to agree to the statement to continue. Let’s finalize this process by clicking the blue “Launch Instances” button. You should now see a success message from AWS:


Click on the instance ID (in my case it’s i-79e03c6e) and you will then be taken to the instance dashboard which will show you what instances are currently running, or the states of other instances.


Further down the screen you should see a “description” tab that gives us more information about our selected instance. Let’s copy the Public DNS value:


Paste this into your browser address bar and load the URL, you should now see the Laravel 5 landing page!


Congratulations, your application is now running within its own Amazon Machine Image live on the AWS EC2 platform.

SSHing into your Instance

If you need to SSH into your instance, then you can do so by running the following command structure in your terminal / command line:



When you first try and SSH into your instance, you may receive a warning similar to this “Load key “[KEY PAIR].pem”: bad permissions Permission denied (publickey).” This is because the .pem file requires a certainly permission level. The fix is to ‘chmod’ your .pem file by running the following command – sudo chmod 0600 [KEY PAIR].pem

For my instance, I would use the following command:

ssh -i /Users/alexbraunton/Downloads/packer-test.pem

We use the ‘ubuntu’ user as this is the value we set the ‘ssh_username’ variable in our config.json template file. Once SSHd in, you can navigate through the system, edit your application code, update software, etc.

To terminate your instance which will stop the application and stop you being billed any further, head back over to your instance dashboard, select the current instance and right click to show the menu.


Click on the “Instance State” option and then “Terminate” to stop and delete your instance.


Comments are disabled for this guide.