Push deploy a Laravel application for free with GitHub Actions
How to set up push deploys for Laravel applications with GitHub Actions
Note: An updated version of this article was published on Laravel News
For many people/teams, it makes sense to use Ploi (referral link) or Forge. You get automatic server provisioning, push deploys, backups, and other features. You can also use a Platform-as-a-Service like Heroku — you'll get push deploys but won't get servers.
However, if you have experience in configuring servers, you might be interested only in the push to deploy part and don't want to pay for other services just to get this one feature.
Here's how you can configure push deploys yourself, for free, using GitHub Actions.
Prerequisites
- A configured server running nginx with PHP
- A GitHub repository
A few notes
- Front-end assets are being compiled by GitHub. Not your local computer, nor your server.
- Tests are being run locally, not in the CI Action. The reason for this is that you might want to deploy a hot-fix that would break tests. Feel free to change this.
The git set up
- Some
public/
assets are in.gitignore
, but are built on GitHub -
master
branch is used for development -
production
is pushed and deployed -
deploy
is created by the Action, by takingproduction
and adding a commit with the built assets
So the code flows like this: master --> production --> deploy
.
1. Server deployment script
Add this bash script to your repository, and name it server_deploy.sh
:
#!/bin/sh
set -e
echo "Deploying application ..."
# Enter maintanance mode
(php artisan down --message 'The app is being (quickly!) updated. Please try again in a minute.') || true
# Update codebase
git fetch origin deploy
git reset --hard origin/deploy
# Install dependencies based on lock file
composer install --no-interaction --prefer-dist --optimize-autoloader
# Migrate database
php artisan migrate --force
# Clear cache
php artisan optimize
# Reload PHP to update opcache
echo "" | sudo -S service php7.4-fpm reload
# Exit maintenance mode
php artisan up
echo "🚀 Application deployed!"
The process explained:
- We're putting the application into maintenance mode and showing a sensible message to the users.
- We're fetching
deploy
and hard resetting the local branch to the version onorigin
- We're updating composer dependencies based on the lock file
- We're running database migrations
- We're updating Laravel & php-fpm caches
- We're putting the server back up
A few notes:
- The server is on the
deploy
branch - We're putting the application down for the shortest duration possible -
composer install
, migrate, update cache. The app needs to go down. It would be irresponsible to simplygit pull
and migrate on a production server. The code and database state need to be in sync. -
artisan down
exits with 1 when the app is already down, which is why we're doing the() || true
wrapping, to be able to re-deploy after a failed deploy, without manual intervention.
2. Local deployment script
You run this script in your local environment when you want to deploy to production. Ideally, if you work in a team, you'll also have a CI Action with the phpunit
safeguard for pull requests targeting the production
branch.
Store this as deploy.sh
:
#!/bin/sh
set -e
vendor/bin/phpunit
(git push) || true
git checkout production
git merge master
git push origin production
git checkout master
This script is simpler. We're running tests, pushing changes (if we have not pushed yet; it's assumed we're on master
), switching to production
, merging changes from master
and pushing production
. Then we switch back to master
.
3. The GitHub Action
Store this as .github/workflows/main.yml
name: CD
on:
push:
branches: [ production ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/[email protected]
with:
token: $
- name: Set up Node
uses: actions/[email protected]
with:
node-version: '12.x'
- run: npm install
- run: npm run production
- name: Commit built assets
run: |
git config --local user.email "[email protected]"
git config --local user.name "GitHub Action"
git checkout -B deploy
git add -f public/
git commit -m "Build front-end assets"
git push -f origin deploy
- name: Deploy to production
uses: appleboy/[email protected]
with:
username: YOUR USERNAME GOES HERE
host: YOUR SERVER'S HOSTNAME GOES HERE
password: $
script: 'cd /var/www/html && ./server_deploy.sh'
Explained:
- we set up Node
- we build the front-end assets
- we force-checkout to
deploy
and commit the assets - we force-push the
deploy
branch toorigin
- we connect to the server via SSH and execute
server_deploy.sh
in the webserver root
Note that you need to store two secrets in the repository:
- a Personal Access Token for a GitHub account with write access to the repository
- the SSH password
Usage
With all this set up, install your Laravel application into /var/www/html
once and checkout to the deploy
branch.
For all subsequent deploys all you need to do is run this command from master
in your local environment:
./deploy.sh
Or you can merge into production
.
Performance and robustness
This approach is robust because it makes it impossible for a request to be received when the codebase and database are out of sync — thanks to artisan down
. And it's also quite performant, with only the necessary things happening on the server, minimizing downtime.
See how this action runs:
The Deploy to production step took only 13 seconds, and the application downtime is actually shorter than that — part of the 13 seconds is GitHub setting up the appleboy/ssh-action
action template (before actually touching your server).
Hope you found this useful.
You can sign up to my newsletter here or follow me on Twitter @samuelstancl.
Comments