September 20th, 2020
Deploying Laravel applications to a shared server using GitHub Actions
#github-actions
#laravel
Introduction
I recently worked on a project where the staging server was on a shared hosting plan with users having very limited privileges. At the time I would push updates using git-FTP, a tool that uses git to upload only changes to the FTP server. Without automation, the git-FTP initial push process would take me several hours. I decided to try out GitHub Actions to offload the workload from my machine.
This post covers my workflow file which uses a combination of SSH and git-FTP for the staging deployment job. I am assuming that you have some knowledge of GitHub Actions.
Before we get to the workflow
The workflow connects to the server through SSH to run commands and files are transferred through FTP. On your GitHub account, you need to have saved secrets for these credentials:
- FTP user credentials.
- SSH private key.
There were some issues with installing composer on the staging server and I did not want to track the dependencies on git, so before deployment, I download the dependencies and create an archive.
Also note that this post covers the deploy job after the initial setup of git-FTP that creates a log file on the staging server.
The Staging Workflow
The 3 jobs on the workflow file:
-
setup — cleans up the vendor directory on the server.
setup: runs-on: ubuntu-latest steps: - name: Remove vendor directory uses: fifsky/ssh-action@master with: command: | cd www/site_directory/ [ -d ./vendor ] && rm -rf ./vendor host: https://staging-server.com user: system_user key: ${{ secrets.SSH_PRIVATE_KEY }}
-
deploy — Download dependencies and deploy the site.
deploy: name: Deploy to the staging server runs-on: ubuntu-latest needs: setup steps: - uses: actions/checkout@v2.1.0 with: fetch-depth: 2 - name: Install Composer Dependencies run: composer install --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist - name: Create zipped vendor directory uses: montudor/action-zip@v0.1.0 with: args: zip -qq -r ./vendor.zip ./vendor - name: FTP-Deploy-Action uses: SamKirkland/FTP-Deploy-Action@3.1.1 with: ftp-server: ftp://ADDRESS_HERE ftp-username: FTP_USER ftp-password: ${{ secrets.FTP_PASSWORD }}
Since
[vendor.zip](http://vendor.zip)
isn’t tracked using git, it is added to the.git-ftp-include
file to be always pushed. Creating an archive lets the transfer to be a single file. This actually reduced my workflow run from minutes to a few seconds, which I thought was a pretty cool trick. -
post-deploy — Clean up
[vendor.zip](http://vendor.zip)
and run several artisan commands.post-deploy: runs-on: ubuntu-latest needs: deploy steps: - name: Run migrations and seeders, clear cache for views, config and routes uses: fifsky/ssh-action@master with: command: | cd www/site_dir/ unzip -qq ./vendor.zip rm -f vendor.zip php artisan migrate:fresh --seed php artisan config:clear php artisan view:clear php artisan route:cache host: https://SERVER_ADDRESS user: USER key: ${{ secrets.USER_PRIVATE_KEY }}
Running the Workflow
In the project I set the workflow to run on push to a staging branch which is only gets updates from master pull requests. Here is the whole workflow file.
name: Pushing to the staging server
on:
push:
branches:
- staging
jobs:
setup:
runs-on: ubuntu-latest
steps:
- name: Remove vendor directory
uses: fifsky/ssh-action@master
with:
command: |
cd www/site_dir/
[ -d ./vendor ] && rm -rf ./vendor
host: https://ADDRESS_HERE
user: USER
key: ${{ secrets.USER_PRIVATE_KEY }}
deploy:
name: Deploy
runs-on: ubuntu-latest
needs: setup
steps:
- uses: actions/checkout@v2.1.0
with:
fetch-depth: 2
- name: Install Composer Dependencies
run: composer install --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist
- name: Create zipped vendor directory
uses: montudor/action-zip@v0.1.0
with:
args: zip -qq -r ./vendor.zip ./vendor
- name: FTP-Deploy-Action
uses: SamKirkland/FTP-Deploy-Action@3.1.1
with:
ftp-server: ftp://ADDRESS_HERE/
ftp-username: FTP_USER
ftp-password: ${{ secrets.FTP_PASSWORD }}
post-deploy:
runs-on: ubuntu-latest
needs: deploy
steps:
- name: Run migrations and seeders, clear cache for views, config and routes
uses: fifsky/ssh-action@master
with:
command: |
cd www/site_dir/
unzip -qq ./vendor.zip
rm -f vendor.zip
php artisan migrate:fresh --seed
php artisan config:clear
php artisan view:clear
php artisan route:cache
host: https://SERVER_ADDRESS
user: USER
key: ${{ secrets.USER_PRIVATE_KEY }}
Conclusion
I would not recommend using this workflow in its current state for deployments to a production server since users might get unintended interruptions when you delete the vendor directory in the setup job. In the scenario where you have composer installed on your server, you would get rid of the setup job and the dependency installation step in the deployment job. It is also a good idea to set the application in maintenance mode when you are updating the composer dependencies or making a deployment update.