12  GitHub Actions

Author
Affiliation

Dr Randy Johnson

Hood College

Published

September 23, 2025

Acknowledgments

Gemini was used for summarization and generation of sample scripts.

Introduction to GitHub Actions

Automation is needed to facilitate the CI/CD software delivery process, from code commit to deployment.

  • Continuous Integration (CI): Frequently merging all developers’ working copies to a shared main branch
  • Continuous Delivery (CD): Software engineering approach in which teams produce software in very short cycles
    • Software can be reliably released at any time

GitHub Actions is one tool that provides this automation.

GitHub Actions

  • A CI/CD platform built directly into GitHub.

  • Allows you to automate, customize, and execute software development workflows right in your repository.

  • Can be triggered by GitHub events (e.g. push, pull_request, issue_created)

Why use GitHub Actions?

  • Integration: Deeply integrated with the GitHub ecosystem

  • Automation: Automate repetitive tasks like testing, building, and deploying

  • Visibility: Workflows and their status are visible to the entire team within the repository

  • Ease of Use: Marketplace of pre-built actions for almost any task

Workflows

  • Defined in a YAML (.yml) file in the .github/workflows/ directory of your repository.

  • A single repository can have multiple workflows.

Events

  • Events are triggers that cause a workflow to run
on:
  push:
  pull_request:
  schedule:
    # Runs at 5:30 AM UTC every day
    - cron: '30 5 * * *'
  workflow_dispatch: # for manual dispatch

Jobs

  • A set of steps that execute on the same runner

  • Jobs run in parallel by default, but you can define dependencies using the needs keyword

jobs:
  job-name:
    runs-on: ubuntu-latest
    steps: ...

Steps

  • Individual tasks within a job
  • Can be a shell command (run:) or a reusable action (uses:)
  • Executed sequentially within a job

Step Actions

  • Reusable units of code to perform a specific task
  • Can be from the GitHub Marketplace, a Docker container, or a repository
  • Examples:
    • actions/checkout@v4 to check out your repository’s code
    • quarto-dev/quarto-action@v2 to render quarto documentation
    • dawidd6/action-send-mail@v3 to send an email notification

Runners

  • The server where the workflow runs
    • GitHub-hosted: Virtual machines provided by GitHub with common software pre-installed (e.g. ubuntu-latest, windows-latest)
    • Self-hosted: Your own server, giving you full control over the environment

Building a Basic Workflow

Initialization

  • Option 1: GitHub
    • Open the Actions tab of your repository
    • Pick a workflow template
  • Option 2: Local
    • Create a yaml file in your repository at .github/workflows/ (e.g. build.yml)

Write / Edit yaml file

  • Add/edit the name and on trigger

  • Add a jobs section with a job name (e.g., build).

  • Under your job name, specify the runner (e.g. runs-on: ubuntu-latest)

  • Add/edit steps: for each job
name: Hello World

on: [push]

jobs:
  hello:
    runs-on: ubuntu-latest
    steps:
      - name: Say Hello
        uses: actions/checkout@v4
        run: echo "Hello, world! This is my first GitHub Action."  

Advanced Concepts

Managing Secrets

  • Sensitive information (API keys, passwords) should never be in your workflow file (or anywhere else in your code)

  • Store them in the repository’s Settings > Secrets and variables > Actions

  • Access them in your workflow with ${{secrets.SECRET_NAME}}

Job Dependencies

  • Use the needs keyword to ensure jobs run in a specific order
jobs:
  build:
    runs-on: ubuntu-latest
    ...
  deploy:
    needs: build
    runs-on: ubuntu-lates
    ...

Caching

  • Use actions/cache to save dependencies and build outputs between workflow runs
  • Significantly speeds up workflows by not re-downloading packages like python packages

Reusable workflows

  • For teams with multiple repositories, create a single, centralized workflow
  • Other repositories can then call this workflow, reducing code duplication and ensuring consistency.

Matrix Builds

  • Run a single job across multiple configurations
    • Different operating systems
    • Language versions
# example courtesy of Gemini
name: CI Multi-OS Test
on: [push, pull_request]

jobs:
  test:
    # Use a matrix strategy to test on multiple operating systems
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
    
    runs-on: ${{ matrix.os }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      # Use a setup action for a language (e.g., Node.js)
      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      
      - name: Install dependencies
        run: npm install

      - name: Run tests
        run: npm test

Additional examples

Docker image

# Template code provided on GitHub by Docker
name: Docker Image CI

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

jobs:

  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: Build the Docker image
      run: docker build . --file Dockerfile --tag my-image-name:$(date +%s)

Create and test python app

# Template provided on GitHub by GitHub Actions team
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

name: Python application

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

permissions:
  contents: read

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: Set up Python 3.10
      uses: actions/setup-python@v3
      with:
        python-version: "3.10"
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install flake8 pytest
        if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
    - name: Lint with flake8
      run: |
        # stop the build if there are Python syntax errors or undefined names
        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
    - name: Test with pytest
      run: |
        pytest

Deploy Node.js to Azure Web App

# Template provided on GitHub by Microsoft Azure
# This workflow will build and push a node.js application to an Azure Web App when a commit is pushed to your default branch.
#
# This workflow assumes you have already created the target Azure App Service web app.
# For instructions see https://docs.microsoft.com/en-us/azure/app-service/quickstart-nodejs?tabs=linux&pivots=development-environment-cli
#
# To configure this workflow:
#
# 1. Download the Publish Profile for your Azure Web App. You can download this file from the Overview page of your Web App in the Azure Portal.
#    For more information: https://docs.microsoft.com/en-us/azure/app-service/deploy-github-actions?tabs=applevel#generate-deployment-credentials
#
# 2. Create a secret in your repository named AZURE_WEBAPP_PUBLISH_PROFILE, paste the publish profile contents as the value of the secret.
#    For instructions on obtaining the publish profile see: https://docs.microsoft.com/azure/app-service/deploy-github-actions#configure-the-github-secret
#
# 3. Change the value for the AZURE_WEBAPP_NAME. Optionally, change the AZURE_WEBAPP_PACKAGE_PATH and NODE_VERSION environment variables below.
#
# For more information on GitHub Actions for Azure: https://github.com/Azure/Actions
# For more information on the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# For more samples to get started with GitHub Action workflows to deploy to Azure: https://github.com/Azure/actions-workflow-samples

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

env:
  AZURE_WEBAPP_NAME: your-app-name    # set this to your application's name
  AZURE_WEBAPP_PACKAGE_PATH: '.'      # set this to the path to your web app project, defaults to the repository root
  NODE_VERSION: '20.x'                # set this to the node version to use

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Set up Node.js
      uses: actions/setup-node@v4
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'npm'

    - name: npm install, build, and test
      run: |
        npm install
        npm run build --if-present
        npm run test --if-present

    - name: Upload artifact for deployment job
      uses: actions/upload-artifact@v4
      with:
        name: node-app
        path: .

  deploy:
    permissions:
      contents: none
    runs-on: ubuntu-latest
    needs: build
    environment:
      name: 'Development'
      url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}

    steps:
    - name: Download artifact from build job
      uses: actions/download-artifact@v4
      with:
        name: node-app

    - name: 'Deploy to Azure WebApp'
      id: deploy-to-webapp
      uses: azure/webapps-deploy@v2
      with:
        app-name: ${{ env.AZURE_WEBAPP_NAME }}
        publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
        package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}