Building and deploying a simple PHP application using GitHub Actions

GitHub Actions is a versatile CI/CD platform that can be used for free. Here's how to build and deploy a PHP application using Composer for dependencies.

December 12th, 2022
Building and deploying a simple PHP application using GitHub Actions

A common trouble point that many people come across when developing a PHP application is deploying their changes automatically to a web server. In fact, it doesn't need to be complicated, GitHub Actions is a CI/CD platform that can be used freely by anyone with a GitHub account (within certain limits). Here's an example of how to build an application with Composer and a Gulp task and deploy it to a remote server. However, if your application is more simple, you can always remove a few steps.


GitHub Actions are configured using a YAML file in your repository. In this file, you can create jobs with a certain number of steps. In fact, there are many pre-made jobs available on the GitHub Marketplace, these can be combined to create a pipeline.

Creating a workflow

First of all, it goes without saying, but the code must be hosted on GitHub for this to work. Once that's taken care of, start by creating a file using the following directory structure in your project, either via your local machine or the web interface :


We're using main as the name of our workflow, but it could be anything you want. It can be created easily by clicking on the "Actions" tab and selecting a blank workflow.

Blank Action

Setting up our pipeline

In this example, we're going to be creating a couple of jobs with multiple steps: one to build and one to deploy. The first step of our pipeline is to tell it when to run. In our case, we want it to run whenever there is a commit or push on the main branch.

However, with such a structure, we must exercise branch discipline to prevent a work in progress build from being deployed:

When changes are ready to be deployed, the develop branch can be merged into the main branch.

Naming and trigger

The first part of the file contains the name of the pipeline as well as the trigger condition. In this case, it will run when a commit is made on the main branch.

name: Build and Deploy

    branches: [ main ]

Build job

The following configuration is used to create our build job. Here are a few notable properties:

    runs-on: ubuntu-latest
      - uses: actions/checkout@v3

      - name: Setup PHP
        uses: "shivammathur/setup-php@v2"
          php-version: 8.2

      - name: Install Composer packages
        uses: "ramsey/composer-install@v2"

      - name: Upload artifact
        uses: actions/upload-artifact@v3
          name: app
          path: |

In this job, we have 4 steps:

Build static assets with Node (optional)

If you use a task runner such as Gulp to automate building your static assets such as compiling SCSS, minifying JS or optimising images. This job is a good place to run it. Adding the following steps to the build job will provide a Node environment, install the required packages and run a npm script.

- name: Setup Node.js
  uses: actions/setup-node@v3
    node-version: 18.x
    cache: 'npm'

- name: Install Node packages
  run: npm install

- name: Build assets
  run: npm run build

In the last step, you can run any npm script defined in your package.json file. If required, add NODE_ENV: production to the environment.

Deploy job

The last part of the process is to deploy our built application to a web server. We're going to use the rsync utility, which is a tool that allows you to synchronise a local directory and a remote directory. It will create missing files, update changed files and delete removed files for us.

    runs-on: ubuntu-latest
    needs: build
      - name: Download artifact
        uses: actions/download-artifact@v3
          name: app
      - name: Deploy with rsync
        uses: burnett01/rsync-deployments@5.2
          switches: -avzr --delete
          path: .
          remote_path: /var/www/mysite
          remote_host: ${{ secrets.DEPLOY_HOST }}
          remote_user: ${{ secrets.DEPLOY_USER }}
          remote_key: ${{ secrets.DEPLOY_KEY }}

This job uses the needs property, so it depends on the build job. Firstly, we download the previously uploaded artifact from the previous job and subsequently upload it to our web server root. Indeed, on a typical Apache/NGINX server, the virtual host root will be located in /var/www/ 1.

In our case, we're using an SSH private key rather than a password. You can check the rsync deployment repo for more options.

Notice that some values are stored in the secrets variable, these are configured in the repository settings.

Adding secrets

Navigate to your repository settings, the Secrets section and the Actions menu to edit the secrets.

Action Settings

Using the "New repository secret", you can create a new secret variable.

Create a new secret

In this example, you will need to create the following secrets:

Running the pipeline

Now, when a commit is pushed to the main branch, the pipeline will run. You can view the result from the "Actions" tab.

Running the action

The preceding screenshot, shows the two jobs that ran and produced a single artifact. Once completed, the server is up-to-date with the latest changes.

Where to go from here

GitHub Actions is a very flexible CI/CD platform, and there are many improvements that could be made to this example. Here are a few ideas:

The possibilities are endless, all depends on what you aim to achieve with your application.

  1. On your server, you will need to make sure that your user has write access on the web server root. Either by changing the owner of the directory to the deployment user or adding the deployment user to www-data and making the directory group writable.