DEV Community

Harsh Mishra
Harsh Mishra

Posted on

Github Actions Full Guide

Introduction to GitHub Actions

1.1 What is GitHub Actions?

GitHub Actions is a powerful feature within GitHub that allows developers to automate tasks in their software development workflows directly within their GitHub repositories. It provides a seamless way to build, test, and deploy code, making it an integral tool in DevOps pipelines and modern software development.

Overview and Use Cases

GitHub Actions stands out as a Continuous Integration/Continuous Deployment (CI/CD) tool. However, its versatility extends far beyond just CI/CD:

  • Automating CI/CD Pipelines: Automate code builds, run tests, and deploy applications seamlessly.
  • Managing Repository Workflows: Automatically label issues, assign reviewers, and trigger custom scripts.
  • Security Checks: Perform automated security scans and apply fixes using tools like Dependabot.
  • Custom Development Tasks: Automate mundane tasks such as merging pull requests, generating release notes, or even interacting with APIs.

Whether you're a solo developer managing side projects or part of a large enterprise, GitHub Actions is a game-changer for increasing productivity and reliability in software projects.

Key Features and Benefits

  1. Integrated with GitHub

    GitHub Actions is tightly integrated into the GitHub ecosystem, allowing developers to manage automation alongside their code without relying on external services.

  2. Event-Driven

    Workflows are triggered by events, such as a new commit, a pull request, or a scheduled time, providing immense flexibility.

  3. Pre-Built Marketplace Actions

    GitHub Actions has a vast marketplace of reusable actions for tasks like deploying to AWS, running tests, or notifying team members in Slack.

  4. Scalability and Customization

    Use GitHub-hosted runners or configure self-hosted runners for complete control over your execution environment.

  5. Free for Public Repositories

    For open-source projects, GitHub Actions offers free unlimited minutes on GitHub-hosted runners, making it an attractive choice for developers.

1.2 Core Concepts

To fully grasp GitHub Actions, understanding its core building blocks is essential:

Workflows

Workflows are the backbone of GitHub Actions. Defined in YAML files and stored in the .github/workflows directory, they specify a series of steps to automate tasks.

  • Example: A workflow might automate the deployment of your application to a server whenever new code is pushed to the main branch.

Jobs

Jobs are a collection of steps executed on a runner. A workflow can contain multiple jobs, which can either run concurrently or have dependencies.

  • Example: A CI pipeline might include separate jobs for running tests, building the application, and deploying it.

Steps

Steps are individual tasks executed in a job. Each step can run commands, execute scripts, or invoke prebuilt or custom actions.

  • Example: A step might involve checking out the repository's code or running a shell script.

Actions

Actions are reusable units of code that perform specific tasks. Developers can use pre-built actions from the GitHub Marketplace or create custom actions for tailored automation needs.

  • Example: The actions/checkout action is commonly used to check out a repository’s code in a workflow.

Events and Triggers

Events trigger workflows. GitHub supports a wide range of events, such as push, pull_request, or even manual invocations via workflow_dispatch.

  • Example: You might trigger a workflow every time a new pull request is created to ensure the changes pass all tests.

Runners

Runners are the servers that execute the jobs defined in workflows.

  • GitHub-Hosted Runners: Provide managed environments for Linux, macOS, and Windows.
  • Self-Hosted Runners: Allow complete control over the environment, ideal for specific dependencies or compliance needs.

With a solid understanding of these concepts, you're ready to create workflows that are not only powerful but also scalable and efficient. In the next section, we’ll dive into how to set up your first GitHub Actions workflow.

Workflow Basics

2.1 YAML Syntax Primer

GitHub Actions workflows are defined in YAML (Yet Another Markup Language), a lightweight and human-readable data serialization format. YAML is straightforward to write and understand, but its simplicity can sometimes cause confusion for those unfamiliar with its syntax. This section provides a detailed primer on YAML to help you confidently write GitHub Actions workflows.

Understanding YAML Structure

YAML organizes data into a hierarchy using indentation to define structure. The building blocks of YAML include key-value pairs, lists, mappings, and nested elements. Let's break down these elements step by step:

Key-Value Pairs

