Skip to content

Workflow Best Practices

When working with GitHub Actions workflows, there are many ways we can improve the functionality and efficiency for both maintainers and the workflows themselves. On this page we'll go through different workflow run events to help you understand and implement pipeline run functionality when needed. We'll also take a look at how to partition jobs to be more efficient and human-readable as well as how deployments integrate with GitHub Actions.

Workflow Events

There are multiple types of event triggers that can trigger a GitHub Actions pipeline, but one of the most common is the on: push trigger. This will trigger a pipeline when a push has been made. One of the first ways to make this pipeline better is to add the following code snippet:

name: example

on:
  push:
    branches:
      - "main"

jobs:
  ...

The code above defines the new trigger as one that will only trigger on commits to the main branch. This is great, but if this project is using trunk-based-development ( development that relies on branching off, developing new features, and merging those changes to the development branch ) then the action will only run code after it has be mergred into the main branch. If the branch to be merged into main isn't up-to-date, then any tests run on the branch won't reflect what will happen when the branch is merged. A solution to this is to add the following code:

name: example

on:
  push:
    branches:
      - "main"
  pull_request:
    branches:
      - "main"

jobs:
  ...

When this pipeline is run on pull_request, the pipeline checks out what would be the merged code of the two branches and then runs the pipeline. This ensures that if a feature branch is missing any breaking changes, then they will be caught in the PR pipeline.

These are just two possible events that can trigger your GitHub Actions pipelines, but there are many more. Most are internal to the repository that is hosting the workflow, but it is possible to define custom external triggers through the repository_dispatch event. It is also possible to run GitHub Actions pipelines manually by specifying the workflow_dispatch event as one of the triggering events (which may optionally accept parameters).

Here is an example of a workflow dispatch event with one input parameter

NOTE: The pull_request_target event, when used incorrectly, may compromise a repository. Unless you are regularly handling pull requests from forks, you most likely wont need to use this event.

Partitioning Jobs

Another common question folks have is when to separate out their workflows into multiple jobs. Jobs should be used to separate your workflow into logical sections. This increases the readability of your workflow file, generates a more understandable visual graph of your workflow in GitHub, and can decrease workflow run times when jobs are run in parallel. By default jobs defined in a workflow will run in parallel, but can be configured to depend on one or more jobs.

Imagine if you were to have a workflow that validated some source code by checking it's syntax and formatting, ran a vulnerability scan, built the code then finally ran unit tests. In this code block we will have a workflow that consists of only one job called "Do-My-Testing":

MyTestingWorkflow.yml

name: Testing

on: 
  pull_request:
    branches:
      - main

jobs:
  Do-My-Testing:
    runs-on: ubuntu-latest
    steps:
      - run: echo "checkout source code"
      - run: echo "install dependencies"
      - run: echo "syntax check source code"
      - run: echo "format source code"
      - run: echo "run vulnerability scanning"
      - run: echo "build source code"
      - run: echo "run unit tests"

When looking at this we can start to break steps of the job that could potentially run independently of others, and provide a clearer picture to what the job does, and gain some time back on waiting for this workflow to run. We split this out into mulitple jobs based on their functions and are able to run all pieces in parallel. With decoupled pieces we gain more configurable options such as to fail or pass the workflow based on job conditions, run on different architectures, enable/disable dependencies etc.

MyTestingWorkflow.yml

name: Testing

on: 
  pull_request:
    branches:
      - main

jobs:
  validate:
    name: Validate my code
    runs-on: ubuntu-latest
    steps:
      - run: echo "checkout source code"
      - run: echo "install validation dependencies"
      - run: echo "syntax check source code"
      - run: echo "format source code"

  vulnerability_check:
    name: Scan for vulnerabilities
    runs-on: ubuntu-latest
    steps:
      - run: echo "checkout source code"
      - run: echo "install vulnerability scanning dependencies"
      - run: echo "run vulnerability scanning"

  build_and_test:
    name: Build and test my code
    runs-on: ubuntu-latest
    steps:
      - run: echo "checkout source code"
      - run: echo "install build dependencies"
      - run: echo "build source code"
      - run: echo "run unit tests"

For a more detailed example and how it can be used in a real world scenario here is our mobile-pipeline-poc workflow which utilizes multiple jobs and artifacts.

See the full workflow output here.

Deployments

GitHub Actions allows users to customize and control deployments using many of the tools that we've gone over so far. For example, users can - Use specific events such as push, pull request, or workflow dispatch to trigger deployment workflows. - Use specific environments to set rules before running specific jobs. For example one can restrict which branch runs a specific workflow or limit access to specific branches. - Implement concurrent jobs and workflows to control the number of deployments running depending on the environment in use.

When deploying to specific environments in GitHub Actions, you can view your current and past deployments in the sidebar tab listed environments under contributors and over languages in the repo.

If you click on the environments tab, it takes you to a page within GitHub where you can explore your most recent deployments from the repo to each environment along with the branch, commit, status, user, and time the deployment was run.

Below, this page also offers a history section with all the information and status included for each deployment in chronological order. You can also filter out by specific environments if you need an environments overview or need to track down a specific deployment.

For more on deploying with GitHub Actions, check out their documentation on the topic that includes examples and links to related information.