Deploy Laravel to AWS Beanstalk with GitLab CI (including testing, and staging/production branches)
Manuele J Sarfatti
Posted on September 18, 2019
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):
- A working Laravel app in your local environment, with its git repository on GitLab, and two branches:
master
andstaging
(all pretty standard so far) - Two configured and running environments on AWS Elastic Beanstalk (you probably want them inside the same Beanstalk Application, I found this guide very useful for this part)
- 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)
- That you have installed the EB CLI 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 , 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!
-- EDIT --
Added rennokki/laravel-aws-eb link
Posted on September 18, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.