A working gitlab Laravel pipeline with PHPUnit, code style, and more
How we deploy our PHP/laravel applications using environments and quality assurance checks.
Intro
I am a big fan of process optimization and I love trying out new ways to develop, deploy, and work on big software projects.
As head of a software development agency in Vienna, Austria, I was lucky enough to have worked on over 100 big web and mobile applications over the last 15 years with startups, international companies, and other amazing developers to try, fail and refine these processes over and over again.
The following approach is our current approach to deploying laravel projects.
To get this out of the way: If you have feedback, suggestions on that, please share them with us in the comments! I would love to hear your thoughts and the approach that works well for you.
We use gitlab for continuous integration/deployment, but this should work similarly also with other tools like Jenkins, ….
More information about the environment approach
I already wrote a detailed blog post about the reasoning and workflow for development, staging, and production environment and how we deal with it.
You can find it here:
How to set up your staging environment for web applications
If you are confused or need more information on how this gitlab-ci file came to be or detailed instruction on how to set your server and gitlab up to work with it, you can find it here:
Deploy PHP/Laravel applications using Gitlab CI/CD
Our gitlab-ci file
To make sure our code works and looks nice, we also run a few quality assurance tasks on it, before we deploy.
Here is our full gitlab-ci.yml file
image: lorisleiva/laravel-docker:7.4 # the number here resembles the php version, so you might have to adjust it to 8.0, ...## templates.init_ssh: &init_ssh |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config.change_file_permissions: &change_file_permissions |
find . -type f -not -path "./vendor/*" -exec chmod 664 {} \;
find . -type d -not -path "./vendor/*" -exec chmod 775 {} \;### the stages we go throughstages:
- build
- test
- deploy## preparecomposer:
stage: build
cache:
key: ${CI_COMMIT_REF_SLUG}-composer
paths:
- vendor/
script:
- *init_ssh
- composer install --prefer-dist --no-ansi --no-interaction --no-progress --no-scripts
- cp .env.gitlab .env
- mv .env.testing .env.testing--not-used
- cp .env.testing.gitlab .env.testing
- touch database/database.sqlite
- php artisan app:install --refresh --seed --demoartifacts:
expire_in: 1 month
paths:
- vendor/
- .env
- .env.testing
- database/database.sqlite
only:
- master
- staging
- production
- development## QAcodestyle:
stage: test
dependencies: []
script:
- phpcs --standard=PSR2 --extensions=php --ignore=app/Support/helpers.php app
only:
- master
- development
- productionphpunit:
stage: test
dependencies:
- composer
script:
- apk add --update-cache xvfb dbus ttf-freefont fontconfig wkhtmltopdf
- phpunit
artifacts:
expire_in: 1 month
paths:
- .env
- .env.testing
- report
- database/database.sqlite
only:
- master
- development
- production## DEPLOYMENTdeploy_development:
stage: deploy
script:
- eval $(ssh-agent -s) && echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
- *init_ssh
- *change_file_permissions
- php artisan deploy {MY_DOMAIN}.devserver.at -s upload
environment:
name: development
url: https://{MY_DOMAIN}.devserver.at
only:
- developmentdeploy_staging:
stage: deploy
script:
- eval $(ssh-agent -s) && echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
- *init_ssh
- *change_file_permissions
- php artisan deploy {MY_DOMAIN}.stagingserver.at -s upload
environment:
name: staging
url: https://{MY_DOMAIN}.stagingserver.at
only:
- masterdeploy_production:
stage: deploy
script:
- eval $(ssh-agent -s) && echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
- *init_ssh
- *change_file_permissions
- php artisan deploy {MY_DOMAIN} -s upload
environment:
name: production
url: https://{MY_DOMAIN}
only:
- production
Here we have added the following things:
in composer step:
We defined alternative .env files for gitlab and for running our tests.
They contain different environment settings, like the database connection (they use sqllite as the main database inside the gitlab pipeline and also change the APP URL to use `http://localhost` if we have e2e tests)
- cp .env.gitlab .env
- mv .env.testing .env.testing--not-used
- cp .env.testing.gitlab .env.testing
- touch database/database.sqlite
- php artisan app:install --refresh --seed --demo
checking code style:
Here we check to make sure that all of our developers used the PSR2 coding style.
codestyle:
stage: test
dependencies: []
script:
- phpcs --standard=PSR2 --extensions=php
only:
- master
- development
- production
running PHPUnit tests:
Using this we run the PHP unit tests and also save the report and SQLite database file as an artifact, so we can download and debug it if something went wrong.
phpunit:
stage: test
dependencies:
- composer
script:
- phpunit
artifacts:
expire_in: 1 month
paths:
- report
- database/database.sqlite
only:
- master
- development
- production
Thank you very much for taking the time to read this.
If you went this far down, I would really appreciate your comment and input.
How are you guys doing it?
I will post more about DevOps, deployment, coding (especially react, flutter and laravel) in the future, so follow me to read more ;)