Git & GitHub track

Lesson 08 · Lessons

GitHub Actions

Workflows, triggers, matrices, caching, artifacts and secrets

Lessons5 min readFree lesson

GitHub Actions react to events such as code push, PR creation, and certain times. It is a CI/CD platform that automatically executes build, test, and deployment. You can define a complex pipeline with a single YAML file. Reuse open source actions for quick composition.

Learning Objectives

  • Can understand and write the basic structure of GitHub Actions workflow YAML.
  • You can set push·pull_request trigger and manual trigger (workflow_dispatch).
  • Parallel tests can be run on multiple OS/Version combinations as a matrix.
  • Workflow execution time can be shortened with dependency cache.
  • You can store build output with artifacts and use sensitive information safely with secrets.

Key concepts

1) Workflow structure

name: Workflow name

on:                      #trigger
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  job-name:
    runs-on: ubuntu-latest    #execution environment
    steps:
      - uses: actions/checkout@v4           #Use action
- name: step name
        run: echo "Hello"                   #shell command

2) Trigger type

on:
  push:                         #push event
    branches: [main, develop]
paths: ['src/**'] #Only when changing a specific path
  pull_request:
    types: [opened, synchronize, reopened]
  schedule:
- cron: '0 9 * * 1' #Every Monday 09:00 UTC
workflow_dispatch: #Manual execution (UI button)
    inputs:
      environment:
        type: choice
        options: [staging, production]

3) Matrix Strategy

strategy:
  matrix:
    os: [ubuntu-latest, windows-latest, macos-latest]
    node: [18, 20, 22]
  fail-fast: false       #Even if one fails, the rest continues to run

runs-on: ${{ matrix.os }}
steps:
  - uses: actions/setup-node@v4
    with:
      node-version: ${{ matrix.node }}

4) Cache

- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-

5) Artifacts

- uses: actions/upload-artifact@v4
  with:
    name: build-output
    path: dist/
    retention-days: 7

#download
- uses: actions/download-artifact@v4
  with:
    name: build-output

6) Secrets

env:
  API_KEY: ${{ secrets.API_KEY }}
  DB_URL: ${{ secrets.DATABASE_URL }}

#GitHub → Repository Settings → Secrets and variables → Actions → New repository secret

See as example

Example 1 — ex01_hello.yml: “Hello” output when pushing

name: Hello World

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  greet:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Say Hello
        run: |
          echo "Hello, GitHub Actions!"
          echo "Repo: ${{ github.repository }}"
          echo "Branch: ${{ github.ref_name }}"
          echo "Actor: ${{ github.actor }}"

Execution results (check in Actions tab)

Hello, GitHub Actions!
Repo: username/my-repo
Branch: main
Actor: username

Key point: You can retrieve repository, branch, and user information with the github context.


Example 2 — ex02_test.yml: Node.js project CI

name: Node.js CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm test

      - name: Build
        run: npm run build

Key point: npm ci is installed accurately based on package-lock.json to ensure reproducibility.


Example 3 — ex03_matrix.yml: OS·Node version matrix

name: Matrix Build

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ${{ matrix.os }}

    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node-version: [18, 20, 22]
        exclude:
          - os: windows-latest
            node-version: 18   #Excluding certain combinations

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      - name: Install and test
        run: |
          npm ci
          npm test

Key point: 3 × 3 = 9 (8 including 1) combinations are executed in parallel.


Example 4 — ex04_cache.yml: Reduce installation time with cache + save artifacts

name: Build with Cache

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'          #Built-in cache (automatically configure actions/cache)

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: dist-${{ github.sha }}
          path: dist/
          retention-days: 7

  deploy:
    needs: build                 #Run after build completion
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Download artifact
        uses: actions/download-artifact@v4
        with:
          name: dist-${{ github.sha }}
          path: dist/

      - name: Deploy
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
        run: echo "Deploying with secret key (masked)"

Key point: needs specifies the order between jobs, and secrets safely transmit sensitive information.


Seeing from a different perspective

Actions concept Metaphor
workflow Factory production line blueprint
job Each workshop in the production line
step Detail work in workshop
action (uses) Ready-made tools
matrix Simultaneous production in multiple factories
artifact Finished product packaging box
secrets Key locked in safe

Common Mistakes

  1. Access code without actions/checkout — Workflow does not automatically import code.
  2. Output secrets as echo — GitHub masks this, but it may remain in the log.
  3. Fix the cache key — The cache is not updated when dependencies change; Use dynamic keys with hashFiles.
  4. Ignore fail-fast: true default — If one fails, all matrix jobs are canceled.
  5. Setting only workflow_dispatch without on: push — It does not run automatically.

