GitHub action it is the GitHub CI/CD solution available out of the box to run custom integration and deployment pipelines called workflows. It is configurable directly from the repository.

CI / CD stands for Continuous Integration and Continuous Deployment, it’s a set of practices usually automated to get the code tested, functional and available in a production environment.

You can create a workflow from an existing template from the Actions tab in your repository. Or manually by creating a new yaml file in the .github/workflows folder.

Now the documentation is very well furnished, but I have compiled some most used syntax and how-to in this article to have a quick reference.

Run a workflow …

There are multiple events that can cause a workflow to run, here is how to use some of them in your action.

On a regular basis

By regular basis, it’s like a cron job, and it works the same way:

on:
  schedule:
    - cron: '0 1 * * 5'

You can find the cron syntax below straight from wikipedia, or use cron expression generator to set it up.

# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12)
# │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# │ │ │ │ │                                   7 is also Sunday on some systems)
# │ │ │ │ │
# │ │ │ │ │
# * * * * * <command to execute>

In our example the workflow would only run at 01:00 AM, only on Friday.

On a push

To trigger the pipeline each time you push to the repository:

on:
  push:
    branches: [master]
    tags:
      - 'v*'

Not mandatory, you can also trigger it the workflow only for certain branches (here master) or even for certain tags (here a regex to match any tag starting with v).

On a pull request

You can also have repository events as triggers, such as the pull request:

on:
  pull_request:

There’s also a trigger for the issues. You can also fine-tune it, check the documentation for reference.

Manually

This one is to be able to go in the action tab and run the workflow manually. To set it up, you need to use the workflow dispatch event:

on:
  workflow_dispatch:

You will see a new button Run workflow showing up in the action tab that will trigger the workflow_dispatch event. It’s not incompatible with other events, so you can have a workflow that runs on push and manually.

If you want to parametrise the button with some input, you can use the inputs keyword:

on:
  workflow_dispatch:
    inputs:
      logLevel:
        description: 'Log level'
        required: true
        default: 'warning'
        type: choice
        options:
          - info
          - warning
          - debug
      tags:
        description: 'Test scenario tags'
        required: true
        type: string

Use the ${{ inputs.logLevel }} to access the value in your workflow.

After another workflow

Let’s say you have a CI build workflow, and you wish to have another workflow to be executed after the end of this one. You can use the workflow_run event to trigger it.

name: CI notify

on:
  workflow_run:
    workflows: ["CI build"]
    types:
      - completed

This workflow needs to be named differently from the one it’s waiting for and will only run when the previous workflow succeeded.

jobs:
  on-success:
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    steps:
      - run: echo 'The triggering workflow passed'

You have access to the workflow’s conclusion, and can use it in your configuration.

Reuse a workflow …

Prevent duplication with the reusing capabilities of workflows with GitHub actions.

From a local file

You can use a workflow from a local file in your repository:

jobs:
  call-workflow-2-in-local-repo:
    uses: ./.github/workflows/workflow-2.yml

  other-job:
    runs-on: ubuntu-latest
    steps:
      - name: Run a script
        run: echo "Hello world"

This will run the workflow-2.yml file in the .github/workflows folder. Then you can have a job that runs after the completion of the workflow. You can call multiple workflows referencing them in the jobs section.

From a public repository

It’s also possible to use a workflow from a public repository (but organization can disable it). Any called workflow can have input parameters that can be passed to them:

jobs:
  call-workflow-passing-data:
    uses: octo-org/example-repo/.github/workflows/reusable-workflow.yml@main
    with:
      config-path: .github/labeler.yml
    secrets:
      envPAT: $

Here the reusable-workflow.yml from the octo-org/example-repo repository would be used (but it does not exist). To pass secrets we use the secrets keyword, and to pass parameters we use the with keyword.

Run that workflow in …

In a specific folder

You can run a step in a specific folder by using the working-directory option. It can be configured for all steps using the default settings which is at the root like the jobs keyword.

defaults:
  run:
    working-directory: src

Here the build step will use the src folder as the root folder. Or directly in the step if you need to execute an action in a different folder:

jobs:
  build:
    steps:
      - uses: actions/checkout@v3
      - name: Install dependencies
        run: npm i
        working-directory: src/frontend

The working-directory is relative to the root of the repository and will override at the step level the one configured in the defaults.

In a container image

If you need to run your step in a container, like a docker image (you may want to set up the credential if it’s a private registry).

name: CI

on: [ push ]

jobs:
  container-job:
    runs-on: ubuntu-latest
    container:
      image: node:20
      env:
        NODE_ENV: development
      ports:
        - 80
      volumes:
        - my_docker_volume:/volume_mount
    steps:
      - name: Run in docker
        run: echo "Hello from inside the container"

If you don’t need volumes, ports or environment variables, you can just limit yourself to the image name for the container’s configuration.

In a user defined matrix

You can also use a matrix to perform a job with multiple values, here an example with multiple ruby versions:

    runs-on: ubuntu-latest
    strategy:
      matrix:
        ruby-version: [ '2.7', '3.0', '3.1' ]
    steps:
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: ${{ matrix.ruby-version }}

It is user defined, and you define more than one (it’s then called a multi-dimension matrix)

In spite of a failure

If you want the workflow to continue even if a step fails, you can add this to your step:

jobs:
  steps:
    - name: My first action
      run: exit -1
      continue-on-error: true

This can be useful when you want to run the following steps that may not be dependent on the failed one.