From Beginner to Expert

    Table of Contents

    1. Introduction to GitHub Actions
    2. Getting Started
    3. Workflow Fundamentals
    4. Jobs and Steps
    5. Actions and Marketplace
    6. Environment Variables and Secrets
    7. Matrix Builds and Strategies
    8. Conditional Workflows
    9. Custom Actions
    10. Advanced Patterns
    11. Security Best Practices
    12. Monitoring and Debugging
    13. Enterprise Features
    14. Real-World Examples

    1. Introduction to GitHub Actions

    GitHub Actions is a powerful CI/CD platform that allows you to automate your software development workflows directly in your GitHub repository. It enables you to build, test, package, release, and deploy your code right from GitHub.

    What are GitHub Actions?

    GitHub Actions makes it easy to automate all your software workflows with world-class CI/CD. Build, test, and deploy your code right from GitHub. Make code reviews, branch management, and issue triaging work the way you want.

    flowchart TD
        A[Developer pushes code] --> B[GitHub Repository]
        B --> C[Trigger Event]
        C --> D[GitHub Actions Runner]
        D --> E[Execute Workflow]
        E --> F[Run Jobs]
        F --> G[Execute Steps]
        G --> H[Complete Workflow]
        H --> I[Report Results]

    Key Components

    • Workflows: Automated procedures added to your repository
    • Events: Specific activities that trigger a workflow
    • Jobs: Set of steps that execute on the same runner
    • Steps: Individual tasks that can run commands or actions
    • Actions: Reusable units of code
    • Runners: Servers that run your workflows

    Benefits

    • Integrated: Native to GitHub ecosystem
    • Flexible: Supports any language, any OS
    • Powerful: Matrix builds, environments, secrets
    • Community: Large marketplace of actions
    • Free: Generous free tier for public repositories

    2. Getting Started

    Your First Workflow

    To create your first GitHub Action workflow, you need to add a YAML file in the .github/workflows directory of your repository.

    # .github/workflows/hello-world.yml
    name: Hello World
    
    on:
      push:
        branches: [ main ]
      pull_request:
        branches: [ main ]
    
    jobs:
      hello:
        runs-on: ubuntu-latest
        steps:
          - name: Say Hello
            run: echo "Hello, World!"
    YAML

    Workflow Structure

    graph TB
        A[Workflow File] --> B[name]
        A --> C[on: Events]
        A --> D[jobs]
    
        C --> C1[push]
        C --> C2[pull_request]
        C --> C3[schedule]
        C --> C4[workflow_dispatch]
    
        D --> D1[job1]
        D --> D2[job2]
    
        D1 --> E1[runs-on]
        D1 --> F1[steps]
    
        F1 --> G1[Step 1]
        F1 --> G2[Step 2]
        F1 --> G3[Step N]
    
        G1 --> H1[name]
        G1 --> I1[run/uses]

    Repository Setup

    1. Create .github/workflows directory
    2. Add workflow YAML files
    3. Commit and push to trigger workflows

    3. Workflow Fundamentals

    Workflow Events

    GitHub Actions can be triggered by various events:

    on:
      # Trigger on push to main branch
      push:
        branches: [ main ]
        paths: [ 'src/**' ]
    
      # Trigger on pull requests
      pull_request:
        branches: [ main, develop ]
        types: [opened, synchronize, reopened]
    
      # Schedule workflows
      schedule:
        - cron: '0 2 * * 1-5'  # Run at 2 AM, Monday to Friday
    
      # Manual trigger
      workflow_dispatch:
        inputs:
          environment:
            description: 'Environment to deploy to'
            required: true
            default: 'staging'
            type: choice
            options:
            - staging
            - production
    
      # Repository events
      issues:
        types: [opened, closed]
    
      release:
        types: [published]
    YAML

    Event Flow Diagram

    sequenceDiagram
        participant Developer
        participant GitHub
        participant Runner
        participant Workflow
    
        Developer->>GitHub: Push code
        GitHub->>Workflow: Trigger event
        Workflow->>Runner: Request runner
        Runner->>Workflow: Execute jobs
        Workflow->>GitHub: Report status
        GitHub->>Developer: Show results

    Workflow Syntax

    name: Workflow Name
    run-name: Custom run name # Optional
    
    on:
      # Event configuration
    
    env:
      # Global environment variables
      NODE_VERSION: 18
    
    defaults:
      run:
        shell: bash
        working-directory: ./app
    
    jobs:
      # Job definitions
    YAML

    4. Jobs and Steps

    Job Configuration

    Jobs run in parallel by default but can be configured to run sequentially:

    jobs:
      test:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
          - name: Run tests
            run: npm test
    
      build:
        needs: test  # Wait for test job to complete
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
          - name: Build application
            run: npm run build
    
      deploy:
        needs: [test, build]  # Wait for both jobs
        runs-on: ubuntu-latest
        if: github.ref == 'refs/heads/main'
        steps:
          - name: Deploy to production
            run: echo "Deploying..."
    YAML

    Job Dependencies

    graph TD
        A[test] --> C[deploy]
        B[build] --> C[deploy]
    
        style A fill:#e1f5fe
        style B fill:#e1f5fe
        style C fill:#f3e5f5

    Runner Types

    jobs:
      linux-job:
        runs-on: ubuntu-latest
    
      windows-job:
        runs-on: windows-latest
    
      macos-job:
        runs-on: macos-latest
    
      self-hosted-job:
        runs-on: [self-hosted, linux, x64]
    
      specific-version:
        runs-on: ubuntu-20.04
    YAML

    Step Types

    steps:
      # Checkout code
      - uses: actions/checkout@v3
    
      # Run shell command
      - name: List files
        run: ls -la
    
      # Multi-line script
      - name: Complex script
        run: |
          echo "Starting deployment"
          ./deploy.sh
          echo "Deployment complete"
    
      # Use action with parameters
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
    
      # Conditional step
      - name: Deploy to staging
        if: github.ref == 'refs/heads/develop'
        run: echo "Deploying to staging"
    YAML

    5. Actions and Marketplace

    Using Actions

    Actions are reusable units of code that you can use in your workflows:

    steps:
      # Official GitHub actions
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
    
      # Community actions
      - uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: myapp:latest
    
      # Action from another repository
      - uses: username/action-name@v1
        with:
          parameter: value
    YAML
    mindmap
      root((GitHub Actions Marketplace))
        Setup Actions
          actions/checkout
          actions/setup-node
          actions/setup-python
          actions/setup-java
    
        Build & Deploy
          docker/build-push-action
          azure/webapps-deploy
          peaceiris/actions-gh-pages
    
        Testing
          codecov/codecov-action
          dorny/test-reporter
          mikepenz/action-junit-report
    
        Security
          github/codeql-action
          snyk/actions
          aquasecurity/trivy-action
    
        Utilities
          actions/cache
          actions/upload-artifact
          actions/download-artifact

    Action Inputs and Outputs

    # Using action outputs
    - name: Get current time
      id: time
      uses: josStorer/get-current-time@v2
      with:
        format: YYYYMMDD-HHmmss
        utcOffset: "+08:00"
    
    - name: Use the time
      run: echo "Current time is ${{ steps.time.outputs.time }}"
    
    # Passing secrets to actions
    - uses: some-action@v1
      with:
        api-key: ${{ secrets.API_KEY }}
    YAML

    6. Environment Variables and Secrets

    Environment Variables

    env:
      # Global environment variables
      NODE_ENV: production
      API_URL: https://api.example.com
    
    jobs:
      build:
        runs-on: ubuntu-latest
        env:
          # Job-level environment variables
          BUILD_DIR: ./dist
    
        steps:
          - name: Build with environment variables
            env:
              # Step-level environment variables
              CUSTOM_VAR: custom-value
            run: |
              echo "NODE_ENV: $NODE_ENV"
              echo "API_URL: $API_URL"
              echo "BUILD_DIR: $BUILD_DIR"
              echo "CUSTOM_VAR: $CUSTOM_VAR"
    YAML

    Default Environment Variables

    GitHub provides many default environment variables:

    steps:
      - name: Show GitHub context
        run: |
          echo "Repository: $GITHUB_REPOSITORY"
          echo "Actor: $GITHUB_ACTOR"
          echo "SHA: $GITHUB_SHA"
          echo "Ref: $GITHUB_REF"
          echo "Workspace: $GITHUB_WORKSPACE"
          echo "Run ID: $GITHUB_RUN_ID"
    YAML

    Managing Secrets

    flowchart LR
        A[Developer] --> B[Repository Settings]
        B --> C[Secrets and Variables]
        C --> D[Actions Secrets]
        D --> E[Add Secret]
        E --> F[Use in Workflow]
    
        F --> G[Encrypted in Transit]
        F --> H[Redacted in Logs]

    Using Secrets

    jobs:
      deploy:
        runs-on: ubuntu-latest
        steps:
          - name: Deploy to AWS
            env:
              AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
              AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
            run: |
              aws s3 sync ./dist s3://my-bucket
    
          - name: Send notification
            uses: some-notification-action@v1
            with:
              webhook-url: ${{ secrets.SLACK_WEBHOOK }}
              message: "Deployment successful!"
    YAML

    Environment-Specific Secrets

    jobs:
      deploy:
        runs-on: ubuntu-latest
        environment: production  # Links to environment secrets
        steps:
          - name: Deploy
            env:
              API_KEY: ${{ secrets.PRODUCTION_API_KEY }}
            run: ./deploy.sh
    YAML

    7. Matrix Builds and Strategies

    Basic Matrix Strategy

    Matrix strategies allow you to run the same job with different configurations:

    jobs:
      test:
        runs-on: ${{ matrix.os }}
        strategy:
          matrix:
            os: [ubuntu-latest, windows-latest, macos-latest]
            node-version: [14, 16, 18]
    
        steps:
          - uses: actions/checkout@v3
          - uses: actions/setup-node@v3
            with:
              node-version: ${{ matrix.node-version }}
          - run: npm test
    YAML

    Matrix Visualization

    graph TD
        A[Matrix Job] --> B[ubuntu-latest + Node 14]
        A --> C[ubuntu-latest + Node 16]
        A --> D[ubuntu-latest + Node 18]
        A --> E[windows-latest + Node 14]
        A --> F[windows-latest + Node 16]
        A --> G[windows-latest + Node 18]
        A --> H[macos-latest + Node 14]
        A --> I[macos-latest + Node 16]
        A --> J[macos-latest + Node 18]

    Advanced Matrix Configuration

    jobs:
      test:
        runs-on: ${{ matrix.os }}
        strategy:
          fail-fast: false  # Don't stop other jobs if one fails
          max-parallel: 2   # Limit concurrent jobs
          matrix:
            os: [ubuntu-latest, windows-latest]
            node-version: [16, 18]
            include:
              # Add specific combinations
              - os: macos-latest
                node-version: 18
                experimental: true
            exclude:
              # Remove specific combinations
              - os: windows-latest
                node-version: 16
    
        steps:
          - name: Show matrix values
            run: |
              echo "OS: ${{ matrix.os }}"
              echo "Node: ${{ matrix.node-version }}"
              echo "Experimental: ${{ matrix.experimental }}"
    YAML

    Dynamic Matrix

    jobs:
      setup:
        runs-on: ubuntu-latest
        outputs:
          matrix: ${{ steps.set-matrix.outputs.matrix }}
        steps:
          - id: set-matrix
            run: |
              if [ "${{ github.event_name }}" == "pull_request" ]; then
                echo "matrix=[\"ubuntu-latest\"]" >> $GITHUB_OUTPUT
              else
                echo "matrix=[\"ubuntu-latest\", \"windows-latest\", \"macos-latest\"]" >> $GITHUB_OUTPUT
              fi
    
      test:
        needs: setup
        runs-on: ${{ matrix.os }}
        strategy:
          matrix:
            os: ${{ fromJSON(needs.setup.outputs.matrix) }}
        steps:
          - run: echo "Testing on ${{ matrix.os }}"
    YAML

    8. Conditional Workflows

    Basic Conditionals

    jobs:
      deploy:
        runs-on: ubuntu-latest
        if: github.ref == 'refs/heads/main'
        steps:
          - name: Deploy to production
            run: echo "Deploying to production"
    
      test:
        runs-on: ubuntu-latest
        steps:
          - name: Always run tests
            run: npm test
    
          - name: Run integration tests
            if: github.event_name == 'pull_request'
            run: npm run test:integration
    
          - name: Deploy to staging
            if: |
              github.ref == 'refs/heads/develop' && 
              github.event_name == 'push'
            run: ./deploy-staging.sh
    YAML

    Conditional Expressions

    steps:
      - name: Check conditions
        if: |
          (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) ||
          (github.event_name == 'workflow_dispatch' && github.event.inputs.force_deploy == 'true')
        run: echo "Condition met for deployment"
    
      - name: Check step status
        if: always()  # Run even if previous steps failed
        run: echo "This always runs"
    
      - name: Only on success
        if: success()
        run: echo "Previous steps succeeded"
    
      - name: Only on failure
        if: failure()
        run: echo "Something failed"
    YAML

    Conditional Job Dependencies

    flowchart TD
        A[push event] --> B{Is main branch?}
        B -->|Yes| C[production-deploy]
        B -->|No| D{Is develop branch?}
        D -->|Yes| E[staging-deploy]
        D -->|No| F[test-only]
    
        G[pull_request event] --> F
    
        style C fill:#ffeb3b
        style E fill:#ff9800
        style F fill:#4caf50

    Environment-Based Conditionals

    jobs:
      deploy:
        runs-on: ubuntu-latest
        if: |
          (github.ref == 'refs/heads/main' && vars.ENVIRONMENT == 'production') ||
          (github.ref == 'refs/heads/develop' && vars.ENVIRONMENT == 'staging')
        environment: ${{ vars.ENVIRONMENT }}
        steps:
          - name: Deploy
            run: |
              echo "Deploying to ${{ vars.ENVIRONMENT }}"
    YAML

    9. Custom Actions

    Types of Custom Actions

    graph TD
        A[Custom Actions] --> B[JavaScript Actions]
        A --> C[Docker Actions]
        A --> D[Composite Actions]
    
        B --> B1[Runs on Node.js]
        B --> B2[Fast execution]
        B --> B3[GitHub API access]
    
        C --> C1[Any language/runtime]
        C --> C2[Isolated environment]
        C --> C3[Slower startup]
    
        D --> D1[Combine existing actions]
        D --> D2[Shell scripts]
        D --> D3[Reusable workflows]

    JavaScript Action

    Create a JavaScript action:

    // action.js
    const core = require('@actions/core');
    const github = require('@actions/github');
    
    try {
      const nameToGreet = core.getInput('who-to-greet');
      console.log(`Hello ${nameToGreet}!`);
    
      const time = (new Date()).toTimeString();
      core.setOutput("time", time);
    
      // Get the JSON webhook payload for the event that triggered the workflow
      const payload = JSON.stringify(github.context.payload, undefined, 2);
      console.log(`The event payload: ${payload}`);
    } catch (error) {
      core.setFailed(error.message);
    }
    JavaScript
    # action.yml
    name: 'Hello World'
    description: 'Greet someone and record the time'
    inputs:
      who-to-greet:
        description: 'Who to greet'
        required: true
        default: 'World'
    outputs:
      time:
        description: 'The time we greeted you'
    runs:
      using: 'node16'
      main: 'action.js'
    YAML
    # package.json
    {
      "name": "hello-world-action",
      "version": "1.0.0",
      "main": "action.js",
      "dependencies": {
        "@actions/core": "^1.10.0",
        "@actions/github": "^5.1.1"
      }
    }
    JSON

    Docker Action

    # Dockerfile
    FROM alpine:3.10
    
    COPY entrypoint.sh /entrypoint.sh
    RUN chmod +x /entrypoint.sh
    
    ENTRYPOINT ["/entrypoint.sh"]
    Dockerfile
    #!/bin/sh -l
    # entrypoint.sh
    
    echo "Hello $1"
    time=$(date)
    echo "time=$time" >> $GITHUB_OUTPUT
    Bash
    # action.yml
    name: 'Hello World Docker'
    description: 'Greet someone using Docker'
    inputs:
      who-to-greet:
        description: 'Who to greet'
        required: true
        default: 'World'
    outputs:
      time:
        description: 'The time we greeted you'
    runs:
      using: 'docker'
      image: 'Dockerfile'
      args:
        - ${{ inputs.who-to-greet }}
    YAML

    Composite Action

    # action.yml
    name: 'Setup Node.js with Cache'
    description: 'Setup Node.js and configure npm cache'
    inputs:
      node-version:
        description: 'Node.js version'
        required: true
        default: '18'
      working-directory:
        description: 'Working directory'
        required: false
        default: '.'
    
    runs:
      using: 'composite'
      steps:
        - name: Setup Node.js
          uses: actions/setup-node@v3
          with:
            node-version: ${{ inputs.node-version }}
    
        - name: Cache dependencies
          uses: actions/cache@v3
          with:
            path: ~/.npm
            key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    
        - name: Install dependencies
          run: npm ci
          shell: bash
          working-directory: ${{ inputs.working-directory }}
    YAML

    10. Advanced Patterns

    Reusable Workflows

    # .github/workflows/reusable-deploy.yml
    name: Reusable Deployment
    
    on:
      workflow_call:
        inputs:
          environment:
            required: true
            type: string
          version:
            required: false
            type: string
            default: 'latest'
        secrets:
          deploy-token:
            required: true
        outputs:
          deployment-url:
            description: "URL of the deployment"
            value: ${{ jobs.deploy.outputs.url }}
    
    jobs:
      deploy:
        runs-on: ubuntu-latest
        environment: ${{ inputs.environment }}
        outputs:
          url: ${{ steps.deploy.outputs.url }}
        steps:
          - name: Deploy
            id: deploy
            run: |
              echo "Deploying ${{ inputs.version }} to ${{ inputs.environment }}"
              echo "url=https://${{ inputs.environment }}.example.com" >> $GITHUB_OUTPUT
    YAML
    # .github/workflows/main.yml
    name: Main Workflow
    
    on:
      push:
        branches: [main]
    
    jobs:
      deploy-staging:
        uses: ./.github/workflows/reusable-deploy.yml
        with:
          environment: staging
          version: ${{ github.sha }}
        secrets:
          deploy-token: ${{ secrets.STAGING_DEPLOY_TOKEN }}
    
      deploy-production:
        needs: deploy-staging
        uses: ./.github/workflows/reusable-deploy.yml
        with:
          environment: production
          version: ${{ github.sha }}
        secrets:
          deploy-token: ${{ secrets.PRODUCTION_DEPLOY_TOKEN }}
    YAML

    Workflow Dependencies

    graph TD
        A[Push to main] --> B[Build]
        B --> C[Test Unit]
        B --> D[Test Integration]
        C --> E[Security Scan]
        D --> E
        E --> F[Deploy Staging]
        F --> G[E2E Tests]
        G --> H{Manual Approval}
        H -->|Approved| I[Deploy Production]
        H -->|Rejected| J[Rollback]

    Artifacts and Caching

    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
    
          # Cache dependencies
          - name: Cache node modules
            uses: actions/cache@v3
            with:
              path: ~/.npm
              key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
              restore-keys: |
                ${{ runner.os }}-node-
    
          - name: Install dependencies
            run: npm ci
    
          - name: Build
            run: npm run build
    
          # Upload artifacts
          - name: Upload build artifacts
            uses: actions/upload-artifact@v3
            with:
              name: dist-files
              path: dist/
              retention-days: 30
    
      test:
        needs: build
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
    
          # Download artifacts
          - name: Download build artifacts
            uses: actions/download-artifact@v3
            with:
              name: dist-files
              path: dist/
    
          - name: Run tests
            run: npm test
    YAML

    Multi-Stage Deployments

    name: Multi-Stage Deployment
    
    on:
      push:
        branches: [main]
    
    jobs:
      build:
        runs-on: ubuntu-latest
        outputs:
          version: ${{ steps.version.outputs.version }}
        steps:
          - uses: actions/checkout@v3
          - id: version
            run: echo "version=$(date +%Y%m%d%H%M%S)" >> $GITHUB_OUTPUT
          - name: Build
            run: echo "Building version ${{ steps.version.outputs.version }}"
    
      deploy-dev:
        needs: build
        runs-on: ubuntu-latest
        environment: development
        steps:
          - name: Deploy to dev
            run: echo "Deploying ${{ needs.build.outputs.version }} to dev"
    
      deploy-staging:
        needs: [build, deploy-dev]
        runs-on: ubuntu-latest
        environment: staging
        steps:
          - name: Deploy to staging
            run: echo "Deploying ${{ needs.build.outputs.version }} to staging"
    
      deploy-production:
        needs: [build, deploy-staging]
        runs-on: ubuntu-latest
        environment: production
        steps:
          - name: Deploy to production
            run: echo "Deploying ${{ needs.build.outputs.version }} to production"
    YAML

    11. Security Best Practices

    Security Considerations

    mindmap
      root((Security))
        Secrets Management
          Use repository secrets
          Environment-specific secrets
          Avoid logging secrets
          Rotate regularly
    
        Permissions
          GITHUB_TOKEN permissions
          Least privilege principle
          Read-only when possible
    
        Dependencies
          Pin action versions
          Verify action sources
          Regular updates
          Dependency scanning
    
        Code Security
          Static analysis
          Vulnerability scanning
          Secure coding practices
          Code signing

    Secure Workflow Example

    name: Secure Workflow
    
    on:
      push:
        branches: [main]
      pull_request:
        branches: [main]
    
    permissions:
      contents: read
      security-events: write
      actions: read
    
    jobs:
      security-scan:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout code
            uses: actions/checkout@v4  # Pinned to specific version
    
          - name: Run Trivy vulnerability scanner
            uses: aquasecurity/trivy-action@0.12.0  # Pinned version
            with:
              scan-type: 'fs'
              scan-ref: '.'
              format: 'sarif'
              output: 'trivy-results.sarif'
    
          - name: Upload Trivy scan results
            uses: github/codeql-action/upload-sarif@v2
            if: always()
            with:
              sarif_file: 'trivy-results.sarif'
    
      code-analysis:
        runs-on: ubuntu-latest
        permissions:
          security-events: write
          actions: read
          contents: read
        steps:
          - name: Checkout repository
            uses: actions/checkout@v4
    
          - name: Initialize CodeQL
            uses: github/codeql-action/init@v2
            with:
              languages: javascript
    
          - name: Autobuild
            uses: github/codeql-action/autobuild@v2
    
          - name: Perform CodeQL Analysis
            uses: github/codeql-action/analyze@v2
    YAML

    Secret Management Best Practices

    jobs:
      deploy:
        runs-on: ubuntu-latest
        environment: production
        steps:
          - name: Validate secrets exist
            run: |
              if [ -z "${{ secrets.API_KEY }}" ]; then
                echo "API_KEY secret is not set"
                exit 1
              fi
    
          - name: Use secrets securely
            env:
              API_KEY: ${{ secrets.API_KEY }}
            run: |
              # Good: Use environment variable
              curl -H "Authorization: Bearer $API_KEY" api.example.com
    
              # Bad: Don't echo secrets
              # echo "API Key: $API_KEY"
    
          - name: Clean up sensitive data
            if: always()
            run: |
              unset API_KEY
              rm -f /tmp/sensitive-file
    YAML

    GITHUB_TOKEN Permissions

    name: Limited Permissions
    
    on: [push, pull_request]
    
    permissions:
      contents: read    # Read repository contents
      packages: write   # Publish packages
      pull-requests: write  # Comment on PRs
    
    jobs:
      build:
        runs-on: ubuntu-latest
        permissions:
          contents: read
          packages: write
        steps:
          - uses: actions/checkout@v4
          - name: Build and publish
            env:
              GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
            run: |
              # Build and publish with minimal permissions
              npm run build
              npm publish
    YAML

    12. Monitoring and Debugging

    Debugging Workflows

    name: Debug Workflow
    
    on: [push]
    
    jobs:
      debug:
        runs-on: ubuntu-latest
        steps:
          - name: Enable debug logging
            run: echo "::debug::This is a debug message"
    
          - name: Show environment
            run: |
              echo "::group::Environment Variables"
              env | sort
              echo "::endgroup::"
    
          - name: Show GitHub context
            run: |
              echo "::group::GitHub Context"
              echo "Event: ${{ github.event_name }}"
              echo "Repository: ${{ github.repository }}"
              echo "Actor: ${{ github.actor }}"
              echo "SHA: ${{ github.sha }}"
              echo "Ref: ${{ github.ref }}"
              echo "::endgroup::"
    
          - name: Show runner context
            run: |
              echo "::group::Runner Information"
              echo "OS: ${{ runner.os }}"
              echo "Architecture: ${{ runner.arch }}"
              echo "Temp directory: ${{ runner.temp }}"
              echo "Workspace: ${{ runner.workspace }}"
              echo "::endgroup::"
    
          - name: Conditional debugging
            if: runner.debug
            run: |
              echo "Debug mode is enabled"
              ls -la ${{ github.workspace }}
    YAML

    Logging and Annotations

    steps:
      - name: Logging examples
        run: |
          echo "::notice::This is a notice"
          echo "::warning::This is a warning"
          echo "::error::This is an error"
    
          echo "::notice file=app.js,line=1,col=5,endColumn=7::Missing semicolon"
          echo "::warning file=package.json::Outdated dependency"
    
      - name: Group logs
        run: |
          echo "::group::Build Process"
          echo "Starting build..."
          npm run build
          echo "Build completed"
          echo "::endgroup::"
    
      - name: Mask sensitive values
        run: |
          TEMP_PASSWORD="secret123"
          echo "::add-mask::$TEMP_PASSWORD"
          echo "Password is: $TEMP_PASSWORD"  # This will be masked in logs
    YAML

    Workflow Monitoring

    graph TD
        A[Workflow Run] --> B[Job Execution]
        B --> C[Step Execution]
    
        B --> D[Logs]
        B --> E[Annotations]
        B --> F[Status Checks]
    
        D --> G[Debug Information]
        E --> H[Warnings/Errors]
        F --> I[Success/Failure]
    
        G --> J[Troubleshooting]
        H --> J
        I --> J
    
        J --> K[Workflow Optimization]

    Performance Monitoring

    jobs:
      performance-test:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout
            uses: actions/checkout@v4
    
          - name: Setup Node.js
            uses: actions/setup-node@v3
            with:
              node-version: '18'
    
          - name: Install dependencies
            run: |
              start_time=$(date +%s)
              npm ci
              end_time=$(date +%s)
              duration=$((end_time - start_time))
              echo "::notice::Dependency installation took ${duration} seconds"
    
          - name: Run performance tests
            run: |
              npm run test:performance | tee performance-results.txt
    
          - name: Upload performance results
            uses: actions/upload-artifact@v3
            with:
              name: performance-results
              path: performance-results.txt
    YAML

    13. Enterprise Features

    Self-Hosted Runners

    name: Self-Hosted Runner Example
    
    on: [push]
    
    jobs:
      build:
        runs-on: [self-hosted, linux, x64]
        steps:
          - uses: actions/checkout@v4
          - name: Build on self-hosted runner
            run: |
              echo "Running on $(hostname)"
              ./build.sh
    YAML

    Runner Groups and Labels

    graph TD
        A[GitHub Enterprise] --> B[Runner Groups]
    
        B --> C[Default Group]
        B --> D[Production Group]
        B --> E[Development Group]
    
        C --> F[ubuntu-latest runners]
        D --> G[production, secure]
        E --> H[dev, testing]
    
        I[Workflows] --> J{Runner Selection}
        J --> K[runs-on: production, secure]
        J --> L[runs-on: self-hosted, dev]

    Organization Policies

    # Organization-level workflow template
    name: Organization Standard Workflow
    
    on:
      push:
        branches: [main]
      pull_request:
        branches: [main]
    
    jobs:
      security-compliance:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
    
          # Required security scanning
          - name: Run security scan
            uses: github/codeql-action/analyze@v2
    
          # Required license check
          - name: License compliance
            run: ./scripts/check-licenses.sh
    
      build-test:
        needs: security-compliance
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - name: Build and test
            run: |
              npm ci
              npm run build
              npm test
    YAML

    Environment Protection Rules

    name: Protected Deployment
    
    on:
      push:
        branches: [main]
    
    jobs:
      deploy-production:
        runs-on: ubuntu-latest
        environment: 
          name: production
          url: https://prod.example.com
        steps:
          - name: Deploy to production
            run: |
              echo "Deploying to production environment"
              echo "This requires manual approval"
    YAML

    Audit Logging

    jobs:
      audit-example:
        runs-on: ubuntu-latest
        steps:
          - name: Log deployment action
            run: |
              echo "::notice::Deployment initiated by ${{ github.actor }}"
              echo "::notice::Repository: ${{ github.repository }}"
              echo "::notice::SHA: ${{ github.sha }}"
              echo "::notice::Timestamp: $(date -Iseconds)"
    
          - name: Send audit log
            env:
              AUDIT_WEBHOOK: ${{ secrets.AUDIT_WEBHOOK }}
            run: |
              curl -X POST "$AUDIT_WEBHOOK" \
                -H "Content-Type: application/json" \
                -d '{
                  "event": "deployment",
                  "actor": "${{ github.actor }}",
                  "repository": "${{ github.repository }}",
                  "sha": "${{ github.sha }}",
                  "timestamp": "'$(date -Iseconds)'"
                }'
    YAML

    14. Real-World Examples

    Node.js Application CI/CD

    name: Node.js Application CI/CD
    
    on:
      push:
        branches: [main, develop]
      pull_request:
        branches: [main]
    
    env:
      NODE_VERSION: 18
    
    jobs:
      test:
        runs-on: ubuntu-latest
        strategy:
          matrix:
            node-version: [16, 18, 20]
        steps:
          - uses: actions/checkout@v4
    
          - name: Setup Node.js
            uses: actions/setup-node@v3
            with:
              node-version: ${{ matrix.node-version }}
              cache: 'npm'
    
          - name: Install dependencies
            run: npm ci
    
          - name: Run linter
            run: npm run lint
    
          - name: Run tests with coverage
            run: npm run test:coverage
    
          - name: Upload coverage to Codecov
            uses: codecov/codecov-action@v3
            with:
              file: ./coverage/lcov.info
    
      build:
        needs: test
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
    
          - name: Setup Node.js
            uses: actions/setup-node@v3
            with:
              node-version: ${{ env.NODE_VERSION }}
              cache: 'npm'
    
          - name: Install dependencies
            run: npm ci
    
          - name: Build application
            run: npm run build
    
          - name: Upload build artifacts
            uses: actions/upload-artifact@v3
            with:
              name: dist-files
              path: dist/
    
      deploy-staging:
        needs: [test, build]
        runs-on: ubuntu-latest
        if: github.ref == 'refs/heads/develop'
        environment: staging
        steps:
          - name: Download artifacts
            uses: actions/download-artifact@v3
            with:
              name: dist-files
              path: dist/
    
          - name: Deploy to staging
            env:
              STAGING_URL: ${{ vars.STAGING_URL }}
              DEPLOY_TOKEN: ${{ secrets.STAGING_DEPLOY_TOKEN }}
            run: |
              echo "Deploying to $STAGING_URL"
              # Your deployment script here
    
      deploy-production:
        needs: [test, build]
        runs-on: ubuntu-latest
        if: github.ref == 'refs/heads/main'
        environment: production
        steps:
          - name: Download artifacts
            uses: actions/download-artifact@v3
            with:
              name: dist-files
              path: dist/
    
          - name: Deploy to production
            env:
              PRODUCTION_URL: ${{ vars.PRODUCTION_URL }}
              DEPLOY_TOKEN: ${{ secrets.PRODUCTION_DEPLOY_TOKEN }}
            run: |
              echo "Deploying to $PRODUCTION_URL"
              # Your deployment script here
    YAML

    Docker Application Pipeline

    name: Docker Application Pipeline
    
    on:
      push:
        branches: [main]
        tags: ['v*']
      pull_request:
        branches: [main]
    
    env:
      REGISTRY: ghcr.io
      IMAGE_NAME: ${{ github.repository }}
    
    jobs:
      build-and-test:
        runs-on: ubuntu-latest
        permissions:
          contents: read
          packages: write
        steps:
          - name: Checkout repository
            uses: actions/checkout@v4
    
          - name: Set up Docker Buildx
            uses: docker/setup-buildx-action@v3
    
          - name: Log in to Container Registry
            if: github.event_name != 'pull_request'
            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}}
    
          - name: Build and push Docker image
            uses: docker/build-push-action@v5
            with:
              context: .
              platforms: linux/amd64,linux/arm64
              push: ${{ github.event_name != 'pull_request' }}
              tags: ${{ steps.meta.outputs.tags }}
              labels: ${{ steps.meta.outputs.labels }}
              cache-from: type=gha
              cache-to: type=gha,mode=max
    
      security-scan:
        runs-on: ubuntu-latest
        needs: build-and-test
        if: github.event_name != 'pull_request'
        steps:
          - name: Run Trivy vulnerability scanner
            uses: aquasecurity/trivy-action@master
            with:
              image-ref: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}'
              format: 'sarif'
              output: 'trivy-results.sarif'
    
          - name: Upload Trivy scan results to GitHub Security tab
            uses: github/codeql-action/upload-sarif@v2
            with:
              sarif_file: 'trivy-results.sarif'
    
      deploy:
        runs-on: ubuntu-latest
        needs: [build-and-test, security-scan]
        if: github.ref == 'refs/heads/main'
        environment: production
        steps:
          - name: Deploy to production
            run: |
              echo "Deploying ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}"
              # Your deployment script here
    YAML

    Multi-Language Monorepo

    name: Monorepo CI/CD
    
    on:
      push:
        branches: [main]
      pull_request:
        branches: [main]
    
    jobs:
      detect-changes:
        runs-on: ubuntu-latest
        outputs:
          frontend: ${{ steps.changes.outputs.frontend }}
          backend: ${{ steps.changes.outputs.backend }}
          shared: ${{ steps.changes.outputs.shared }}
        steps:
          - uses: actions/checkout@v4
          - uses: dorny/paths-filter@v2
            id: changes
            with:
              filters: |
                frontend:
                  - 'frontend/**'
                  - 'shared/**'
                backend:
                  - 'backend/**'
                  - 'shared/**'
                shared:
                  - 'shared/**'
    
      test-frontend:
        needs: detect-changes
        if: needs.detect-changes.outputs.frontend == 'true'
        runs-on: ubuntu-latest
        defaults:
          run:
            working-directory: ./frontend
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v3
            with:
              node-version: '18'
              cache: 'npm'
              cache-dependency-path: frontend/package-lock.json
          - run: npm ci
          - run: npm run test
          - run: npm run build
    
      test-backend:
        needs: detect-changes
        if: needs.detect-changes.outputs.backend == 'true'
        runs-on: ubuntu-latest
        defaults:
          run:
            working-directory: ./backend
        services:
          postgres:
            image: postgres:14
            env:
              POSTGRES_PASSWORD: postgres
            options: >-
              --health-cmd pg_isready
              --health-interval 10s
              --health-timeout 5s
              --health-retries 5
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-python@v4
            with:
              python-version: '3.11'
              cache: 'pip'
          - run: pip install -r requirements.txt
          - run: python -m pytest
            env:
              DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
    
      deploy:
        needs: [detect-changes, test-frontend, test-backend]
        if: always() && github.ref == 'refs/heads/main'
        runs-on: ubuntu-latest
        steps:
          - name: Deploy frontend
            if: needs.test-frontend.result == 'success'
            run: echo "Deploying frontend"
    
          - name: Deploy backend
            if: needs.test-backend.result == 'success'
            run: echo "Deploying backend"
    YAML

    Infrastructure as Code

    name: Infrastructure Deployment
    
    on:
      push:
        branches: [main]
        paths: ['infrastructure/**']
      pull_request:
        branches: [main]
        paths: ['infrastructure/**']
    
    jobs:
      terraform-plan:
        runs-on: ubuntu-latest
        defaults:
          run:
            working-directory: ./infrastructure
        steps:
          - uses: actions/checkout@v4
    
          - name: Setup Terraform
            uses: hashicorp/setup-terraform@v2
            with:
              terraform_version: 1.5.0
    
          - name: Configure AWS credentials
            uses: aws-actions/configure-aws-credentials@v3
            with:
              aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
              aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
              aws-region: us-east-1
    
          - name: Terraform Init
            run: terraform init
    
          - name: Terraform Format Check
            run: terraform fmt -check
    
          - name: Terraform Plan
            id: plan
            run: |
              terraform plan -no-color -out=tfplan
              terraform show -no-color tfplan > plan.txt
    
          - name: Comment PR with plan
            if: github.event_name == 'pull_request'
            uses: actions/github-script@v6
            with:
              script: |
                const fs = require('fs');
                const plan = fs.readFileSync('infrastructure/plan.txt', 'utf8');
    
                github.rest.issues.createComment({
                  issue_number: context.issue.number,
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  body: `## Terraform Plan\n\`\`\`\n${plan}\n\`\`\``
                });
    
      terraform-apply:
        needs: terraform-plan
        runs-on: ubuntu-latest
        if: github.ref == 'refs/heads/main'
        environment: production
        defaults:
          run:
            working-directory: ./infrastructure
        steps:
          - uses: actions/checkout@v4
    
          - name: Setup Terraform
            uses: hashicorp/setup-terraform@v2
            with:
              terraform_version: 1.5.0
    
          - name: Configure AWS credentials
            uses: aws-actions/configure-aws-credentials@v3
            with:
              aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
              aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
              aws-region: us-east-1
    
          - name: Terraform Init
            run: terraform init
    
          - name: Terraform Apply
            run: terraform apply -auto-approve
    YAML

    Conclusion

    GitHub Actions is a powerful platform that can automate virtually any software development workflow. From simple CI/CD pipelines to complex multi-environment deployments, GitHub Actions provides the flexibility and power needed to streamline your development process.

    Key takeaways:

    • Start simple and gradually add complexity
    • Use reusable workflows and actions to avoid duplication
    • Follow security best practices for secrets and permissions
    • Monitor and debug your workflows for optimal performance
    • Leverage the community marketplace for common tasks

    Remember that GitHub Actions is continuously evolving, so stay updated with the latest features and best practices through the official documentation and community resources.

    Happy automating! 🚀


    Discover more from Altgr Blog

    Subscribe to get the latest posts sent to your email.

    Leave a Reply

    Your email address will not be published. Required fields are marked *