The basic structure of YAML revolves around key-value pairs. Keys represent the variable or property name, and values represent their corresponding data.

name: Build Workflow
on: push
Enter fullscreen mode Exit fullscreen mode

Here, name is the key, and Build Workflow is its value. Similarly, on is a key with the value push.

Lists

Lists are collections of items. In YAML, they are defined with a - (dash) followed by a space. Lists are commonly used in GitHub Actions to define multiple triggers, steps, or configurations.

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Step One
        run: echo "Hello, World!"
      - name: Step Two
        run: echo "Goodbye, World!"
Enter fullscreen mode Exit fullscreen mode

Here, the steps key contains a list of items, each representing a different step in the workflow.

Mappings (Dictionaries)

Mappings are collections of key-value pairs grouped under a single key. They allow nesting of properties to create a hierarchy.

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Run Tests
        run: npm test
Enter fullscreen mode Exit fullscreen mode

In this example, jobs is a mapping with build as a key. Within build, there are additional nested key-value pairs like runs-on and steps.

Comments

YAML supports comments for documentation and explanations. Comments begin with a # symbol and are ignored during execution.

# This is a comment
name: Example Workflow # Inline comments are also supported
Enter fullscreen mode Exit fullscreen mode

Common YAML Elements in GitHub Actions Workflows

Now that we understand YAML basics, let's look at the common elements you'll encounter when writing workflows for GitHub Actions:

1. Workflow Name

The name key specifies the name of the workflow. This name is displayed in the GitHub Actions UI.

name: CI/CD Pipeline
Enter fullscreen mode Exit fullscreen mode

2. Workflow Triggers (on)

The on key defines the events that trigger the workflow. It can be a single event, a list of events, or an advanced configuration using types and branches.

Single Event:

on: push
Enter fullscreen mode Exit fullscreen mode

Multiple Events:

on:
  - push
  - pull_request
Enter fullscreen mode Exit fullscreen mode

Advanced Configuration:

on:
  push:
    branches:
      - main
      - dev
  pull_request:
    types:
      - opened
      - closed
Enter fullscreen mode Exit fullscreen mode

This configuration specifies that the workflow will trigger on push events to the main or dev branches and specific pull_request events like opened and closed.

3. Jobs

The jobs key contains the definition of all jobs in the workflow. Each job has a unique name and includes keys like runs-on, steps, and dependencies.

Basic Job Structure:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v3
Enter fullscreen mode Exit fullscreen mode

4. Steps

Steps are individual tasks within a job. Each step can either run commands or use pre-built actions.

Command-Based Steps:

steps:
  - name: Install Dependencies
    run: npm install
Enter fullscreen mode Exit fullscreen mode

Action-Based Steps:

steps:
  - name: Checkout Code
    uses: actions/checkout@v3
Enter fullscreen mode Exit fullscreen mode

5. Environment Variables

Environment variables are defined at the job or step level. They allow dynamic data to be passed into workflows.

jobs:
  build:
    env:
      NODE_ENV: production
    steps:
      - name: Print Environment Variable
        run: echo "Environment: $NODE_ENV"
Enter fullscreen mode Exit fullscreen mode

6. Outputs

Jobs or steps can define outputs that subsequent jobs can consume.

Defining Outputs:

jobs:
  build:
    steps:
      - name: Set Output
        run: echo "::set-output name=version::1.0.0"
Enter fullscreen mode Exit fullscreen mode

Using Outputs:

jobs:
  deploy:
    needs: build
    steps:
      - name: Deploy Version
        run: echo "Deploying version ${{ needs.build.outputs.version }}"
Enter fullscreen mode Exit fullscreen mode

Best Practices for Writing YAML in GitHub Actions

  1. Consistency in Indentation: YAML relies heavily on indentation. Always use spaces, not tabs, and maintain consistent levels.
  2. Comment Extensively: Add comments to explain the purpose of keys and steps for easier collaboration and maintenance.
  3. Reuse Code: Use reusable workflows and composite actions to avoid duplication.
  4. Validate Syntax: Use YAML validation tools to catch syntax errors before committing workflows.

