Git/GitHub Basics #4 - GitHub Actions

Last Edited: 2/19/2025

This blog post introduces the GitHub actions for CI/CD.

DevOps

So far, we have discussed the basic functionalities of Git and GitHub as a distributed version control system and hosting service for Git remote repositories, which are fundamental to working on projects possibly with others. However, GitHub offers additional features that facilitate DevOps practices. In this article, we will discuss one such feature: GitHub Actions, and how it is useful for CI/CD.

YAML Files

With GitHub Actions, you can run arbitrary commands using a virtual runner at any event trigger, such as push, pull, or merge of a pull request. The environment configurations and commands to be executed can be specified in a YAML file with yml or yaml extensions. YAML is a human-readable and computationally powerful data serialization language often used for configuration files. It serves as an alternative to JSON by storing key-value pairs but with different syntax.

key1: value1 # String
key2: 2 # Number
key3: true # boolean
key4: on # boolean (true & false, on & off, yes & no)
key5: > 
    When > symbol is used, this long sentence is 
    merged into one sentence.
key6: |
    When | symbol is used, the new lines and whitespaces 
    are preserved. 

The example above shows key-value pairs in strings, numbers, and booleans. YAML automatically infers the type, though you can use double quotes if necessary. Long strings can be broken into multiple lines using > or |, as shown above. Arrays and objects can also be expressed as follows:

# Array
array1: 
  - value1
  - value2
  - value3
 
array2: [value1, value2, value3]
 
# Object
object:
  key1: value1
  key2: value2
  key3: value3
 
# Array of Objects
array3:
  - key1: value1
    key2: value2
  - key1: value3
    key2: value4

Arrays and objects can be combined, allowing the expression of complex object types. To set up a GitHub action, create a .github directory at the root level, add a workflows subdirectory, and place a corresponding YAML file with configurations inside it.

Continuous Integration

Continuous integration (CI) is a practice where developers should push valuable and small code changes incrementally to improve software. To achieve CI, any breaking changes need to be detected before being applied, ideally automatically. GitHub Actions allow you to run testing scripts upon events like pushes or pull requests to facilitate CI. Below is an example workflow for running tests:

testing.yaml
name: "Testing"
 
# When to trigger workflow
on:
  pull_request:
    branches:
      - main
 
# Workflows
jobs:
  test: # name of a workflow
    runs-on: ubuntu-latest # OS (mac, windows, ubuntu)
    steps: 
      - uses: actions/checkout@v4 # preset action for accessing source code
      - uses: actions/setup-node@v4 # preset action for setting up NodeJS
        with: 
          node-version: 20
      - name: Install Dependencies
        run: npm ci # clean isntall
      - name: Run Test
        run: npm test # defined in the source code
      - run: npm run build # test if the source code compiles

The above YAML file configures a workflow that tests the source code using test scripts defined in the codebase on NodeJS installed on Ubuntu when a pull request is made on the main branch. You can use preset actions like actions/checkout and actions/setup-node to simplify environment configuration. Names can optionally be assigned to each command, which will be displayed on the GitHub interface (otherwise, it defaults to "Run <command>"). If any command fails, it is flagged on GitHub with an error message automatically, aiding in debugging without needing to download the code and run tests manually.

jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node-version: [18, 20]
    runs-on: ${{ matrix.os }}
    steps: 
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: 
          node-version: ${{ matrix.node-version }}

To test and build across different OS and versions, you can use strategy: matrix:. The example above tests six combinations of OS and Node.js versions using this strategy, which is beneficial for software that needs cross-platform compatibility.

Continuous Delivery

Continuous delivery (CD) is a concept where developers can release software continuously without manually configuring the server each time. This can also be achieved using GitHub Actions, as demonstrated in the following workflow example for deploying software on AWS with SSH keys:

deploy.yaml
name: "Deployment"
 
on:
  push: # When pushing directly to main or merge pull request
    branches:
      - main
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps: 
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: 
          node-version: 20
      - name: Install Dependencies
        run: npm ci
      - run: npm run build
      - name: Get SSH key and sort permissions out
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa
      - name: Deploy using SCP
        run: > 
          scp -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa 
          -r ./dist/*${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:~node-app
      - name: Restart the server
        run: > 
          ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} 
          "cd ~/node-app && npm install && pm2 restart 0"

Secrets like SSH keys and API keys that do not belong to the repository can be stored in the "Action secrets" section under "Settings," which workflows can access via secrets. The above workflow sets up the SSH key and permissions, sends files using SCP (a networking protocol for sending files), and restarts the server with pm2. The details of the scripts depend on remote server architectures, but once you set up the workflow, delivery can be automated, which is highly convenient. (Many paid hosting services automatically perform this too.)

Conclusion

In this article, we introduced YAML files, CI/CD, and GitHub Actions, all of which are essential for DevOps. YAML files are used across multiple DevOps tools, and CI/CD is at the core of DevOps practices. For more information on GitHub Actions (preset actions, example actions, events, etc.), refer to the official documentation cited below.

Resources