Skip to main content

Command Palette

Search for a command to run...

GitHub Actions Beginner to Advanced📚

Part-1

Updated
•9 min read

What is CI/CD

Before CI/CD, software development followed a traditional approach where multiple teams worked sequentially. The development team would write code and push it to repositories like GitHub. Then the integration team would manually build and merge the code, and finally the testing team would perform end-to-end testing.

This approach had several challenges:

  • Dependency on multiple teams

  • Manual handoffs between stages

  • Increased chances of integration errors

  • Slower delivery to clients

To overcome these issues, CI/CD (Continuous Integration and Continuous Delivery/Deployment) was introduced.

With Continuous Integration (CI):

  • Developers frequently push code to the repository

  • Each push automatically triggers a pipeline

  • Tools like Jenkins or GitHub Actions execute the pipeline

  • The pipeline performs:

    • Build

    • Unit testing

    • Basic validations

This ensures that integration happens continuously and issues are detected early without manual intervention.

With Continuous Delivery/Deployment (CD):

  • After successful CI, the application is automatically:

    • Delivered to staging environments (Continuous Delivery), or

    • Deployed to production (Continuous Deployment)

Overall, CI/CD does not replace teams but automates their repetitive tasks, reducing manual effort and improving efficiency.

As a result:

  • Faster feedback to developers

  • Early detection of bugs

  • Reduced manual work

  • Improved code quality

  • Faster and more reliable releases (often 40–50% improvement in delivery time)


CI/CD Using GitHub Actions

Now let’s map CI/CD into GitHub-native world.

GitHub Actions is an event-driven automation platform built into GitHub that allows you to define CI/CD workflows as code.

Here everything is triggered by an Event

Some events are below

  • push

  • pull_request

  • workflow_dispatch

  • schedule

  • release

Core Components of Github Actions are

Workflow -----> Job ----- > Steps -----> Action

Workflow

A workflow is the complete automation pipeline.

