Skip to main content

GitHub Actions Fundamentals

Understanding the core concepts and building blocks of GitHub Actions workflows.

What is GitHub Actions?​

GitHub Actions is GitHub's built-in CI/CD platform that allows you to automate workflows directly within your GitHub repositories. It enables you to build, test, and deploy your code right from GitHub with powerful automation capabilities.

Core Concepts​

Key Components​

1. Workflows​

  • Definition: Automated procedures defined in YAML files
  • Location: .github/workflows/ directory in your repository
  • Trigger: Events that start workflow execution
  • Scope: Can be triggered by repository events or manually

2. Events​

  • Push Events: Code pushed to branches
  • Pull Request Events: PR opened, updated, or merged
  • Schedule Events: Time-based triggers using cron syntax
  • Manual Events: Workflow dispatch triggers
  • Webhook Events: External service notifications

3. Jobs​

  • Definition: Set of steps that execute on the same runner
  • Parallel Execution: Multiple jobs can run simultaneously
  • Dependencies: Jobs can depend on other jobs
  • Environment: Each job runs in a fresh virtual environment

4. Steps​

  • Individual Tasks: Single commands or actions within a job
  • Sequential Execution: Steps run one after another
  • Shared Environment: Steps share the same runner environment
  • Conditional Execution: Steps can run based on conditions

5. Actions​

  • Reusable Units: Pre-built or custom workflow components
  • Marketplace: Thousands of community-contributed actions
  • Custom Actions: Actions you create for your specific needs
  • Composite Actions: Actions that combine multiple steps

Workflow Syntax​

Basic Workflow Structure​

name: CI/CD Pipeline

# When to run the workflow
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]

# Environment variables available to all jobs
env:
NODE_VERSION: '18'
DOCKER_REGISTRY: ghcr.io

# Jobs to execute
jobs:
build-and-test:
runs-on: ubuntu-latest

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

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}

- name: Install dependencies
run: npm ci

- name: Run tests
run: npm test

Workflow Metadata​

Name​

name: My Workflow Name
# Optional: Display name for the workflow

Description​

description: 'Builds and tests the application'
# Optional: Description of what the workflow does

Run Name​

run-name: 'Build and test on ${{ github.ref }} by ${{ github.actor }}'
# Optional: Dynamic name for workflow runs

Event Configuration​

Push Events​

on:
push:
branches: [ main, develop ]
branches-ignore: [ 'legacy/**' ]
tags: [ 'v*' ]
paths: [ 'src/**', 'package.json' ]
paths-ignore: [ 'docs/**', '*.md' ]

Pull Request Events​

on:
pull_request:
branches: [ main ]
types: [ opened, synchronize, reopened, closed ]
paths: [ 'src/**' ]

Schedule Events​

on:
schedule:
# Run every day at 2:30 AM UTC
- cron: '30 2 * * *'
# Run every Monday at 9 AM UTC
- cron: '0 9 * * 1'

Manual Triggers​

on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy to'
required: true
default: 'staging'
type: choice
options:
- staging
- production
version:
description: 'Version to deploy'
required: false
type: string

Multiple Events​

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 2 * * *'
workflow_dispatch:

Job Configuration​

Basic Job Structure​

jobs:
job-name:
runs-on: ubuntu-latest
needs: [other-job]
if: github.ref == 'refs/heads/main'

steps:
- name: Step name
run: echo "Hello World"

Runner Selection​

# GitHub-hosted runners
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
runs-on: windows-latest
runs-on: windows-2019
runs-on: macos-latest
runs-on: macos-11

# Self-hosted runners
runs-on: [self-hosted, linux, x64]
runs-on: [self-hosted, windows, x64]
runs-on: [self-hosted, macos, x64]

Job Dependencies​

jobs:
setup:
runs-on: ubuntu-latest
steps:
- run: echo "Setup complete"

build:
runs-on: ubuntu-latest
needs: setup
steps:
- run: echo "Building..."

test:
runs-on: ubuntu-latest
needs: [setup, build]
steps:
- run: echo "Testing..."

Job Conditions​

jobs:
deploy:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- run: echo "Deploying to production"

notify:
runs-on: ubuntu-latest
if: always()
needs: [build, test]
steps:
- run: echo "Sending notifications"

