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
Popular Actions
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
- Workflows are the main automation units in GitHub Actions
- Events trigger workflow execution based on repository activities
- Jobs run in parallel and contain sequential steps
- Actions are reusable workflow components from the marketplace
- 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.