With this foundational understanding of YAML and its use in GitHub Actions workflows, you’re ready to begin writing efficient and effective workflows. In the next section, we’ll dive into how to structure jobs and steps for real-world scenarios.

2.2 Workflow Files

GitHub Actions workflows are defined using workflow files, which are written in YAML format and stored in the .github/workflows/ directory of a repository. Understanding the anatomy of a workflow file and how to create and manage these files is crucial to effectively using GitHub Actions.

Anatomy of a Workflow File

A workflow file is a structured YAML document that defines a series of jobs and steps to automate tasks. Here's a breakdown of its components:

1. Workflow Name

The name key specifies the workflow's name, which appears in the GitHub Actions UI. This is optional, but a meaningful name improves clarity.

name: CI/CD Pipeline
Enter fullscreen mode Exit fullscreen mode

2. Trigger Events (on)

The on key defines the events that trigger the workflow. These can be simple events, multiple events, or advanced configurations.

Example of a Push Event Trigger:

on: push
Enter fullscreen mode Exit fullscreen mode

Example of Multiple Events:

on:
  - push
  - pull_request
Enter fullscreen mode Exit fullscreen mode

Advanced Trigger Configuration:

on:
  push:
    branches:
      - main
      - dev
  pull_request:
    types:
      - opened
      - reopened
      - closed
  schedule:
    - cron: "0 0 * * *" # Trigger every day at midnight UTC
Enter fullscreen mode Exit fullscreen mode

3. Jobs

The jobs key is a collection of tasks that define the steps that need to be executed in the workflow. Each job runs in its own virtual environment (runner) and can have dependencies on other jobs.

Each job is defined by a unique name and contains a series of steps.

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v3
Enter fullscreen mode Exit fullscreen mode

4. Steps

Each job consists of steps. Steps are individual tasks executed sequentially within the job. They can run commands, invoke actions, or perform other tasks necessary for the workflow. Each step can use either a predefined GitHub Action or custom shell commands.

Example of Steps:

steps:
  - name: Checkout Code
    uses: actions/checkout@v3
  - name: Set Up Node.js
    uses: actions/setup-node@v3
    with:
      node-version: '14'
  - name: Install Dependencies
    run: npm install
  - name: Run Tests
    run: npm test
Enter fullscreen mode Exit fullscreen mode

5. Environment Variables

You can define environment variables at the job or step level. These variables are passed into the job and can be accessed by the steps.

Example:

jobs:
  build:
    env:
      NODE_ENV: production
    steps:
      - name: Print Environment Variable
        run: echo "Environment: $NODE_ENV"
Enter fullscreen mode Exit fullscreen mode

6. Dependencies Between Jobs

You can specify dependencies between jobs using the needs keyword. This ensures that one job runs only after the completion of another job.

Example of Job Dependencies:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to Production
        run: echo "Deploying to production..."
Enter fullscreen mode Exit fullscreen mode

In this example, the deploy job will only run after the build job has completed successfully.

Creating and Managing Workflow Files

Workflow files are typically stored in the .github/workflows/ directory within your repository. To create and manage workflow files:

  1. Create a New Workflow File

    Create a new YAML file in the .github/workflows/ directory. The file should have a .yml or .yaml extension. For example, ci.yml or deploy.yml.

  2. Define the Workflow in the File

    Within the workflow file, define the necessary components such as the workflow name, trigger events, jobs, steps, and any other necessary configurations.

  3. Commit the Workflow File

    Once the workflow file is created and defined, commit and push the changes to your repository. GitHub will automatically detect the workflow file and run it based on the defined triggers.

  4. Manage Workflow Files

    Workflow files can be edited and version-controlled just like any other code file. GitHub will trigger workflows based on the latest version of the workflow file, so changes will automatically take effect after pushing new commits.

  5. View Workflow Execution

    After a workflow is triggered, you can view the execution status on the GitHub Actions tab in your repository. GitHub provides detailed logs for each step of the workflow, allowing you to troubleshoot and monitor progress.

By following these steps, you can effectively create, manage, and monitor workflow files, automating tasks such as testing, deployment, and more.

2.3 Triggering Workflows