Step Configuration​

Using Actions​

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

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

Running Commands​

steps:
- name: Install dependencies
run: npm ci

- name: Run tests
run: |
npm run test:unit
npm run test:integration
npm run test:e2e

Environment Variables​

steps:
- name: Build application
run: npm run build
env:
NODE_ENV: production
API_URL: ${{ secrets.API_URL }}
BUILD_VERSION: ${{ github.sha }}

Conditional Steps​

steps:
- name: Run tests
run: npm test
if: github.event_name == 'pull_request'

- name: Deploy to staging
run: npm run deploy:staging
if: github.ref == 'refs/heads/develop'

- name: Deploy to production
run: npm run deploy:production
if: github.ref == 'refs/heads/main'

Events and Triggers​

Repository Events​

Push Events​

on:
push:
branches: [ main, develop ]
# Trigger on pushes to main or develop branches

tags: [ 'v*' ]
# Trigger on version tags

paths: [ 'src/**', 'package.json' ]
# Trigger only when specified paths change

paths-ignore: [ 'docs/**', '*.md' ]
# Ignore changes to documentation files

Pull Request Events​

on:
pull_request:
branches: [ main ]
types: [ opened, synchronize, reopened, closed ]
# Trigger on specific PR events

paths: [ 'src/**' ]
# Trigger only when source code changes

Issue Events​

on:
issues:
types: [ opened, closed, labeled ]
# Trigger on issue events

External Events​

Webhook Events​

on:
repository_dispatch:
types: [ deployment-requested ]
# Trigger on repository dispatch events

workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
type: choice
options:
- staging
- production
# Manual workflow trigger with inputs

Schedule Events​

on:
schedule:
# Every day at 2:30 AM UTC
- cron: '30 2 * * *'

# Every Monday at 9 AM UTC
- cron: '0 9 * * 1'

# Every 15 minutes
- cron: '*/15 * * * *'

Event Filters​

Branch Filters​

on:
push:
branches:
- main
- develop
- 'feature/*'
- 'release/*'
branches-ignore:
- 'legacy/**'
- 'deprecated/**'

Path Filters​

on:
push:
paths:
- 'src/**'
- 'package.json'
- 'Dockerfile'
paths-ignore:
- 'docs/**'
- '*.md'
- 'README.md'

Tag Filters​

on:
push:
tags:
- 'v*'
- 'v*.*.*'
- 'release-*'

Actions Marketplace​

Source Control​

- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0 # Fetch all history

- name: Checkout with submodules
uses: actions/checkout@v3
with:
submodules: recursive

Language Setup​

# Node.js
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'

# Python
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
cache: 'pip'

# Java
- name: Setup Java
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'

Docker Actions​

- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ github.sha }}
ghcr.io/${{ github.repository }}:latest

Testing Actions​

- name: Run tests with coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
fail_ci_if_error: true

- name: Upload test results
uses: actions/upload-artifact@v3
with:
name: test-results
path: test-results/

Custom Actions​

Creating a Custom Action​

# action.yml
name: 'Custom Action'
description: 'A custom action for my workflow'
inputs:
name:
description: 'Name to greet'
required: true
default: 'World'
runs:
using: 'node16'
main: 'dist/index.js'

JavaScript Action​

// src/index.js
const core = require('@actions/core');
const github = require('@actions/github');

async function run() {
try {
const name = core.getInput('name');
console.log(`Hello, ${name}!`);

// Set output
core.setOutput('greeting', `Hello, ${name}!`);

} catch (error) {
core.setFailed(error.message);
}
}

run();

Secrets and Environment Variables​

Secrets Management​

Repository Secrets​

steps:
- name: Use secret
run: echo "API key: ${{ secrets.API_KEY }}"
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Organization Secrets​

steps:
- name: Use organization secret
run: echo "Org secret: ${{ secrets.ORG_SECRET }}"

Environment Secrets​

jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy
run: echo "Deploying with ${{ secrets.DEPLOY_KEY }}"

Environment Variables​

Workflow-Level Variables​

env:
NODE_ENV: production
BUILD_VERSION: ${{ github.sha }}
REPOSITORY: ${{ github.repository }}

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Build
run: echo "Building version ${{ env.BUILD_VERSION }}"

Job-Level Variables​

