Featured Article CI/CD Automation

GitHub Actions for CI/CD: Complete Workflow Automation Guide

Master GitHub Actions for automated CI/CD pipelines with reusable workflows, matrix builds, and deployment strategies.

HA
Hari Prasad
October 08, 2024
5 min read ...
Financial Planning Tool

PPF Calculator

Calculate your Public Provident Fund returns with detailed projections and tax benefits. Plan your financial future with precision.

Try Calculator
Free Forever Secure
10K+
Users
4.9★
Rating
Career Tool

Resume Builder

Create professional DevOps resumes with modern templates. Showcase your skills, experience, and certifications effectively.

Build Resume
No Login Export PDF
15+
Templates
5K+
Created
Kubernetes Tool

EKS Pod Cost Calculator

Calculate Kubernetes pod costs on AWS EKS. Optimize resource allocation and reduce your cloud infrastructure expenses.

Calculate Costs
Accurate Real-time
AWS
EKS Support
$$$
Save Money
AWS Cloud Tool

AWS VPC Designer Pro

Design and visualize AWS VPC architectures with ease. Create production-ready network diagrams with subnets, route tables, and security groups in minutes.

Design VPC
Visual Editor Export IaC
Multi-AZ
HA Design
Pro
Features
Subnets Security Routing
Explore More

Discover My DevOps Journey

Explore my portfolio, read insightful blogs, learn from comprehensive courses, and leverage powerful DevOps tools—all in one place.

50+
Projects
100+
Blog Posts
10+
Courses
20+
Tools

GitHub Actions has become the go-to CI/CD platform for modern development teams, offering powerful automation directly integrated with your code repositories. This comprehensive guide covers everything from basic workflows to advanced patterns used in production.

Why GitHub Actions?

GitHub Actions provides several advantages:

  • Native Integration: Built directly into GitHub
  • Free Tier: 2,000 minutes/month for private repos, unlimited for public
  • Marketplace: Thousands of pre-built actions
  • Matrix Builds: Test across multiple environments simultaneously
  • Self-Hosted Runners: Run on your own infrastructure
  • Secrets Management: Secure credential storage
  • Reusable Workflows: DRY principle for CI/CD

Core Concepts

Workflows

YAML files in .github/workflows/ that define automation

Events

Triggers that start workflows (push, pull_request, schedule, etc.)

Jobs

Groups of steps that run on the same runner

Steps

Individual tasks that run commands or actions

Actions

Reusable units of code (from Marketplace or custom)

Runners

Servers that execute workflows (GitHub-hosted or self-hosted)

Basic Workflow Structure

# .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
    
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '18'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run tests
      run: npm test
    
    - name: Build application
      run: npm run build

Advanced Triggers

on:
  # Multiple events
  push:
    branches:
      - main
      - 'release/**'
    paths:
      - 'src/**'
      - 'package.json'
    paths-ignore:
      - 'docs/**'
      - '**.md'
  
  pull_request:
    types: [opened, synchronize, reopened]
    branches:
      - main
  
  # Schedule (cron)
  schedule:
    - cron: '0 2 * * *'  # Daily at 2 AM UTC
  
  # Manual trigger
  workflow_dispatch:
    inputs:
      environment:
        description: 'Environment to deploy'
        required: true
        type: choice
        options:
          - development
          - staging
          - production
      version:
        description: 'Version to deploy'
        required: true
        type: string
  
  # Release published
  release:
    types: [published]
  
  # Issue/PR events
  issues:
    types: [opened, labeled]
  
  # Workflow call (reusable)
  workflow_call:
    inputs:
      config-path:
        required: true
        type: string

Matrix Strategy

Test across multiple versions and platforms:

name: Matrix Build

on: [push, pull_request]

jobs:
  test:
    runs-on: ${{ matrix.os }}
    
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [16, 18, 20]
        include:
          - os: ubuntu-latest
            node-version: 18
            experimental: false
          - os: ubuntu-latest
            node-version: 21
            experimental: true
        exclude:
          - os: windows-latest
            node-version: 16
      fail-fast: false
      max-parallel: 4
    
    continue-on-error: ${{ matrix.experimental || false }}
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v4
      with:
        node-version: ${{ matrix.node-version }}
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run tests
      run: npm test
    
    - name: Upload coverage
      if: matrix.os == 'ubuntu-latest' && matrix.node-version == '18'
      uses: codecov/codecov-action@v3
      with:
        files: ./coverage/lcov.info

Docker Build and Push

name: Docker Build and Push

on:
  push:
    branches: [main]
    tags: ['v*']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    runs-on: ubuntu-latest
    
    permissions:
      contents: read
      packages: write
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
    
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3
    
    - name: Log in to Container Registry
      uses: docker/login-action@v3
      with:
        registry: ${{ env.REGISTRY }}
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
    
    - name: Extract metadata
      id: meta
      uses: docker/metadata-action@v5
      with:
        images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
        tags: |
          type=ref,event=branch
          type=ref,event=pr
          type=semver,pattern={{version}}
          type=semver,pattern={{major}}.{{minor}}
          type=semver,pattern={{major}}
          type=sha,prefix={{branch}}-
    
    - name: Build and push
      uses: docker/build-push-action@v5
      with:
        context: .
        platforms: linux/amd64,linux/arm64
        push: true
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
        cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
        cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max