Triggering workflows is at the core of GitHub Actions. Workflows are executed in response to specific events, defined in the on key within the workflow file. Understanding the available triggers and filters allows you to fine-tune when and how workflows run.

Event Triggers

GitHub provides several types of events that can trigger workflows. Some of the most commonly used triggers are:

1. Push Events

The push event triggers a workflow whenever changes are pushed to a repository. This is ideal for tasks like continuous integration or deployment.

on: push
Enter fullscreen mode Exit fullscreen mode

You can further filter push events by branch, tag, or specific file paths.

Example: Trigger on Push to main Branch Only

on:
  push:
    branches:
      - main
Enter fullscreen mode Exit fullscreen mode

2. Pull Request Events

The pull_request event triggers a workflow whenever a pull request is created, updated, merged, or closed. This is useful for reviewing and testing changes before merging them into the main branch.

on: pull_request
Enter fullscreen mode Exit fullscreen mode

You can configure pull_request triggers to run only for specific actions, branches, or patterns.

Example: Trigger for Specific Actions

on:
  pull_request:
    types:
      - opened
      - synchronize
Enter fullscreen mode Exit fullscreen mode

3. Schedule Events

The schedule event allows workflows to run at predefined times, specified using a cron syntax. This is useful for recurring tasks like backups, performance monitoring, or periodic reporting.

Example: Trigger Daily at Midnight UTC

on:
  schedule:
    - cron: "0 0 * * *"
Enter fullscreen mode Exit fullscreen mode

4. Manual Trigger (Workflow Dispatch)

The workflow_dispatch event lets you manually trigger a workflow via the GitHub Actions UI. This is ideal for ad-hoc tasks like running maintenance scripts or deploying hotfixes.

Example: Enabling Manual Trigger

on:
  workflow_dispatch:
    inputs:
      environment:
        description: "Select environment"
        required: true
        default: "production"
        type: string
Enter fullscreen mode Exit fullscreen mode

With the workflow_dispatch input configuration, users can specify custom inputs (like environment) when triggering the workflow manually.


Filters

Filters allow you to refine workflow triggers, ensuring workflows only run for specific branches, tags, or file paths.

1. Branch Filters

Branch filters let you specify which branches trigger the workflow. You can include or exclude branches using branches and branches-ignore.

Example: Include Specific Branches

on:
  push:
    branches:
      - main
      - dev
Enter fullscreen mode Exit fullscreen mode

Example: Exclude Specific Branches

on:
  push:
    branches-ignore:
      - staging
Enter fullscreen mode Exit fullscreen mode

2. Tag Filters

Tag filters let you trigger workflows based on Git tags, commonly used for releases.

Example: Trigger for Specific Tags

on:
  push:
    tags:
      - "v1.*" # Trigger for all tags starting with v1.
Enter fullscreen mode Exit fullscreen mode

3. Path Filters

Path filters allow workflows to run only when changes are made to specific files or directories. This is useful for workflows tied to specific parts of a codebase.

Example: Include Specific Paths

on:
  push:
    paths:
      - "src/**" # Trigger for changes in the src directory
      - "README.md" # Trigger for changes to README.md
Enter fullscreen mode Exit fullscreen mode

Example: Exclude Specific Paths

on:
  push:
    paths-ignore:
      - "docs/**" # Ignore changes in the docs directory
Enter fullscreen mode Exit fullscreen mode

Combining Triggers and Filters

You can combine multiple triggers and filters to define complex workflows. For example:

Example: Combined Trigger Configuration

on:
  push:
    branches:
      - main
    tags:
      - "release-*"
    paths:
      - "src/**"
  pull_request:
    branches:
      - main
  schedule:
    - cron: "0 0 * * *"
Enter fullscreen mode Exit fullscreen mode

In this configuration:

  • The workflow triggers on push to the main branch, but only for release-* tags and changes in the src directory.
  • It also triggers on pull_request events targeting the main branch.
  • Finally, it runs every day at midnight UTC.

By mastering event triggers and filters, you can ensure that workflows are efficient, targeted, and responsive to your development needs. This flexibility is one of GitHub Actions' most powerful features.

Jobs, Steps, and Runners

