Deploy Laravel to AWS Beanstalk with GitLab CI (including testing, and staging/production branches)
Tags: aws, laravel, gitlab, tutorial
Published: 2019-09-18 12:57:07

_Photo by Dewang Gupta on Unsplash_

This is ~~a tutorial~~ the story of how one night I set out to streamline my deployment process and found myself hours later at 4am building new docker images. Hopefully it contains valuable information for those of you looking into setting up CI/CD for your Laravel app, so that you won't have to stay up all night like me.

## What we (I) want(ed) to accomplish

Quite simply, that on every push to `master` or `staging` phpunit tests are run automatically.

Additionally, that if all tests pass on the branch `staging` the app is automatically deployed to Elastic Beanstalk.

I left the `master` branch deployment to "manual" because I don't want an accidental push to deploy a new feature in production.

## Requirements

I'm trying to keep this concise and to the point, and for this reason it's expected that you already have the following ready (if you don't, don't worry, bookmark this article and come back later):

1. A working Laravel app in your local environment, with its git repository on GitLab, and two branches: `master` and `staging` (all pretty standard so far)
2. Two configured and running environments on AWS Elastic Beanstalk (you probably want them inside the same Beanstalk Application, I found [this guide](https://colintoh.com/blog/aws-elastic-beanstalk-survival-guide-introduction) very useful for this part)
3. A IAM user in your AWS Account with Elastic Beanstalk Full Access permissions (we will need the Access/Secret Keys of this user, _make sure you note them down somewhere_)
4. That you have installed the [EB CLI](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/eb-cli3-install.html) _and_ ran `eb init`

## 0. A bad default

If you've run `eb init` you'll notice that you now have a folder named `.elasticbeanstalk`. This folder is essential for GitLab, but for some reason the command also added a few lines in your `.gitignore` effectively making it impossible for GitLab to do its job. If you see something like this in your `.gitignore`:

```
# Elastic Beanstalk Files
.elasticbeanstalk/*
!.elasticbeanstalk/*.cfg.yml
!.elasticbeanstalk/*.global.yml
```

go ahead and delete those lines.

## 1. Getting started

Let's start from the end of the process, just to spice things up a little bit.

After every deployment of a Laravel application (and this is true for any server setup, not just EB) we need to make sure that our remote (gitignored) `/vendor` folder has all the needed packages, and that the structure of the remote database is up to date. This is done by running two commands: `$ composer install` and `$ php artisan migrate`.

Additionally, in our specific setup we need to tell EB that our app is served from the folder `/public`, and since SSL is **morally mandatory** by now we also probably want to tell Apache to redirect all _http_ requests to _https_.

This is a basic setup that should work well for 98% of the projects.

Now, we could _ssh_ into the remote machine and run a few commands, but fortunately Elastic Beanstalk provides us with Configuration Files (a set of YAML files in a `.ebextensions` folder that instruct the remote server on AWS to run certain commands right before and right after deployment) that suit our CI/CD workflow perfectly.

You are welcome to explore [the official documentation](https://docs.aws.amazon.com/en_pv/elasticbeanstalk/latest/dg/ebextensions.html) , but again fortunately yours truly went ahead and published the needed files on GitHub for your peruse.

In the root of your project run:

```
$ svn export https://github.com/mjsarfatti/laravel-eb-gitlab-ci/trunk/.ebextensions
```

This will create a `.ebextensions` folder, with four files inside. Feel free to inspect the files, and note that I went ahead and added a couple of goodies in there.

## 2. The GitLab YAML

To instruct GitLab on how and what to run during deployment we need to create and commit a `.gitlab-ci.yml` file. How GitLab CI works is out of the scope of this tutorial, but the file itself is pretty self-explanatory.

Add it to your project:


```
$ svn export https://github.com/mjsarfatti/laravel-eb-gitlab-ci/trunk/.gitlab-ci.yml
```

Then open up this file, and make sure to set the variable `MYSQL_DATABASE` to whatever your testing database name is (you'll probably find it in your `phpunit.xml` file).

### A useful bit to know 🎁

At times you may want to deploy even if tests fail (for example if you are testing the deployment itself). Or you want to push a simple edit (like a typo in your README, or in a warning message) and you don't want to use precious CI minutes for useless testing.

Our `.gitlab-ci.yml` has a friendly rule that will skip testing if the commit message contains the phrase `[skip tests]` (or a variation such as `[skip test]` or `[skip-tests]`.

## 3. Setting up GitLab CI for success

Log in to your GitLab account, and in your project's _Settings_ > _CI / CD_ add two variables, one named `AWS_ACCESS_KEY_ID` with, you got it, your IAM user access-key-id, and one named `AWS_SECRET_ACCESS_KEY` with... well you know what.

And that's it 🥳

Push away and enjoy your well deserved peace of mind.

## Oh, I was almost forgetting

So how is it that I spent the night building docker images, you ask?

Well, it all started when I discovered these AMAZING Docker images by Eduardo Bizarro preconfigured with PHP, Composer, Node and Yarn and available for PHP 7.1, 7.2 and 7.3: https://github.com/edbizarro/gitlab-ci-pipeline-php/

Crazy, I thought (it was still only 11pm), I'll just include those in my `.gitlab-ci.yml` and call it a night.

LOL.

The images don't include `eb` (why would they). So I tried to install it. But `eb` needs `pip`, and `pip` needs a few weird Python packages. Just `apt-get`, right? Wrong.

The images don't run on the root user (I guess it's a sane default?), which means I can't install any package during CI. I knew the way to go would be to fork the project, dive into their Dockerfile and customize it to my needs. But I've barely ever used Docker before, I didn't feel like embarking on a new adventure that late at night (funny, uh?).

So I fought, and I fought, and I fought. I tried all 3 Linux distributions they come with, and I tried all different methods to install `eb`, including installing all the tools in a local folder. Nothing worked.

So now it's 3am, and there I am with a simple choice: should I go to bed and rest or is this now the fight of my life?

You can imagine what happened.

I opened up https://hub.docker.com/, studied Bizarro's repo, studied the getting started and, lo and behold, an hour later I had:

- successfully reconfigured Bizarro's images to install EBCli, AWSCli and CLI53 (because why not)
- published the new image to Docker
- successfully used my first ever new Docker image to deploy my Laravel app to Elastic Beanstalk 🎉🎉🎉

So if you notice in our `.gitlab-ci.yml` file we are using `image: mjsarfatti/gitlab-ci-pipeline-php-aws:latest`.

Please note: this is a PHP 7.2 Debian-based image. You can't install packages. It's not thoroughly tested. But it works.

## Big thanks to

As always, I would be nowhere near where I am if it wasn't for the material, software and tutorials that accompanied me last night. In no particular order, big thanks to:

- https://github.com/edbizarro/gitlab-ci-pipeline-php/
- https://medium.com/@thimblot/deploying-a-flask-application-on-aws-with-gitlab-ci-cd-part-2-a175dc132950
- https://medium.com/dowebthings/how-to-deploy-from-gitlab-to-elastic-beanstalk-aws-cdac7b701004
- https://blog.powerupcloud.com/deploy-python-application-on-aws-elasticbeanstalk-using-gitlab-runner-8982fb26e4a4
- https://github.com/spiritix/lumen-aws-gitlab-continuous-deployment
- https://github.com/Cox-Automotive/aws-ebcli
- https://github.com/rennokki/laravel-aws-eb

***Comment below if this guide has been useful, and if you face any problem!***

<small>-- EDIT --
Added rennokki/laravel-aws-eb link</small>