jobs:
build:
runs-on: ubuntu-latest
env:
NODE_ENV: production
API_URL: https://api.example.com
steps:
- name: Build
run: echo "Building for ${{ env.API_URL }}"

Step-Level Variables​

steps:
- name: Build
run: echo "Building application"
env:
NODE_ENV: production
DEBUG: false

Built-in Variables​

GitHub Context Variables​

steps:
- name: Show GitHub context
run: |
echo "Repository: ${{ github.repository }}"
echo "Ref: ${{ github.ref }}"
echo "SHA: ${{ github.sha }}"
echo "Actor: ${{ github.actor }}"
echo "Event: ${{ github.event_name }}"
echo "Workflow: ${{ github.workflow }}"
echo "Run ID: ${{ github.run_id }}"
echo "Run Number: ${{ github.run_number }}"

Runner Context Variables​

steps:
- name: Show runner context
run: |
echo "OS: ${{ runner.os }}"
echo "Architecture: ${{ runner.arch }}"
echo "Tool cache: ${{ runner.tool_cache }}"
echo "Temp: ${{ runner.temp }}"
echo "Workspace: ${{ runner.workspace }}"

Best Practices​

Workflow Organization​

File Naming​

.github/workflows/
├── ci.yml # Continuous integration
├── cd.yml # Continuous deployment
├── security.yml # Security scanning
├── dependency-update.yml # Dependency updates
└── release.yml # Release workflows

Workflow Structure​

name: Descriptive Workflow Name
description: 'Clear description of what this workflow does'

on:
# Specific event triggers
push:
branches: [ main ]

env:
# Global environment variables
NODE_VERSION: '18'

jobs:
# Well-named jobs with clear purposes
test:
runs-on: ubuntu-latest
steps:
# Clear step names and descriptions

Security Best Practices​

Secrets Usage​

steps:
- name: Secure secret usage
run: |
# Good: Use secrets in environment variables
export API_KEY="${{ secrets.API_KEY }}"
./deploy.sh

# Bad: Don't echo secrets
# echo "API key: ${{ secrets.API_KEY }}"

Permission Management​

permissions:
contents: read
packages: write
pull-requests: read
statuses: write

Performance Optimization​

Caching​

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

Parallel Jobs​

jobs:
test-unit:
runs-on: ubuntu-latest
steps:
- run: npm run test:unit

test-integration:
runs-on: ubuntu-latest
steps:
- run: npm run test:integration

lint:
runs-on: ubuntu-latest
steps:
- run: npm run lint

Common Patterns​

Conditional Execution​

steps:
- name: Run tests
run: npm test
if: github.event_name == 'pull_request'

- name: Deploy
run: npm run deploy
if: github.ref == 'refs/heads/main'

Matrix Builds​

strategy:
matrix:
node-version: [16, 18, 20]
os: [ubuntu-latest, windows-latest, macos-latest]

Artifact Management​

- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-files
path: dist/

- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build-files
path: dist/

Key Takeaways​

Essential Concepts​

  1. Workflows are the main automation units in GitHub Actions
  2. Events trigger workflow execution based on repository activities
  3. Jobs run in parallel and contain sequential steps
  4. Actions are reusable workflow components from the marketplace
  5. Secrets provide secure storage for sensitive information

Best Practices Summary​

  • Clear Naming: Use descriptive names for workflows, jobs, and steps
  • Security First: Never expose secrets in logs or outputs
  • Performance: Use caching and parallel execution when possible
  • Organization: Structure workflows logically and maintainably
  • Documentation: Comment complex workflows and document custom actions

Common Mistakes to Avoid​

  • Hardcoded Values: Use environment variables and secrets
  • Large Workflows: Break complex workflows into smaller, focused ones
  • Missing Conditions: Use conditional execution to avoid unnecessary runs
  • Poor Caching: Implement caching for dependencies and build artifacts
  • Insecure Practices: Never commit secrets or expose sensitive data

Next Steps: Ready to build advanced workflows? Continue to Section 2.2: Advanced Workflow Patterns to learn about matrix builds, conditional execution, and performance optimization.


Understanding these fundamentals is crucial for building effective GitHub Actions workflows. In the next section, we'll explore advanced patterns that will help you create more sophisticated and efficient CI/CD pipelines.