3.1 Understanding Jobs and Steps

Jobs and steps are the building blocks of GitHub Actions workflows. Together, they define the tasks a workflow performs and how those tasks are executed. This section provides a comprehensive guide to defining jobs, configuring their dependencies, and detailing steps within them.

What are Jobs?

In GitHub Actions, jobs are units of work performed by a workflow. Each job is executed on a runner, which is a virtual machine (or container) that processes the defined tasks. Jobs can run independently in parallel, depend on other jobs to create a sequence of execution, and execute specific tasks using steps.

Defining Jobs

Jobs are defined under the jobs key in a workflow file. Each job has a unique identifier and contains configuration keys, such as runs-on to specify the environment (runner) where the job will execute and steps for a list of tasks within the job.

Example of a Basic Job:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v3
Enter fullscreen mode Exit fullscreen mode

In this example, build is the job name, runs-on specifies that the job will execute on an Ubuntu-based runner, and steps defines the tasks to be performed within the job.

Configuring Job Dependencies

By default, jobs run independently and in parallel. However, you can create dependencies between jobs using the needs keyword. A job that depends on another will wait for its completion before executing.

Example of Dependent Jobs:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Build Application
        run: npm run build

  test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Run Tests
        run: npm test

  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to Production
        run: npm run deploy
Enter fullscreen mode Exit fullscreen mode

In this example, the test job depends on build, so it will run only after the build job completes. The deploy job depends on test, so it runs after test finishes. This sequencing ensures tasks are executed in the correct order.

What are Steps?

Steps are the individual tasks that a job performs. Steps are defined inside a job and executed sequentially. Each step can use a predefined action from the GitHub Actions Marketplace or execute a custom command using the run key.

Configuring Steps within Jobs

Steps are defined as a list under the steps key in a job. Each step contains a name for a descriptive label (optional but recommended), uses to specify a predefined action, and run to execute a shell command.

Example: Steps in a Job

jobs:
  example-job:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

      - name: Set Up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '14'

      - name: Install Dependencies
        run: npm install

      - name: Run Tests
        run: npm test
Enter fullscreen mode Exit fullscreen mode

In this example, the first step checks out the code using the actions/checkout action. The second step sets up Node.js with version 14. The third and fourth steps install dependencies and run tests using custom shell commands.

Best Practices for Jobs and Steps

Use descriptive names for each job and step to make workflows easier to understand and debug. Minimize dependencies to reduce wait times and improve workflow efficiency. Leverage predefined actions from the GitHub Marketplace to simplify workflows and save development time. Isolate tasks by breaking down workflows into smaller, reusable steps or jobs to enhance modularity and maintainability. Fail fast by validating prerequisites (e.g., environment setup) in early steps to catch issues early and fail workflows quickly.

By effectively defining jobs, configuring their dependencies, and structuring steps, you can create powerful, efficient workflows that automate complex development and deployment processes. These foundational concepts form the basis for mastering GitHub Actions.

3.2 Job Outputs and Job Artifacts

GitHub Actions allows jobs to pass data and artifacts between them, enabling workflows to share results and facilitate multi-step pipelines. This is achieved using job outputs and job artifacts.

Job Outputs

Job outputs allow you to pass data from one job to another in the same workflow. Outputs are defined in one job and consumed by another using the needs keyword.

Setting Job Outputs

Job outputs are declared in the outputs property of a job. Each output is assigned a value by writing to the $GITHUB_OUTPUT file in one of the job’s steps.

Example: Setting Outputs

jobs:
  generate-output:
    runs-on: ubuntu-latest
    outputs:
      result: ${{ steps.step1.outputs.result }}
    steps:
      - name: Generate an Output
        id: step1
        run: echo "result=Hello, World!" >> $GITHUB_OUTPUT

  use-output:
    needs: generate-output
    runs-on: ubuntu-latest
    steps:
      - name: Access Output
        run: echo "The output is: ${{ needs.generate-output.outputs.result }}"
Enter fullscreen mode Exit fullscreen mode

