Published on

Deploying a Drupal website with GitHub Actions

Since the integration of pipeline building tools into major git repository hosting services (GitHub Actions for GitHub, GitLab CI/CD for GitLab), there is no longer any excuse to continue manual deployment via FTP.

Personally more invested in the GitHub ecosystem than that of GitLab, I use GitHub Actions for the majority of my deployments with a pipeline that I have refined over the years.

This is the one I use for deploying my Drupal projects.

Checking out the code

name: Build and deploy
on: push

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

Here we create a pipeline called Build and deploy that runs on the latest version of Ubuntu. It is triggered as soon as code is pushed to the repository (here on all branches, but it is possible to scope this pipeline to the main branch only for example).

We then add a first step that retrieves the code via the actions/checkout action.

Install PHP dependencies via Composer

      - name: Validate composer.json
        run: composer validate --no-check-all

      - name: Install composer dependencies
        run: composer install --prefer-dist --no-progress

We then add 2 steps:

  • Validate composer.json: we validate that the composer.json file is compliant, a practice recommended by Composer
  • Install composer dependencies: we install the project dependencies in the pipeline

Building the front-end assets

      - name: Install NPM dependencies
        run: npm ci

      - name: Build assets
        run: npm run build
  • Install NPM dependencies: here we use the command npm ci (for Clean Install) instead of npm install, which is convenient for ensuring that the dependencies installed are with a fixed version specified in the package-lock.json file
  • Build assets : on lance ensuite la commande de build pour compiler les assets du front-end

Deploy files to a server via SSH

      - name: Install SSH Key
        uses: shimataro/ssh-key-action@v2
        with:
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          name: id_rsa
          known_hosts: unnecessary
          if_key_exists: fail

      - name: Deploy to server
        run: rsync -avzq --no-perms --no-owner --no-group --chown=www-data:www-data -e "ssh -o StrictHostKeyChecking=no" --exclude-from="${GITHUB_WORKSPACE}/rsync-exclude.txt" $GITHUB_WORKSPACE/ ${{ secrets.SSH_HOST }}:/var/www/html/

Several things are happening here:

  • Install SSH Key: we install an SSH key to access the remote server. Here I use a secret called SSH_PRIVATE_KEY defined in the repository to be deployed on the GitHub side. Note that for these 2 steps, I disabled the host verification with StrictHostKeyChecking=no to facilitate the configuration of the pipeline and the remote server.
  • Deploy to server: this is where everything happens. Once the project is compiled, I deploy it via rsync using the permissions defined on the server. The server connection address and the SSH user are defined in a single secret SSH_HOST of the form user@myserver.com. The path /var/www/html is hard-coded here but can also be saved in a variable.

    To avoid deploying files that have no place on the server, I use the exclude-from argument which allows me to read from an rsync-exclude.txt file a list of files/folders to exclude. At a minimum, this contains the .git, .github and node_modules folders.

Rebuild Drupal Cache

So far, the deployment steps are standard and can be used on most PHP projects as is.

Due to the way Drupal and its cache system work, it is often necessary to rebuild it so that it takes into account the latest file modifications, particularly when changing twig or PHP files.

So I add the next step to run the drush cr command to rebuild the Drupal cache directly on the server.

Since we have already configured the SSH key in the pipeline, we just have to run the command directly on the remote server.

      - name: Rebuild drush cache
        run: ssh -o StrictHostKeyChecking=no ${{ secrets.SSH_HOST }} "cd /var/www/html && vendor/bin/drush cr"

Complete pipeline

name: Build and deploy
on: push

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

      - name: Validate composer.json
        run: composer validate --no-check-all

      - name: Install composer dependencies
        run: composer install --prefer-dist --no-progress

      - name: Install NPM dependencies
        run: npm ci

      - name: Build assets
        run: npm run build

      - name: Install SSH Key
        uses: shimataro/ssh-key-action@v2
        with:
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          name: id_rsa
          known_hosts: unnecessary
          if_key_exists: fail

      - name: Deploy to server
        run: rsync -avzq --no-perms --no-owner --no-group --chown=www-data:www-data -e "ssh -o StrictHostKeyChecking=no" --exclude-from="${GITHUB_WORKSPACE}/rsync-exclude.txt" $GITHUB_WORKSPACE/ {{ secrets.SSH_HOST }}:/var/www/html/

      - name: Rebuild drush cache
        run: ssh -o StrictHostKeyChecking=no ${{ secrets.SSH_HOST }} "cd /var/www/html && vendor/bin/drush cr"

Bonus: Automatically warm up Drupal cache on deployments

If you want to automatically warm up the Drupal cache during deployments to ensure the fastest possible page load speed for all your visitors, I have written a specific article detailing the procedure to set up using the Warmer module.

No more excuses for manual deployments!