In this blog post, I would like to share a little about workflows on GitHub. In a nutshell, workflows allow you automate stuffs on GitHub, like continuous integration, continuous deployment, make bots that automatically comment on issues, make bots that automatically create pull requests from issues etc. This post contains a hands-on lab to follow along.
You can view the completed code in this repository
Set Up
- Fork the repository
- Clone the repository release v1.
1
2
3
4
5
6
7
8
| git clone git@github.com:trinuro-organization/gha-ci-example-fork.git # change this
cd .\gha-ci-example-fork\ # change this
git remote add upstream https://github.com/trinuro/gha-ci-example
git fetch --tags upstream
git push --tags
git checkout v1
git switch -c myBranch
|
- A quick test to see whether it works:
Desired Output:
- Inside the repository, you will see 3 important files:
example_module.py
: Just a simple python moduletest_counting_sort.py
and test_find_missing_number.py
: Tests cases for said module
- Navigate to the the github repository> Actions tab and enable Actions.

Workflows
- A workflow uses the YAML syntax
- A workflow must have the following “root” keys
1
2
3
4
| on:
# identifies the events that trigger this workflow
jobs:
# identifies the jobs to run
|
Events that trigger workflows
- There are many events that can trigger workflows.
- A few common ones:
push
: When a commit is pushed to GitHubpull_request
: When a pull request is created (Before it is merged)workflow_dispatch
: Manual trigger (Gives you a button to trigger said workflow)repository_dispatch
: Trigger due to events outside the repo (Very interesting, this one)
- To add an event trigger, add it under
on
1
2
3
4
5
6
7
8
| on: push # works for single event trigger
on: [push, pull_request] # multiple events
# more granular event trigger
on:
push:
branches: [main]
pull_request:
branches: [main]
|
- In our example, let’s say we want to define 4 event triggers.
1
2
3
4
5
6
7
8
9
10
| on:
push:
branches: ['main']
pull_request: # trigger when pr is created to main branch
branches: ['main']
workflow_dispatch: # manual trigger
repository_dispatch: # trigger via outside events
jobs:
# identifies the jobs to run
|
Jobs
- We can define multiple jobs that run during a workflow
- Each job is run inside its own independent container.
- Each job can has multiple steps (that run in the same container)
- Compulsory options in a job are
runs-on
: Runs on a certain kind of container. There are 3 options on GitHub:- Ubuntu-latest
- MacOS-latest
- Windows-latest
steps
: Define the steps in an action
- There are two “kinds” of steps. Different keyword is used for both of them.
- Steps that run GitHub Actions:
uses
- Steps that run commands on the container:
run
- Let’s define a job with one step: Greeting the user.
1
2
3
4
5
6
7
8
9
| # snip
jobs:
test: # name of job (can be "job1" etc)
runs-on: ubuntu-latest # use linux machine
steps:
- name: "Hello World" # not compulsory but aids in debugging
# use run to execute commands in the container
run: echo "Hello $GITHUB_ACTOR, happy learning!"
|
Output:
7. That is cool, but let’s clone our repository in the container so that we can examine the code inside. For this, we will use a pre-made action: actions/checkout
1
2
3
4
5
6
7
8
9
| jobs:
test:
runs-on: ubuntu-latest
steps:
- name: "Hello World"
run: echo "Hello $GITHUB_ACTOR, happy learning!"
- name: "Import Repository"
# "uses" keyword allows us to use a github action
uses: actions/checkout@v4
|
- Learnt the fact that the container does not contain the repo the hard way lol

- Next, we will execute the Python test code
1
2
3
4
5
6
7
8
9
10
11
| jobs:
test:
runs-on: ubuntu-latest
steps:
- name: "Hello World"
run: echo "Hello $GITHUB_ACTOR, happy learning!"
- name: "Import Repository"
uses: actions/checkout@v4
- name: "Run Unit Test"
# we can run multiline commands like this
run: python -m unittest
|
- Just for fun, this is how you run a multiline step
1
2
3
4
5
6
7
8
9
10
11
12
13
| jobs:
test:
runs-on: ubuntu-latest
steps:
- name: "Hello World"
run: echo "Hello $GITHUB_ACTOR, happy learning!"
- name: "Import Repository"
uses: actions/checkout@v4
- name: "Run Unit Test"
# we can run multiline commands like this
run: |
ls -la
python -m unittest
|
Output:

Other Workflow Triggers
- There are two unique workflow triggers that we defined, workflow dispatch and repository dispatch. Let’s see how to trigger them.
- To trigger
workflow_dispatch
, navigate to your GitHub Repo > Actions Tab > Click “Python Test” on the left sidebar. Click on “Run workflow” to trigger the workflow.

- To trigger
repository_dispatch
, you need to send a post request to a github endpoint.- First, create a fine-tuned access token. Go to account settings> Developer Settings > PAT > Fine-grained tokens and press Generate New Token

- The fine-grained token must have the following permission set:
- “Contents” repository permissions (read and write)
- Send a HTTP request to the relevant endpoint.
1
2
3
4
5
6
7
| curl -L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer github_pat_..<snip>.." \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/<accName>/<repo-name>/dispatches \
-d '{"event_type":"my_input","client_payload":{}'
|
Output:

- “Repository dispatch triggered”
Conclusion
- This is a very simple example of a continuous integration workflow using GitHub.
- Automated workflows are quite powerful as it frees developers from mundane testing tasks and in terms of security, encourage a shift left in security.
- Further reading:
- GitHub Workflow Syntax: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions