Github recently introduced Github Actions which developers can use to build a CI/CD pipeline right from the Github repository. It uses simple yml files to build the workflows to run build, run tests & deployment. Recently I added a CI/CD pipeline for my site using Github Actions to run a basic build, lint, and deploy the changes to a Digital Ocean droplet.

The post assumes you have the following:
- A repository hosted on Github
- A composer based Drupal instance running on Digital Ocean
- For deployment, the Drupal instance is connected to the Github repository

Setting up a workflow

Workflows are custom automated processes that can be set up in Github repository to build, test, package, release, or deploy any project on GitHub. With workflows you can automate your software development life cycle with a wide range of tools and services. See Configuring a workflow for more information.

Github suggests several workflow setups right out of the UI based on your project which can be a good starting point. The workflows reside in the .github/workflows directory of your project where you can define them based on your need. So, one configuration yml file per workflow.

Setting up a Continuous Integration workflow

To set up a Ci workflow,  create a ci.yml in .github/workflows directory. The file can contain the steps like a composer build, static analysis, linting, running test, etc.

name: Build & lint project
on: [push]
    runs-on: ubuntu-latest
      - uses: actions/checkout@v2
      - name: Validate composer.json and composer.lock
        run: composer validate
      - name: Install composer dependencies
        run: composer install --prefer-dist --no-progress -vvv
      - uses: actions/setup-node@v1
          node-version: "14"
      - name: Install frontend dependencies
        run: cd web/themes/imalabya && yarn install
    runs-on: ubuntu-latest
      - uses: actions/checkout@v2
      - name: Install composer dependencies
        run: composer install --prefer-dist --no-progress -vvv
      - name: Run PHPCS with DrupalPractice on modules
        uses: docker://hussainweb/drupalqa:php7.4
          args: phpcs --standard="Drupal,DrupalPractice" -n --extensions="php,module,install,theme" web/themes/imalabya web/modules/custom --ignore=web/themes/*/dist/,web/themes/*/Gulpfile.js,web/themes/*/node_modules/

Let’s go step by step to explain the config file.

  • Name the workflow; this will show up on the Actions interface in Github. Again it's arbitrary but should be relevant to what the workflow does.
  • Tell Github when to trigger the workflow. Here you can add the branch or branches where you want to run the workflow. There are other configurations available like running the workflow on pull requests or on corn. For more information see: Triggering a workflow with events
  • Next, define the jobs in the workflow which will be executed in parallel unless a dependency is added using needs: job_1. Unlimited jobs based can be defined on the need as long as they are within the workflow usage limits. In the above configuration, I have defined 2 jobs viz. build & lint
    • Each job runs on an isolated environment described by the runs-on key. Here you can choose the environment the job needs to build on.
    • A job uses an action to run as part of a step in the job which is defined by the uses key. Here you can an action defined in public repositories or a docker image.
  • Github actions - build & lint demo completed
    A completed build job on Github Action.
    In the build job, first check-out the code to the latest version. The checkout action is important because as mentioned each job runs on an isolated environment. So, the environment will not have the code base unless the checkout operation is performed. Then the composer.json & composer.lock files are validated using composer validate. Next, composer install is run to install dependencies followed by a yarn install to install frontend dependencies. It's quite a trivial task just to ensure that the composer and yarn files are valid and there is no issue to run them on the server.
  • Github actions - lint job demo
    A completed lint job on Github Action.
    In the lint job, again first check-out the code. Since it's an isolated environment, install the composer dependencies. Here I have used drupalqa docker image by Hussain to run PHPCS on my custom module and theme files. The composer builds from the build job can be shared using artifacts, but since the jobs run in parallel it's fine to execute composer install in both the jobs.

On every push, Github Actions will now trigger the jobs defined and run a composer build and run PHPCS on the code for linting. This is a very basic build step but more integrations like running PHPUnit tests & Behat tests can be added similarly.

Setting up a continuous deployment workflow

Now that there is a continuous integration in place let's deploy the code to the Digital Ocean server. For the deployment workflow, create a deploy.yml file in the .github/workflows directories.

For deployment, I have used appleboy/ssh-action action to execute remote ssh commands and for the deployment script.

The appleboy/ssh-action requires the SSH host, username, password, keys, etc to establish a connection with the server. Now for security reasons, this confidential and sensitive information cannot be added in the configuration. These keys must be passed as encrypted environment variables to the configuration. Github provides Secrets to hold such information in an encrypted manner. A new secret can be added from the Secrets tab under the Settings of a Github repository.

Here is my deploy.yml file content

name: Deploy to Digital Ocean droplet
on: [push]
    runs-on: ubuntu-latest
    - name: Deploy to droplet using SSH
      uses: appleboy/ssh-action@master
        host: ${{ secrets.HOST }}
        username: ${{ secrets.USERNAME }}
        key: ${{ secrets.PRIVATE_KEY }}
        debug: true
        script: |
          cd ${{ secrets.DOCROOT }}
          git pull
          robo job:drupal-update

In the above yml file, using appleboy/ssh-action SSH into the server. In my droplet, password login is disabled so I have added my SSH private in the PRIVATE_KEY secret, but if password login enabled you can use the pass key to log in the server. Once logged in, execute the deployment. In my case, after pulling the latest changes I run the RoboFile.php file to execute the job:drupal-update job to run my deployment which basically installs composer dependencies, import pending configurations & builds the frontend. You can find the full file content here.


Github Actions is a great tool to integrate a seamless continuous integration & deployment pipeline for projects hosted on Github. It's still under active development and soon enough there will enhancements which will further increase developer experience.