Kubernetes Deployment

name: Deploy to Kubernetes

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
    
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        role-to-assume: $
        aws-region: us-east-1
    
    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v2
    
    - name: Build and push Docker image
      env:
        ECR_REGISTRY: $
        IMAGE_TAG: $
      run: |
        docker build -t $ECR_REGISTRY/myapp:$IMAGE_TAG .
        docker push $ECR_REGISTRY/myapp:$IMAGE_TAG
        echo "image=$ECR_REGISTRY/myapp:$IMAGE_TAG" >> $GITHUB_OUTPUT
    
    - name: Configure kubectl
      run: |
        aws eks update-kubeconfig --name my-cluster --region us-east-1
    
    - name: Deploy to Kubernetes
      run: |
        kubectl set image deployment/myapp myapp=$/myapp:$
        kubectl rollout status deployment/myapp

Reusable Workflows

Callable Workflow

# .github/workflows/reusable-build.yml
name: Reusable Build Workflow

on:
  workflow_call:
    inputs:
      node-version:
        required: false
        type: string
        default: '18'
      environment:
        required: true
        type: string
    secrets:
      deploy-token:
        required: true
    outputs:
      build-id:
        description: "Build identifier"
        value: $

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      build-id: $
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: $
    
    - name: Install and build
      run: |
        npm ci
        npm run build
    
    - name: Generate build ID
      id: build
      run: echo "id=build-$" >> $GITHUB_OUTPUT

Calling the Workflow

# .github/workflows/deploy-staging.yml
name: Deploy to Staging

on:
  push:
    branches: [develop]

jobs:
  build:
    uses: ./.github/workflows/reusable-build.yml
    with:
      node-version: '18'
      environment: 'staging'
    secrets:
      deploy-token: $
  
  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
    - name: Deploy
      run: |
        echo "Deploying build: $"

Composite Actions

Create custom reusable actions:

# .github/actions/setup-app/action.yml
name: 'Setup Application'
description: 'Install dependencies and setup caching'

inputs:
  node-version:
    description: 'Node.js version'
    required: false
    default: '18'
  cache-key:
    description: 'Cache key prefix'
    required: false
    default: 'npm'

outputs:
  cache-hit:
    description: 'Whether cache was hit'
    value: ${{ steps.cache.outputs.cache-hit }}

runs:
  using: 'composite'
  steps:
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
    
    - name: Cache dependencies
      id: cache
      uses: actions/cache@v3
      with:
        path: ~/.npm
        key: ${{ inputs.cache-key }}-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
        restore-keys: |
          ${{ inputs.cache-key }}-${{ runner.os }}-
    
    - name: Install dependencies
      shell: bash
      run: npm ci

Use the composite action:

# .github/workflows/test.yml
name: Test

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup application
      uses: ./.github/actions/setup-app
      with:
        node-version: '18'
        cache-key: 'test'
    
    - name: Run tests
      run: npm test

Environment Deployments

name: Deploy to Environments

on:
  push:
    branches: [main]

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://staging.example.com
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Deploy to Staging
      run: |
        echo "Deploying to staging..."
        # Deployment commands
  
  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://example.com
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Deploy to Production
      run: |
        echo "Deploying to production..."
        # Deployment commands

Caching Strategies

name: Build with Caching

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    # Cache Node modules
    - name: Cache Node modules
      uses: actions/cache@v3
      with:
        path: ~/.npm
        key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
        restore-keys: |
          ${{ runner.os }}-node-
    
    # Cache build output
    - name: Cache build
      uses: actions/cache@v3
      with:
        path: dist
        key: ${{ runner.os }}-build-${{ github.sha }}
    
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '18'
        cache: 'npm'  # Built-in caching
    
    - name: Install and build
      run: |
        npm ci
        npm run build

Artifacts

name: Build and Share Artifacts

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Build application
      run: |
        npm ci
        npm run build
    
    - name: Upload build artifacts
      uses: actions/upload-artifact@v3
      with:
        name: build-$
        path: dist/
        retention-days: 7
    
    - name: Upload test results
      if: always()
      uses: actions/upload-artifact@v3
      with:
        name: test-results
        path: test-results/
  
  deploy:
    needs: build
    runs-on: ubuntu-latest
    
    steps:
    - name: Download artifacts
      uses: actions/download-artifact@v3
      with:
        name: build-$
        path: dist/
    
    - name: Deploy
      run: |
        echo "Deploying artifacts..."

Security Scanning

name: Security Scan

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 0 * * 0'  # Weekly