In this example:

  • The outputs property is declared in the generate-output job, referencing the result of step1.
  • The result variable is set by writing result=Hello, World! to $GITHUB_OUTPUT.
  • The use-output job accesses this output via needs.generate-output.outputs.result.

Using Job Outputs

Outputs are especially useful for sharing computed values between jobs and dynamically modifying workflow behavior based on earlier results.

Example: Dynamic Workflow Behavior

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      deploy_flag: ${{ steps.check.outputs.isDeployRequired }}
    steps:
      - name: Check if Deployment is Needed
        id: check
        run: echo "isDeployRequired=true" >> $GITHUB_OUTPUT

  deploy:
    needs: build
    if: ${{ needs.build.outputs.deploy_flag == 'true' }}
    runs-on: ubuntu-latest
    steps:
      - name: Deploy Code
        run: echo "Deploying the application..."
Enter fullscreen mode Exit fullscreen mode

In this example:

  • The build job declares an output deploy_flag, set by the check step.
  • The deploy job runs only if the deploy_flag output is true.

Job Artifacts

Artifacts are files or directories saved during a workflow and shared between jobs or downloaded for later inspection. They are commonly used for logs, reports, build outputs (e.g., binaries, test results), and debugging purposes.

Saving Artifacts

You can save artifacts using the actions/upload-artifact action. Artifacts are stored with the workflow and can be accessed later.

Example: Saving Artifacts

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Run Tests
        run: |
          mkdir test-results
          echo "Test passed!" > test-results/report.txt
      - name: Upload Test Results
        uses: actions/upload-artifact@v3
        with:
          name: test-results
          path: test-results/
Enter fullscreen mode Exit fullscreen mode

In this example:

  • A directory test-results is created to store test reports.
  • The actions/upload-artifact action uploads the directory as an artifact named test-results.

Downloading Artifacts

Artifacts uploaded during a workflow can be downloaded using the actions/download-artifact action. This is particularly useful when reusing files across jobs.

Example: Downloading Artifacts

jobs:
  retrieve:
    runs-on: ubuntu-latest
    steps:
      - name: Download Artifacts
        uses: actions/download-artifact@v3
        with:
          name: test-results
Enter fullscreen mode Exit fullscreen mode

Best Practices for Job Outputs and Artifacts

Use outputs for lightweight data sharing (e.g., variables or strings). Use artifacts for sharing large or complex files (e.g., logs, binaries). Always name your artifacts and outputs descriptively for easy identification. Clean up unnecessary artifacts to save storage and improve workflow performance.

3.3 Environment Variables

Environment variables are a powerful way to pass configuration and data to workflows. They can be used for credentials, application settings, or runtime adjustments.

Setting Environment Variables

Environment variables can be defined globally for a workflow, at the job level, or for individual steps.

Example: Defining Global Environment Variables

env:
  NODE_ENV: production
  APP_VERSION: 1.2.3
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Print Environment Variables
        run: |
          echo "Environment: $NODE_ENV"
          echo "Version: $APP_VERSION"
Enter fullscreen mode Exit fullscreen mode

Defining Environment Variables for a Job

You can set environment variables at the job level, affecting all steps within the job.

Example: Job-Level Environment Variables

jobs:
  test:
    runs-on: ubuntu-latest
    env:
      TEST_MODE: true
    steps:
      - name: Print Test Mode
        run: echo "Test mode is $TEST_MODE"
Enter fullscreen mode Exit fullscreen mode

Defining Environment Variables for Individual Steps

Environment variables can also be defined for specific steps using the env key inside the step.

Example: Step-Level Environment Variables

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Set Deployment Key
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
        run: echo "Using deployment key $DEPLOY_KEY"
Enter fullscreen mode Exit fullscreen mode

Using Default Environment Variables

GitHub provides a set of built-in environment variables that you can use directly in workflows. Some commonly used variables include:

  • GITHUB_REPOSITORY: The repository name (e.g., user/repo)
  • GITHUB_SHA: The commit SHA that triggered the workflow
  • GITHUB_REF: The branch or tag ref that triggered the workflow
  • GITHUB_RUN_ID: The unique ID of the workflow run

Example: Using Default Variables

