Working with GitHub Actions Steps: Options and Code Examples

What Is GitHub Actions? 

GitHub Actions is a CI/CD platform that allows developers to automate tasks within their software development lifecycle. It integrates closely with GitHub repositories to build, test, and deploy code from within the repository itself. By leveraging YAML-based configuration files, developers can define custom workflows comprising multiple jobs and steps.

The service supports various programming languages and tools, providing customization options through its marketplace for pre-built actions. These pre-built actions can be easily reused, helping developers save time on coding repetitive tasks. With pay-as-you-go pricing, GitHub Actions offers a scalable solution for projects of any size.

Understanding GitHub Actions Jobs vs. Steps 

In GitHub Actions, workflows are structured into jobs and steps, each serving distinct roles in the automation process.

Jobs are a collection of steps that execute on the same runner, providing a higher level of organization. Each job runs in a fresh instance of the runner environment, which means the job has its own isolated set of resources. Jobs can be configured to run sequentially or in parallel, and dependencies between jobs can be defined using the needs keyword.

Example:

name: Example Workflow

on: push

jobs:
  build:
runs-on: ubuntu-latest  
steps:  
  - name: Checkout code  
    uses: actions/checkout@v2  
  - name: Build the project  
    run: make build
 test:
runs-on: ubuntu-latest  
needs: build  
steps:  
  - name: Checkout code  
    uses: actions/checkout@v2  
  - name: Run tests  
    run: make test

Here is how a job definition appears in the GitHub Actions UI:

In this example, the test job depends on the build job and will only run after the build job completes successfully.

Steps are the individual tasks within a job, such as running a command or using an action. Steps run sequentially within the context of their job and share the same runner environment, which allows them to pass data between each other using the filesystem or environment variables.

Example:

jobs:
  example-job:
    runs-on: ubuntu-latest  
    steps:  
      - name: Checkout code  
        uses: actions/checkout@v2  
      - name: Set up Node.js  
        uses: actions/setup-node@v2  
        with: 
          node-version: '14'  
      - name: Install dependencies  
        run: echo "Installing dependencies"
      - name: Run tests  
        run: echo "Running tests"

Here, the example-job consists of four steps that checkout the code, set up Node.js, install dependencies, and run tests sequentially.

Common GitHub Actions Step Options with Examples

Below we discuss the following step options:

  • jobs.<job_id>.steps[*].if: Allows conditional execution of a step based on the evaluation of an expression, enabling or skipping steps.
  • jobs.<job_id>.steps[*].uses: Specifies an action to run as part of a step. Actions are reusable units of code that can be pulled from repositories or Docker images.
  • jobs.<job_id>.steps[*].run: Used to execute command-line scripts within the runner’s environment as separate steps, often for installing dependencies, running tests, or performing builds.
  • jobs.<job_id>.steps[*].shell: Specifies the shell environment that should be used to execute the run commands. 
  • jobs.<job_id>.steps[*].with: Allows you to pass input parameters to an action, set as environment variables within the action. This is useful for specifying a version number or providing authentication credentials.
  • jobs.<job_id>.steps[*].env: Sets environment variables for a specific step, overriding variables set at the job or workflow level. This is often used to pass sensitive data.

jobs.<job_id>.steps[*].if

The if conditional allows steps to run only if specific conditions are met. Expressions can utilize supported contexts and must evaluate to true for the step to execute. This feature is useful for controlling the flow of the workflow based on dynamic conditions.

Example: Run in specific context

steps:
  - name: My first step
   if: ${{ github.event.pull_request.assignee == null }} 
   run: echo This is a pull request with an assignee removed.

In this example, the step runs only if the event is a pull request and the action is ‘unassigned’. It demonstrates how conditions can be used to control step execution based on the event context.

Example: Using status check functions

    steps:
      - name: My first step
        uses: octo-org/action-name@main  
      - name: My backup step
        if: ${{ failure() }}
        uses: actions/heroku@1.0.0

Here, the  my backup step runs only if the previous step fails, illustrating the use of status check functions to handle errors gracefully within the workflow.

jobs.<job_id>.steps[*].uses