jobs:
  dependency-scan:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Run Snyk to check for vulnerabilities
      uses: snyk/actions/node@master
      env:
        SNYK_TOKEN: $
      with:
        args: --severity-threshold=high
  
  code-scan:
    runs-on: ubuntu-latest
    
    permissions:
      security-events: write
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v2
      with:
        languages: javascript, python
    
    - name: Autobuild
      uses: github/codeql-action/autobuild@v2
    
    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v2
  
  container-scan:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Build image
      run: docker build -t myapp:$ .
    
    - name: Run Trivy vulnerability scanner
      uses: aquasecurity/trivy-action@master
      with:
        image-ref: myapp:$
        format: 'sarif'
        output: 'trivy-results.sarif'
    
    - name: Upload Trivy results to GitHub Security
      uses: github/codeql-action/upload-sarif@v2
      with:
        sarif_file: 'trivy-results.sarif'

Self-Hosted Runners

name: Self-Hosted Runner

on: [push]

jobs:
  build:
    runs-on: [self-hosted, linux, x64, gpu]
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Build with GPU
      run: |
        echo "Running on self-hosted runner with GPU"
        # GPU-intensive tasks

Setup Self-Hosted Runner

# On your server
mkdir actions-runner && cd actions-runner

# Download latest runner
curl -o actions-runner-linux-x64-2.311.0.tar.gz \
  -L https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz

# Extract
tar xzf ./actions-runner-linux-x64-2.311.0.tar.gz

# Configure
./config.sh --url https://github.com/yourorg/yourrepo --token YOUR_TOKEN

# Install and start service
sudo ./svc.sh install
sudo ./svc.sh start

# Check status
sudo ./svc.sh status

Secrets Management

name: Deploy with Secrets

on: [push]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    # Organization secret
    - name: Use organization secret
      run: echo $
    
    # Repository secret
    - name: Use repository secret
      run: echo $
    
    # Environment secret
    - name: Deploy to production
      environment: production
      run: |
        echo $
    
    # Azure Key Vault
    - name: Get secrets from Azure Key Vault
      uses: Azure/get-keyvault-secrets@v1
      with:
        keyvault: "myKeyVault"
        secrets: 'mySecret'
      id: azureSecrets
    
    - name: Use Azure secret
      run: echo $

Conditional Execution

name: Conditional Workflow

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Run on main branch only
      if: github.ref == 'refs/heads/main'
      run: echo "Main branch"
    
    - name: Run on PR only
      if: github.event_name == 'pull_request'
      run: echo "Pull request"
    
    - name: Run on specific actor
      if: github.actor == 'dependabot[bot]'
      run: echo "Dependabot update"
    
    - name: Run based on file changes
      id: changes
      uses: dorny/paths-filter@v2
      with:
        filters: |
          src:
            - 'src/**'
          tests:
            - 'tests/**'
    
    - name: Build if source changed
      if: steps.changes.outputs.src == 'true'
      run: npm run build
    
    - name: Test if tests changed
      if: steps.changes.outputs.tests == 'true'
      run: npm test

Status Badges

Add to your README.md:

![CI](https://github.com/username/repo/actions/workflows/ci.yml/badge.svg)
![Deploy](https://github.com/username/repo/actions/workflows/deploy.yml/badge.svg?branch=main)

Best Practices

Use specific action versions (uses: actions/checkout@v4)
Cache dependencies to speed up workflows
Use matrix builds for multi-platform testing
Implement security scanning in CI pipeline
Use reusable workflows for DRY principles
Set appropriate timeouts to prevent hanging jobs
Use environments for deployment approvals
Leverage composite actions for repeated steps
Monitor workflow costs and optimize runtime
Use concurrency groups to cancel outdated runs

Concurrency Control

name: Deploy

on: [push]

concurrency:
  group: $-$
  cancel-in-progress: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Deploy
      run: echo "Deploying..."

Troubleshooting

name: Debug Workflow

on: [push]

jobs:
  debug:
    runs-on: ubuntu-latest
    
    steps:
    - name: Dump GitHub context
      run: echo '${{ toJSON(github) }}'
    
    - name: Dump job context
      run: echo '${{ toJSON(job) }}'
    
    - name: Dump runner context
      run: echo '${{ toJSON(runner) }}'
    
    - name: Enable debug logging
      run: echo "::debug::This is a debug message"
    
    - name: Set step output
      id: test
      run: echo "result=success" >> $GITHUB_OUTPUT
    
    - name: Use step output
      run: echo "Result was ${{ steps.test.outputs.result }}"

Conclusion

GitHub Actions provides a powerful, flexible platform for CI/CD automation. By leveraging reusable workflows, matrix builds, and the extensive marketplace, you can create sophisticated pipelines that improve code quality, accelerate deployments, and enhance team productivity.

Resources


What’s your favorite GitHub Actions workflow? Share in the comments!

HA
Author

Hari Prasad

Seasoned DevOps Lead with 11+ years of expertise in cloud infrastructure, CI/CD automation, and infrastructure as code. Proven track record in designing scalable, secure systems on AWS using Terraform, Kubernetes, Jenkins, and Ansible. Strong leadership in mentoring teams and implementing cost-effective cloud solutions.

Continue Reading

DevOps Tools & Calculators Free Tools

Power up your DevOps workflow with these handy tools

Enjoyed this article?

Explore more DevOps insights, tutorials, and best practices

View All Articles