jobs:
  info:
    runs-on: ubuntu-latest
    steps:
      - name: Print Default Variables
        run: |
          echo "Repository: $GITHUB_REPOSITORY"
          echo "Commit: $GITHUB_SHA"
Enter fullscreen mode Exit fullscreen mode

Combining Environment Variables with Secrets

GitHub secrets allow you to store sensitive data securely and inject it into workflows as environment variables.

Example: Using Secrets as Environment Variables

jobs:
  secure:
    runs-on: ubuntu-latest
    steps:
      - name: Access Secret Key
        env:
          API_KEY: ${{ secrets.API_KEY }}
        run: echo "Using API key: $API_KEY"
Enter fullscreen mode Exit fullscreen mode

Best Practices for Environment Variables

Store sensitive data in secrets instead of plain environment variables to ensure security. Use descriptive names for variables to make workflows self-explanatory. Avoid hardcoding values; prefer inputs, outputs, or secrets for flexibility. Leverage default environment variables to dynamically adapt workflows to different contexts.

By mastering job outputs, artifacts, and environment variables, you can build modular, secure, and flexible workflows in GitHub Actions.

Full YAML File for CI/CD Pipeline

Below is the complete YAML file for a CI/CD pipeline in GitHub Actions:

name: CI/CD Pipeline
run-name: Execute CI/CD Pipeline