The uses keyword selects an action to run as part of a step in your job. Actions are reusable units of code that can be defined in the same repository, a public repository, or a Docker container image. Specifying the version of the action helps maintain stability and security.

Example: Using versioned actions

steps:
  - uses: actions/checkout@v4.2.0

This example specifies a particular version (v4.2.0) of the checkout action, ensuring that the workflow uses a stable release of the action.

Example: Using a public action

jobs:
  my_first_job:
    steps:  
      - name: My first step  
        uses: actions/heroku@main

In this script, a public action from the heroku repository is used. The uses keyword references the action, and the main branch is specified.

jobs.<job_id>.steps[*].run

The run keyword executes command-line programs using the operating system’s shell. Each run command represents a new process and shell in the runner environment. You can run single-line or multi-line commands.

Example: Running a single-line command

- name: Install dependencies
  run: npm install

This step installs dependencies using npm install, a common command in JavaScript projects.

Example: Running a multi-line command

- name: Clean install dependencies and build
    run: |
      npm ci  
      npm run build

In this example, the run keyword executes multiple commands to clean install dependencies and build the project, showing how to manage complex tasks in a single step.

jobs.<job_id>.steps[*].shell

The shell keyword overrides the default shell settings in the runner’s operating system. You can use built-in shell keywords or define custom shell options.

Example: Running a command using Bash

steps:
  - name: Display the path
    shell: bash  
    run: echo $PATH

This step runs a command using Bash to display the system’s PATH environment variable.

Example: Running an inline Python script

steps:
  - name: Display the path
    shell: python  
    run: |  
      import os  
      print(os.environ['PATH'])

Here, an inline Python script is executed to print the PATH environment variable, demonstrating how to run scripts in different programming languages.

jobs.<job_id>.steps[*].with

The with keyword is used to provide input parameters to an action. Each input parameter is set as an environment variable, prefixed with INPUT_ and converted to uppercase.

Example: Defining input parameters

jobs:
  my_first_job:
steps:  
  - name: My first step  
    uses: actions/hello_world@main  
    with:  
      first_name: John
      middle_name: W.
      last_name: Smith
  - name: Print names to console
    run: |
      echo "First Name: John"
      echo "Middle Name: W."
      echo "Last Name: Smith"

In this example, the hello_world action is provided with three input parameters: first_name, middle_name, and last_name. These parameters are accessible within the action as environment variables.

jobs.<job_id>.steps[*].env

The env keyword sets environment variables for steps to use in the runner environment. These variables can override job and workflow environment variables with the same name.

Example: Setting environment variables

steps:
- name: My first action
  env:
    GITHUB_TOKEN: ${{ secrets.API_TOKEN }}
    FIRST_NAME: John
    LAST_NAME: Smith
  run: echo "consuming secrets"

This step sets environment variables API_TOKEN, FIRST_NAME, and LAST_NAME, which can be used within the step. The API_TOKEN is sourced from the secrets context, ensuring secure handling of sensitive data.

Related content: Read our guide to GitHub actions tutorial

Combine GitHub Actions with Codefresh to Support GitOps and Kubernetes Deployments

GitHub actions is a very powerful platform but it is focused mostly on CI and does not support GitOps and native Kubernetes deployments. Codefresh is created specifically for GitOps and Cloud native applications and includes native support for using GitHub Actions for the CI part of the Software lifecycle.

This means that you can get the best of both worlds by keeping all your CI workflows in GitHub Actions, while using Codefresh for advanced features such as:

  • Application dashboards
  • Git source managements
  • Configuration drift management
  • Kubernetes environment dashboards
  • Topology views

In case you are new to Codefresh – we have made it our mission since 2014 to help teams accelerate their pace of innovation. Codefresh recently released a completely rebuilt GitOps CI/CD toolset. Powered by Argo, Codefresh now combines the best of open source with an enterprise-grade runtime allowing you to fully tap the power of Argo Workflows, Events, CD, and Rollouts. It provides teams with a unified GitOps experience to build, test, deploy, and scale their applications.

Ready to Get Started?

Deploy more and fail less with Codefresh and Argo