Cleanup

  • The workflow is saved in .github/workflows/*.yml and has the structure of event → job → step.
  • Increase test coverage by executing multiple environment combinations in parallel using a matrix.
  • Reduce dependency installation time with cache and save build results with artifact.
  • secrets is set in the GitHub UI and referenced as ${{ secrets.NAME }} in the workflow.
  • Control the order by setting dependency relationships between jobs with needs.

Try it yourself

#The Actions yml file runs from a GitHub repository.
#Copy the file to the workflow directory with the command below.
mkdir -p .github/workflows
cp 08_GitHub_Actions/src/ex01_hello.yml .github/workflows/
git add .github/workflows/ex01_hello.yml
git commit -m "ci: add hello workflow"
git push
#→ Check the results in the GitHub repository Actions tab.

Application:

  1. Add the input parameter to workflow_dispatch to select the environment when running manually.
  2. Create a workflow that runs every midnight at midnight with the schedule trigger.

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

src/ex01_hello.ymlYAML
name: Hello World

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  greet:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Print environment info
        run: |
          echo "=== Hello, GitHub Actions! ==="
          echo "Repository : ${{ github.repository }}"
          echo "Branch     : ${{ github.ref_name }}"
          echo "Actor      : ${{ github.actor }}"
          echo "Commit SHA : ${{ github.sha }}"
          echo "Runner OS  : ${{ runner.os }}"

      - name: List files
        run: ls -la
src/ex02_test.ymlYAML
name: Node.js CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js 20
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint --if-present

      - name: Run tests
        run: npm test --if-present

      - name: Build
        run: npm run build --if-present

      - name: Report status
        if: always()
        run: |
          echo "Job status: ${{ job.status }}"
src/ex03_matrix.ymlYAML
name: Matrix Build

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

jobs:
  test:
    name: Test on ${{ matrix.os }} / Node ${{ matrix.node-version }}
    runs-on: ${{ matrix.os }}

    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node-version: [18, 20, 22]
        exclude:
          - os: windows-latest
            node-version: 18

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

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      - name: Show environment
        run: |
          node --version
          npm --version

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test --if-present

  summary:
    name: Matrix Summary
    needs: test
    runs-on: ubuntu-latest
    if: always()
    steps:
      - name: Print result
        run: echo "All matrix jobs completed with status ${{ needs.test.result }}"
src/ex04_cache.ymlYAML
name: Build with Cache and Artifacts

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

jobs:
  build:
    runs-on: ubuntu-latest

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

      - name: Setup Node.js with npm cache
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test --if-present

      - name: Build
        run: npm run build --if-present

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: dist-${{ github.sha }}
          path: |
            dist/
            !dist/**/*.map
          retention-days: 7
          if-no-files-found: warn

  deploy:
    name: Deploy to production
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    environment: production

    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: dist-${{ github.sha }}
          path: dist/

      - name: Deploy
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
          TARGET_HOST: ${{ secrets.TARGET_HOST }}
        run: |
          echo "Deploying to $TARGET_HOST..."
          echo "Token length: ${#DEPLOY_TOKEN} chars"
          echo "Deploy complete!"

Assignment

Problem 1

Write a workflow that runs a bash script on push and saves the results as an artifact.

  • File name: hw01.yml
  • Requirements:
  • Trigger: main branch push, workflow_dispatch
  • Steps:
  1. Check out the code with actions/checkout@v4.
  2. Run date, uname -a, git log --oneline -5 and save in report.txt.
  3. Upload report.txt to actions/upload-artifact@v4 under the name ci-report.
  • Retention period: 3 days
#example structure
name: CI Report
on:
  push:
    branches: [main]
  workflow_dispatch:
jobs:
  report:
    runs-on: ubuntu-latest
    steps:
      ...

Problem 2

Create a matrix CI workflow for your Python projects.

  • File name: hw02.yml
  • Requirements:
  • Trigger: push (main, develop), pull_request (main)
  • Matrix:
    • OS: ubuntu-latest, macos-latest
  • Python version: 3.10, 3.11, 3.12
  • Steps:
    1. checkout
  1. Set up Python with actions/setup-python@v5
  2. pip install -r requirements.txt (echo "no requirements" if there is no file) 4. python -m pytest --if-present || echo "no tests"
  • fail-fast: false settings

Check the correct answer

After writing it yourself, compare it with the reference solution below.

Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

hw01.ymlYAML
name: CI Report

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  report:
    runs-on: ubuntu-latest

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

      - name: Generate report
        run: |
          {
            echo "=== Build Report ==="
            echo "Date: $(date)"
            echo ""
            echo "=== System Info ==="
            uname -a
            echo ""
            echo "=== Recent Commits ==="
            git log --oneline -5
          } > report.txt
          cat report.txt

      - name: Upload report artifact
        uses: actions/upload-artifact@v4
        with:
          name: ci-report
          path: report.txt
          retention-days: 3
hw02.ymlYAML
name: Python Matrix CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
    runs-on: ${{ matrix.os }}

    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest]
        python-version: ['3.10', '3.11', '3.12']

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

      - name: Setup Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          if [ -f requirements.txt ]; then
            pip install -r requirements.txt
          else
            echo "No requirements.txt — skipped"
          fi

      - name: Run tests
        run: |
          if python -m pytest --version 2>/dev/null; then
            python -m pytest
          else
            echo "No pytest — skip tests"
          fi

      - name: Show Python version
        run: python --version

Learn this lesson with a mentor

Work through the Git & GitHub track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now