on:
  push:
    branches:
      - main
    tags:
      - v*
    paths:
      - src/**
  pull_request:
    branches:
      - main
    types:
      - opened
      - synchronize
      - closed
    paths:
      - src/**
  schedule:
    - cron: "0 0 * * *"
  workflow_dispatch:
    inputs:
      environment:
        description: "Deployment environment"
        required: true
        type: choice
        options:
          - development
          - staging
          - production
      version:
        description: "Version number to deploy"
        required: false
        type: string

env:
  GLOBAL_VAR: GlobalEnvironmentVariableValue

jobs:
  build:
    name: Build Project
    runs-on: ubuntu-latest
    env:
      BUILD_ENV: BuildJobEnvironmentValue
    outputs:
      build-output: ${{ steps.save-build-output.outputs.build_status }}
    steps:
      - id: checkout-code
        name: Checkout Code
        uses: actions/checkout@v3

      - id: install-dependencies
        name: Install Dependencies
        run: |
          echo "Installing dependencies..."
          echo "GLOBAL_VAR=$GLOBAL_VAR"
          echo "BUILD_ENV=$BUILD_ENV"
          npm install

      - id: build-project
        name: Build Project
        run: |
          echo "Building Vite React project..."
          echo "NODE_ENV=$NODE_ENV"
          npm run build
        env:
          NODE_ENV: production

      - id: save-build-output
        name: Save Build Output
        run: |
          echo "build_status=success" >> $GITHUB_OUTPUT
          echo "Custom Input Provided: $CUSTOM_ENV"
        env:
          CUSTOM_ENV: custom-build-value

      - id: upload-artifacts
        name: Upload Build Artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build-artifacts
          path: ./dist

  test:
    name: Run Tests
    needs: build
    runs-on: ubuntu-latest
    env:
      TEST_ENV: TestJobEnvironmentValue
    outputs:
      test-output: ${{ steps.save-test-output.outputs.test_status }}
    steps:
      - id: checkout-code
        name: Checkout Code
        uses: actions/checkout@v3

      - id: run-tests
        name: Run Tests
        run: |
          echo "Running tests..."
          echo "TEST_ENV=$TEST_ENV"
          echo "GLOBAL_VAR=$GLOBAL_VAR"
          echo "Build Output: ${{ needs.build.outputs.build-output }}"
          echo "Test Param Provided: $CUSTOM_PARAM"
        env:
          CUSTOM_PARAM: test-param-value

      - id: save-test-output
        name: Save Test Output
        run: echo "test_status=passed" >> $GITHUB_OUTPUT

      - id: download-artifacts
        name: Download Build Artifacts
        uses: actions/download-artifact@v3
        with:
          name: build-artifacts

      - id: output-artifacts-content
        name: Output Artifacts Content
        run: ls

  deploy:
    name: Deploy Application
    needs: [build, test]
    runs-on: ubuntu-latest
    outputs:
      deployment-status: ${{ steps.save-deployment-output.outputs.deployment_status }}
    steps:
      - id: deploy-to-env
        name: Deploy to Environment
        run: |
          echo "Deploying Vite React application..."
          echo "DEPLOY_ENV=$DEPLOY_ENV"
          echo "GLOBAL_VAR=$GLOBAL_VAR"
          echo "Test Output: ${{ needs.test.outputs.test-output }}"
        env:
          DEPLOY_ENV: DeploymentJobEnvironmentValue

      - id: download-artifacts
        name: Download Build Artifacts
        uses: actions/download-artifact@v3
        with:
          name: build-artifacts

      - id: output-artifacts-content
        name: Output Artifacts Content
        run: ls

      - id: save-deployment-output
        name: Save Deployment Output
        run: echo "deployment_status=success" >> $GITHUB_OUTPUT
Enter fullscreen mode Exit fullscreen mode

Explanation of Each Section

1. Workflow Metadata

  • name: This is the name of the workflow visible in the GitHub Actions tab.
  • run-name: Defines the dynamic run name for the workflow execution. Helpful for identifying runs in the Actions log.

2. Triggers (on)

Defines the events that trigger the workflow. Each trigger type is configured as follows:

  • push: Triggers the workflow when code is pushed to specific branches, tags, or file paths:

    • branches: List of branches (e.g., main) where the workflow should run.
    • tags: Regex-style tag matching (e.g., v*).
    • paths: Filters for files or directories (e.g., src/**).
  • pull_request: Triggers workflows for PR events:

    • branches: Targets PRs into specific branches (e.g., main).
    • types: Specifies PR events (e.g., opened, synchronize, closed).
  • schedule: Runs workflows on a cron schedule. In this example, it runs daily at midnight (0 0 * * *).

  • workflow_dispatch: Enables manual triggering with input parameters:

    • environment: Dropdown choice of environments (development, staging, production).
    • version: Optional text input for version deployment.

3. Global Environment Variables (env)

Sets variables accessible across all jobs in the workflow:

  • GLOBAL_VAR: A shared value used by all jobs.

4. Jobs

Each job defines tasks executed on a specific runner. The pipeline includes three jobs: build, test, and deploy.

4.1 Build Job
  • runs-on: Specifies the runner (ubuntu-latest).
  • env: Job-specific environment variables (e.g., BUILD_ENV).
  • outputs: Declares outputs (e.g., build-output), derived from specific steps.
  • Steps:
    • Checkout code: Uses actions/checkout@v3 to fetch repository code.
    • Install dependencies: Installs project dependencies using npm install.
    • Build project: Builds a Vite React project using npm run build.
    • Save output: Sets an output using $GITHUB_OUTPUT.
    • Upload artifacts: Saves build outputs (./dist) for use in other jobs.
4.2 Test Job
  • needs: Indicates dependency on the build job.
  • Steps:
    • Repeats checkout to fetch code.
    • Runs project tests and outputs results.
    • Downloads and verifies build artifacts.
4.3 Deploy Job
  • needs: Requires successful execution of both build and test jobs.
  • Steps:
    • Deploys the built project with environment-specific configurations.
    • Downloads build artifacts for deployment validation.

Advanced Features Demonstrated

  • Job Dependencies: Ensures test waits for build, and deploy depends on both.
  • Custom Inputs: workflow_dispatch parameters allow user-defined workflows.
  • Reusable Artifacts: Artifacts ensure build files are shared securely across jobs.
  • Dynamic Outputs: Job and step outputs enable modular, flexible workflows.

This YAML file represents a robust and production-ready CI/CD pipeline for building, testing, and deploying a Vite React application. Each feature is designed to support scalable and maintainable automation.

Top comments (3)

Collapse
 
urbanisierung profile image
Adam

Detailed introduction! You could also add workflow templates as a further section, to demonstrate how to re-use flows in different jobs - also across repositories.

Collapse
 
elpidaguy profile image
Kaustubh Joshi

Well put, Thank you!

Collapse
 
hasan_py profile image
Hasan

Great article! Thank you!