This is a Yaml File in .github/workflow/*.yml

Job

A job is a group of steps that run on a runner (machine).

  • Runs on VM or container

  • Each job is isolated

  • Jobs can run:

    • Parallel (default)

    • Sequential (using needs)

A runner is the machine where your job runs.

Here runner is a like a node in jenkins means we can use github provided runnners or we can make self hosted runners

Github hosted runners examples

runs-on: ubuntu-latest
runs-on: ubuntu-22.04
runs-on: windows-latest
runs-on: macos-latest
runs-on: ubuntu-22.04-arm

Self hosted runners examples

runs-on: self-hosted
runs-on: [self-hosted, linux, ec2]
runs-on: [self-hosted, linux, onprem]

Step

A step is an individual task inside a job.

It can do

  • Checkout code

  • Install dependencies

  • Run tests

  • Build app

steps:
  - name: Install dependencies
    run: npm install

  - name: Run tests
    run: npm test

Action

An action is a pre-built reusable component. it's like a plugin

- uses: actions/checkout@v4

Example of simple workflow

name: CI Pipeline

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4   # Action

      - name: Install
        run: npm install           # Step

      - name: Test
        run: npm test              # Step

Jenkins VS GitHub Actions

Aspect Jenkins GitHub Actions
Hosting Self-hosted SaaS (GitHub)
Setup Heavy (install, plugins) Zero setup
Pipeline Jenkinsfile YAML
Trigger Polling/Webhook Native events
Plugins Huge ecosystem Actions marketplace
Scaling Manual Auto
Maintenance High Minimal

Workflow Syntax Deep Dive

.github/workflows/*.yml

GitHub Actions workflows are stored inside the repository under:

.github/workflows/*.yml

This location is mandatory because GitHub has a built-in workflow engine that automatically scans this directory.

Whenever it finds YAML files in .github/workflows/:
It registers them as workflows
It links them to repository events (like push, pull request, etc.)
It triggers them based on the defined conditions

If a workflow file is placed outside this directory:
GitHub will not recognize it
The workflow will never be triggered

Name

This is not just a Name it identifies workflow in ui and helps in debugging and tracking

name: Deploy - ${{ github.ref_name }}

ON

This Defines when your pipeline need to be triggered

Types of Triggers

Code Events

  • push

  • pull_request

Manual

  • workflow_dispatch

Scheduled

on:
  schedule:
    - cron: "0 2 * * *"

Needs

By default, in GitHub Actions, jobs run in parallel. However, if we want to execute them sequentially, we use the needs keyword.

When a job specifies needs, it will wait for the dependent job(s) to complete successfully before starting execution.

jobs:
  build:
    runs-on: ubuntu-latest

  deploy:
    needs: build

Uses VS Run

This is where every one get confused

run means excute commands directly

- name: Build
  run: mvn clean install

uses is like pre-built action

- name: Checkout
  uses: actions/checkout@v4
uses: actions/setup-java@v4
uses: docker/build-push-action@v5

Events and Triggers

PUSH Event

Triggered when code is pushed to a repository.

on:
   push:
      Branches: [main , dev]

Here pipeline will be triggered on branches Main , dev when code is pushed

Advance filtering

on: 
  push:
    path:
        - 'src/'

PULL_REQUEST Event

Triggered when PR activity happens.

on:
 pull_request:
    Branches: [main]

Advances filtering in pr

on:
  pull_request:
         types: [opened, synchornize, reopened]

synchronize means when a new commits happens to an existing pr

WORKFLOW_DISPATCH

Triggered manually workflow from UI

on: 
  workflow_dispatch:
           inputs:
               environment:
                 description: 'Select env'
                 required: true

When we manually trigger this it prompts to ask which env we need to type it then pipleline will be triggered

SCHEDULE

Runs workflow on a schedule

on: 
  schedule:
     - cron : "0 2 * * *"      // MIN HOUR DAY MONTH WEEKDAY

MULTIPLE TRIGGERS

Here we can combine multiple events

on:
  push:
    branches: [ main ]
  pull_request:
  workflow_dispatch:

Workflow runs if ANY event occurs

on:
  push:
    branches: [ dev ]
  pull_request:
    branches: [ main ]
  workflow_dispatch:

REPOSITARY_DISPATCH

To Trigger a workflow using api

on:
  repository_dispatch:
    types: [deploy_event]

The repository_dispatch API is written in external systems like Jenkins pipelines, shell scripts, or backend services. It is used to programmatically trigger GitHub Actions workflows via REST API after certain events like build completion or external system actions.

RUNNERS

A runner is a machine that executes your jobs. think like a node in jenkins

Two Types of runners

GitHub Hosted Runners

GitHub provides ready-made machines for you. no need of any setup

runs-on: ubuntu-latest

Every job = new clean machine

Self-Hosted Runners

runs-on: self-hosted

It runs on

  • Your EC2 / VM

  • On-prem server

  • Kubernetes pod


Let's Create a Self Hosted runner on ec2

Step1 : Launch EC2 Instance

Go to AWS → EC2 → Launch instance

Recommended config:

  • OS: Ubuntu 22.04

  • Instance type: t2.micro (for practice) / bigger for real workloads

  • Storage: 20 GB+

  • Security Group:

    • Allow SSH (22)

Make sure instance has:

  • Internet access (public IP or NAT)

Step2: Connect to EC2

ssh -i your-key.pem ubuntu@<public-ip>

Step3: Go to GitHub → Runner Setup

Open a repository

Settings → Actions → Runners → New self-hosted runner

Select:

  • OS: Linux

  • Architecture: x64

Step4: GitHub will give you commands like this

mkdir actions-runner && cd actions-runner

curl -o actions-runner-linux-x64.tar.gz -L https://github.com/actions/runner/releases/download/v2.x.x/actions-runner-linux-x64-2.x.x.tar.gz

tar xzf actions-runner-linux-x64.tar.gz

Excute these commands in your ec2

it will ask for url

https://github.com/<your-username>/<repo>

Step5: Token

In the same Ui page you will be getting the token copy and paste it

Step6: Runner name (Give a name to your runner)

./run.sh   // Start the runnner

Step7: Use Runner in your workflow

jobs:
  build:
    runs-on: self-hosted
💡
Make sure Ec2 (runner) is running or else jobs will fail

Scalling Runners :

handling multiple jobs efficiently

Scaling GitHub-Hosted Runners:

GitHub handles scaling automatically

jobs:
  build:
  test:
  security-scan:

Here all three jobs run parallely

strategy:
  matrix:
    os: [ubuntu-latest, windows-latest]

This creates Mutiple Runners parallely

  • Concurrency limits (depends on plan)

  • Job queue if limit exceeded

Scaling Self-Hosted Runners :

Option A: Having Multiple Ec2 servers

Options B: Using ASG ( Auto Scalling Group)


Actions MarketPlace

There are of 3 types of actions i.e

Pre-built

Third Party

Custom

Pre-Built Actions:

Actions created by GitHub or verified providers.

Maintained by GitHub
Stable and secure
Standard functionality

uses: actions/checkout@v4
uses: actions/setup-node@v4
uses: actions/upload-artifact@v4

Third Party Actions:

Actions built by community or companies and shared publicly.

uses: docker/build-push-action@v5
uses: slackapi/slack-github-action@v1
uses: aquasecurity/trivy-action@v0.20.0

BIG RISK

Supply chain attacks

  • Malicious code possible

  • Maintainer may abandon project

Custom Actions:

A custom action is a reusable automation component that encapsulates logic so multiple workflows can use it consistently.

Most used is Composite Actions

A YAML-based action that combines multiple steps.

.github/actions/docker-build/action.yml
action.yaml

name: "Docker Build and Push"

inputs:
  image-name:
    required: true

runs:
  using: "composite"
  steps:
    - name: Build Image
      run: docker build -t ${{ inputs.image-name }} .

    - name: Push Image
      run: docker push ${{ inputs.image-name }}

Using in workflow

- uses: ./.github/actions/docker-build
  with:
    image-name: my-app:latest

Secrets In GitHub Actions

Defined in GitHub UI → Settings → Secrets

env:
  DB_PASSWORD: ${{ secrets.DB_PASSWORD }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Using secret"