1. Git & GitHub Fundamentals
GitHub Actions relies on Git object mechanics. Workflows are declared as code and committed directly to your repository inside the .github/workflows/ directory. GitHub observes changes to Git references (refs/heads/<branch>, refs/tags/<tag>, refs/pull/<number>/merge) to evaluate trigger rules.
How Git Mechanics Map to Actions
- Commits & Pushes: Every
git pushtransmits a SHA hash and branch ref that GitHub Actions uses to check out the exact state of your code. - Pull Requests: A PR trigger creates a virtual merge commit (
refs/pull/PR_NUMBER/merge) combining your source branch with the target branch to test collision-free execution. - Tags & Releases: Semantic version tags (
v1.0.0) trigger build, package, and publish workflows.
Bash
# Developer Workflow triggering CI
git checkout -b feature/user-auth
git commit -m "feat: implement JWT login"
git push origin feature/user-auth # Triggers push / pull_request events on GitHub
2. YAML Syntax
Workflows are written in YAML (YAML Ain’t Markup Language). YAML uses strictly spaces for indentation (tabs are forbidden) and relies on key-value pairs, scalar types, arrays, and dictionaries.
Key Syntax Rules for GitHub Actions
- Indentation: standard is 2 spaces per level.
- Lists/Sequences: Prefixed with a hyphen and a space (
- item). - Multi-line Strings: Use
|to preserve line breaks or>to fold line breaks into spaces. - Booleans & Numbers:
true,false,10,3.14(unquoted). Quoting forces string representation ("true").
YAML
# Demonstration of YAML data structures
string_scalar: "Hello World"
number_scalar: 42
boolean_scalar: true
# List / Array syntax
browser_list:
- chromium
- firefox
- webkit
# Dictionary / Object syntax
runner_config:
os: ubuntu-latest
timeout: 30
# Multi-line script execution block (| preserves newlines)
multi_line_command: |
echo "Line 1"
echo "Line 2"
npm test
3. Creating Your First Workflow
A minimal workflow file must be stored in .github/workflows/main.yml. It requires three root keys: name, on, and jobs.
YAML
# .github/workflows/first-workflow.yml
name: First Workflow
# Event trigger
on:
push:
branches: [ "main" ]
# Execution unit
jobs:
welcome-job:
runs-on: ubuntu-latest
steps:
- name: Print welcome message
run: echo "GitHub Actions workflow executed successfully!"
- name: Output commit metadata
run: |
echo "Commit SHA: ${{ github.sha }}"
echo "Triggered by: ${{ github.actor }}"
4. Events and Triggers (on)
The on key determines when a workflow runs. You can listen to single events, array events, or apply fine-grained activity filters and path filters.
Filtering Rules
branches: Restrict runs to specific target branches.paths/paths-ignore: Trigger only when specific files change.tags: Trigger on Git tags matching glob patterns.
YAML
name: Event Trigger Masterclass
on:
# Trigger on pushes to main or release branches, but only if code inside src/ changed
push:
branches:
- main
- 'releases/v*'
paths:
- 'src/**'
- 'package.json'
paths-ignore:
- '**.md'
# Trigger on Pull Requests targeting main branch when opened or synchronized
pull_request:
types: [opened, synchronize, reopened]
branches:
- main
# Trigger on tag creation matching v1.0.0, v2.1.0, etc.
push:
tags:
- 'v*.*.*'
5. Jobs and Steps
- Job: A set of steps running on a specific host environment (
runs-on). Jobs run in parallel by default. - Step: An individual task executed sequentially inside a job. Steps share a filesystem and environment state on the runner VM.
YAML
name: Job Dependency Pipeline
on: push
jobs:
compile:
runs-on: ubuntu-latest
steps:
- name: Step 1 - Build
run: echo "Compiling assets..."
test:
needs: compile # Forces 'test' to wait until 'compile' finishes successfully
runs-on: ubuntu-latest
steps:
- name: Step 1 - Unit Test
run: echo "Executing unit tests..."
deploy:
needs: [compile, test] # Waits for both upstream jobs
runs-on: ubuntu-latest
steps:
- name: Step 1 - Deploy
run: echo "Deploying to production..."
6. Using Marketplace Actions
Instead of writing raw shell scripts for every task, you can import community and official actions from the GitHub Marketplace using the uses: keyword.
Versioning Actions
@v4(Major version tag – receives non-breaking updates).@v4.1.2(Exact semantic version).@8f4b7f8...(Full commit SHA – most secure, immutable).
YAML
jobs:
setup-environment:
runs-on: ubuntu-latest
steps:
# Action 1: Clone repository code
- name: Checkout Repository
uses: actions/checkout@v4
# Action 2: Provision Node.js runtime environment
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
# Step 3: Native shell execution
- name: Install dependencies
run: npm ci
7. Environment Variables and Secrets
GitHub Actions handles environment configuration at the workflow, job, or step scope. Sensitive credentials must be stored as Encrypted Secrets in repository/organization settings.
YAML
name: Environment & Secrets Demo
on: push
# Workflow-level environment variables
env:
GLOBAL_APP_NAME: "MyEnterpriseApp"
LOG_LEVEL: "debug"
jobs:
process-payment:
runs-on: ubuntu-latest
# Job-level environment variables
env:
JOB_ENV: "payment-processor"
steps:
- name: Secure API Call
# Step-level environment variables
env:
PAYMENT_API_KEY: ${{ secrets.PAYMENT_GATEWAY_KEY }} # Injecting secret
DYNAMIC_VAR: "step-specific-value"
run: |
echo "Processing payment for $GLOBAL_APP_NAME"
# Accessing secret safely via environment variable in shell
python process.py --key "$PAYMENT_API_KEY"
- name: Set dynamic environment variable for subsequent steps
run: |
echo "BUILD_TIMESTAMP=$(date +'%Y-%m-%d_%H-%M-%S')" >> $GITHUB_ENV
- name: Read dynamic variable
run: |
echo "Timestamp was: $BUILD_TIMESTAMP"
8. Expressions and Conditional Execution
Expressions let you evaluate programmatic conditions and access context objects (github, env, runner, steps, secrets). Use if: directives to skip jobs or steps dynamically.
YAML
jobs:
conditional-job:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Execute on main branch only
if: github.ref == 'refs/heads/main'
run: echo "This step runs ONLY on pushes to main."
- name: Execute step with custom status check functions
id: test_step
run: npm test
# Runs ONLY if the previous step failed
- name: Report Failure
if: failure() && steps.test_step.outcome == 'failure'
run: echo "The test execution step failed!"
# Runs ALWAYS, regardless of prior step pass/fail state
- name: Always Cleanup
if: always()
run: echo "Cleaning up temporary test artifacts..."
9. Matrix Strategy
A Matrix Strategy generates multiple job runs by defining input variables across OSs, runtimes, or database versions. GitHub Actions expands these configurations into a parallel execution grid.
YAML
jobs:
matrix-build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false # Keep running other matrix jobs even if one fails
max-parallel: 4 # Limit concurrent runners
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18, 20]
# Include specific additional configuration
include:
- os: ubuntu-latest
node-version: 22
experimental: true
# Exclude invalid or unwanted combinations
exclude:
- os: windows-latest
node-version: 18
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: node -v
10. Caching Dependencies
To speed up workflow runs, use actions/cache or built-in caching inside setup actions. Caches persist archive files between workflow runs using a unique cache key (typically hashed from lockfiles).
YAML
jobs:
cache-demo:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Method A: Built-in setup action caching (Recommended)
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip' # Automatically hashes requirements.txt/Pipfile
# Method B: Manual Cache Configuration via actions/cache
- name: Cache Custom Directory
uses: actions/cache@v4
with:
path: ~/.custom_cache_dir
# Primary lookup key
key: ${{ runner.os }}-custom-${{ hashFiles('**/lockfile.json') }}
# Fallback lookup keys if primary key misses
restore-keys: |
${{ runner.os }}-custom-
- name: Install Dependencies
run: pip install -r requirements.txt
11. Uploading and Downloading Artifacts
Artifacts allow you to persist output files (build binaries, test reports, coverage HTML) beyond the runner’s ephemeral lifecycle and pass data between independent jobs.
YAML
jobs:
build-job:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: mkdir dist && echo "Compiled App Code" > dist/app.bin
# Upload artifacts
- name: Upload Build Binaries
uses: actions/upload-artifact@v4
with:
name: compiled-app-artifact
path: dist/
retention-days: 7
deploy-job:
needs: build-job # Must wait for build-job to upload
runs-on: ubuntu-latest
steps:
# Download artifacts generated in upstream job
- name: Download Build Binaries
uses: actions/download-artifact@v4
with:
name: compiled-app-artifact
path: downloaded-dist/
- name: Verify Downloaded Assets
run: cat downloaded-dist/app.bin
12. Manual and Scheduled Workflows
Workflows can run on cron schedules or be triggered manually via the GitHub UI/API with custom user inputs (workflow_dispatch).
YAML
name: Manual & Scheduled Execution
on:
# Cron trigger: Runs every Monday at 08:00 UTC
schedule:
- cron: '0 8 * * 1'
# Manual trigger with interactive input forms
workflow_dispatch:
inputs:
target_env:
description: 'Target Deployment Environment'
type: choice
required: true
options:
- staging
- production
perform_cleanup:
description: 'Run deep database cleanup?'
type: boolean
default: false
jobs:
execute:
runs-on: ubuntu-latest
steps:
- name: Print Trigger Context
run: |
echo "Trigger event: ${{ github.event_name }}"
echo "Selected Environment: ${{ inputs.target_env }}"
echo "Perform Cleanup: ${{ inputs.perform_cleanup }}"
13. Reusable Workflows and Composite Actions
Composite Action (.github/actions/setup-stack/action.yml)
Bundles multiple run steps into a single reusable step block.
YAML
name: 'Setup Project Stack'
description: 'Standardized setup steps for project'
runs:
using: 'composite'
steps:
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
shell: bash # Shell directive is mandatory for composite actions
Reusable Workflow (.github/workflows/reusable-ci.yml)
Encapsulates complete jobs for consumption across callers.
YAML
name: Reusable CI Core
on:
workflow_call:
inputs:
node-version:
type: string
default: '20'
jobs:
run-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm test
Calling Workflow (.github/workflows/caller.yml)
YAML
jobs:
invoke-reusable:
uses: ./.github/workflows/reusable-ci.yml
with:
node-version: '22'
14. Docker Integration
GitHub Actions runners come pre-configured with Docker, enabling step containers, containerized jobs, service containers (databases/queues), and image publishing.
YAML
jobs:
# Integration testing using a Database Service Container
database-tests:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: mysecretpassword
POSTGRES_DB: testdb
ports:
- 5432:5432
# Health checks to ensure DB is ready before test steps start
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Verify PostgreSQL Connection
run: |
PGPASSWORD=mysecretpassword psql -h localhost -U postgres -d testdb -c "SELECT 1;"
# Build & Publish Docker Container Image
docker-build-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log in to GitHub Container Registry (GHCR)
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push Docker Image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}/app:latest
15. Cloud Deployments (AWS/Azure/GCP)
To deploy safely to modern cloud infrastructure, never use long-lived access key secrets. Use OpenID Connect (OIDC) to request temporary, short-lived tokens using IAM Role assumption.
YAML
name: Keyless OIDC AWS Deployment
on:
push:
branches: [ "main" ]
jobs:
deploy-aws:
runs-on: ubuntu-latest
# Required permission to request the OIDC JWT token from GitHub
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Authenticate with AWS via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsDeploymentRole
aws-region: us-east-1
- name: Deploy to S3 Static Bucket
run: |
aws s3 sync ./dist s3://my-production-app-bucket --delete
16. Enterprise CI/CD Patterns and Security
Production enterprise workflows require strict security practices: Zero Trust permissions, Pinned Action Hashes, Concurrency Controls, and Protected Environments.
YAML
name: Enterprise Secure Pipeline
on:
push:
branches: [ "main" ]
# Prevent race conditions by cancelling in-progress runs on same branch
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# Hardened Security Rule 1: Set Least-Privilege permissions globally
permissions: {}
jobs:
secure-build:
runs-on: ubuntu-latest
permissions:
contents: read # Read-only code access
id-token: write # For OIDC deployment step
# Attach workflow to protected GitHub Environment (Approval rules & secrets)
environment:
name: production
url: https://my-app.company.com
steps:
# Hardened Security Rule 2: Pin Actions to full immutable commit SHAs
- name: Checkout Code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Node.js
uses: actions/setup-node@39370e3970a6d050c080011e813206d11393d18e # v4.1.0
with:
node-version: '20'
- name: Run Dependency Vulnerability Audit
run: npm audit --audit-level=high
- name: Build Application
run: npm run build