homeprojectstemplates
 
   

GitHub actions 101

Published February 19, 2023Last updated April 12, 20245 min read

GitHub action is a CI/CD tool integrated within GitHub repositories that can run different kinds of jobs (building, testing, deployment). Store workflow files in .github/workflows inside the repository, which will be triggered based on specified conditions.

This post covers GitHub actions basics, from specifying the workflow name to configuring different jobs.

Name

Specify the name of the workflow with the name field.

# .github/workflows/config.yml
name: CI/CD pipeline

Running

on field specifies when the workflow should be running.

Automatically

The following configuration runs on every push to a specific branch.

# .github/workflows/config.yml
on:
push:
branches:
- main

The following configuration runs on every push to every branch.

# .github/workflows/config.yml
on:
push:
branches:
- '*'

Cron jobs

The following configuration runs at a specified interval (e.g., every hour).

# .github/workflows/config.yml
on:
schedule:
- cron: '0 * * * *'

Manual triggers

The following configuration enables manual triggering. Trigger it on the Actions tab by selecting the workflow and clicking the Run workflow button.

Use a manual trigger to upload apps to the Google Play console or update the GitHub profile Readme.

# .github/workflows/config.yml
on:
workflow_dispatch:

Environment variables

Specify with the env field. Set repository secrets in Settings Secrets and variables Actions page.

# .github/workflows/config.yml
env:
API_KEY: ${{ secrets.API_KEY }}

Jobs

Specify the job name with the name field. Otherwise, the workflow will use the jobs item as the job name.

Every job should have a runs-on field specified for the machine, which will be running on (e.g., ubuntu-latest) or container field with set Docker image (e.g., node:20.9.0-alpine3.17)

Every job can have a separate working directory in case you have multiple subdirectories, and you want to run a different job in a different subdirectory so you can specify it within the defaults field

jobs:
job-name:
defaults:
run:
working-directory: directory-name

You can specify multiple tasks inside one job. Every task can have the following fields

  • name - task name
  • uses - GitHub action path from GitHub Marketplace
  • with - parameters for the specified GitHub action
  • run - bash commands
  • env - environment variables
# .github/workflows/config.yml
jobs:
build:
name: Custom build job
runs-on: ubuntu-latest
# container: node:20.9.0-alpine3.17
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install and build
run: |
npm ci
npm run build

Use the needs field for running jobs sequentially. It specifies a job that has to be finished before starting the next one. Otherwise, jobs will run in parallel.

# .github/workflows/config.yml
jobs:
build:
# ...
deploy:
name: Custom deploy job
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy
run: |
npm run deploy

Every job can run multiple times with different versions using a matrix strategy (e.g., Node versions 18 and 20 or multiple OS versions inside an array of objects).

# .github/workflows/config.yml
jobs:
build:
name: Custom build job
strategy:
matrix:
node-version: [18, 20]
os:
[
{ name: 'linux', image: 'ubuntu-latest' },
{ name: 'windows', image: 'windows-latest' },
{ name: 'macos', image: 'macos-latest' },
]
runs-on: ${{ matrix.os.image }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install and build
run: |
npm ci
npm run build

Every job can provision databases for e2e tests with a services field like Postgres in the following example.

# .github/workflows/config.yml
jobs:
build:
name: Custom build job
runs-on: ubuntu-latest
strategy:
matrix:
database-name:
- test-db
database-user:
- username
database-password:
- password
database-host:
- postgres
database-port:
- 5432
services:
postgres:
image: postgres:latest
env:
POSTGRES_DB: ${{ matrix.database-name }}
POSTGRES_USER: ${{ matrix.database-user }}
POSTGRES_PASSWORD: ${{ matrix.database-password }}
ports:
- ${{ matrix.database-port }}:${{ matrix.database-port }}
# Set health checks to wait until postgres has started
options: --health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
# ...
- run: npm run test:e2e
env:
DATABASE_URL: postgres://${{ matrix.database-user }}:${{ matrix.database-password }}@${{ matrix.database-host }}:${{ matrix.database-port }}/${{ matrix.database-name }}

Passing artifacts between jobs can be done with uploading (actions/upload-artifact) and downloading (actions/download-artifact) actions.

# .github/workflows/config.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install and build
run: |
npm ci
npm run build
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: artifact
path: public
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download artifact
uses: actions/download-artifact@v3
with:
name: artifact
# ...

Running locally

You can use act to run GitHub actions locally.

Install it and run it with the following commands.

curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
cp ./bin/act /usr/local/bin/act
act