Cartoon person steering a space ship

Building a workflow with GitHub Actions

Bekah Whittle
Bekah Whittle // Director, Field Services // GitHub

In this guide, we will create your first GitHub Actions workflow, explaining the basic components and methods while we do. We’ll also set up the demo repository, which will serve you for the rest of this Essentials of GitHub Actions module. Along the way, we’ll hear insights and tips from Amplifon on how they used GitHub Actions for CI/CD and workflow automation to improve their development processes. 

Let’s get started!


In this guide, you will learn:

  • How to create a GitHub Actions workflow file

  • How to trigger a workflow according to specific GitHub events

  • How to execute a reusable action in your workflow

  • How to execute a workflow using a GitHub-hosted runner


Follow along with our demo repository

If you want to follow along with the Automations learning pathway, you’ll need to create a copy of our demo repository, which contains an example web application that we’ll deploy to GitHub Pages using GitHub Actions. The demo repository also contains copies of all the files created during this module in the demo-files directory. 

  1. Open the actions-learning-pathway repository in a new tab.

  2. Click Use this template above the file list and select Create a new repository.

  3. Use the Owner dropdown menu to select the account you want to own the repository. 

  4. Name your repository actions-learning-pathway and add a simple description to make it easier to identify later.

  5. Set the default visibility for the repo to public, as private repositories use Actions minutes, while public repositories can use GitHub-hosted runners for free.

  6. Click Create repository from template and we’re ready to build our first Actions workflow!

Create your first Actions workflow file

An Actions workflow is a configurable automated process made up of one or more jobs defined using YAML. Workflows follow a specific structure and are stored in the .github/workflows directory of your repository. There are two ways to create a new workflow—either by creating a new YAML file in the .github/workflows directory, or from the Actions tab of your repository. The first way is simple and straightforward, but the second has some advantages, so let’s do that for now:

Navigate to the “Actions” tab of your repository and click New workflow.

Take a look around and you’ll see that GitHub has suggested a variety of starter workflows based on the languages of the code in your repository. Since the actions-learning-pathway repository has JavaScript and Node.js files, suggestions are primarily for those languages. 

The Choose a workflow page shows suggestions for actions according to the languages used in the repository and allows a user to search for workflows.

The page also lists workflows organized by categories, such as Deployment, Security, Continuous Integration, Automation, and Pages, and provides a search bar to help narrow recommendations. This page is a good place to look before building something from scratch. 

But since we’re interested in learning about the basics of the workflow file, we’ll start from scratch: 

Click Set up a workflow for yourself to begin editing a blank YAML file in the .github/workflows directory of your repository. Set the file name to hello-world.yml

Now you’re ready to start building your first GitHub Actions workflow!

Actions has been transformational in helping us build our CI/CD pipeline. We had been wrestling with unpredictable deployments, duplicate code creation across working groups, and inefficient manual processes. Adopting workflow automation led to remarkable enhancements: expedited deployments, improved release management, and enhanced code reuse and knowledge sharing.

Now, we’re able to code, build, and distribute directly from the browser. Actions is a powerful yet flexible tool that expands our ability to innovate and scale.

Bernat Nosas Comerma
Bernat Nosas Comerma // Lead of Global DevOps // Amplifon

Now that we have a file to edit and a place to do it, let’s build a workflow to automatically say “Hello World!” whenever someone pushes to our repository. We’ll write the file and explain the steps along the way. You can either write the code line by line, or copy the example at the end and follow along with the other steps.

Name your workflow

name: hello-world

Giving your workflow a descriptive name not only improves its readability, it also helps others to more easily understand your workflow’s purpose without investigating each component. In this case, we’re writing your typical “Hello world”, so we’ll name it hello-world.

Set the workflow’s trigger event

Every workflow file needs to have one or more events that trigger it to run. Events cover most any GitHub activity, from opening a pull request to commenting on an issue to the successful completion of another workflow. You can even trigger workflows from outside of GitHub via an API call and the repository-dispatch event. These events provide a simple means for a tight integration between your CI/CD pipelines and the platform powering your development, while still providing the flexibility to interact with your other tools.

For our hello-world example, we’ll keep it simple and set the workflow event to push.

name: hello-world
on: push

The push event runs your workflow when you push a commit or tag, or when you create a repository from a template. So when we finish writing hello-world.yml and commit it, the workflow will run. But first, we have to tell it what to do.

Define a job

Now we need to define what happens when the workflow runs by creating a job. Every workflow has one or more jobs, which consist of a series of interrelated, executable steps. 

We’ll start the jobs: section of our workflow and name our job hello-world-job.

name: hello-world
on: push
jobs: 
  hello-world-job:

By default, jobs function independently and run in parallel, but you can also configure a job to wait for another job(s) to complete before it begins. For instance, you could have separate build jobs for different architectures running concurrently, with a subsequent packaging job that only initiates once all build jobs have successfully concluded.

Specify a runner

A runner is a machine that executes the jobs in a workflow. There are two basic types of runners available: GitHub-hosted runners and self-hosted runners. 

A self-hosted runner is a system that you deploy and manage on your own infrastructure to execute GitHub Actions jobs.

A GitHub-hosted runner provides a freshly-provisioned virtual machine running Ubuntu Linux, Microsoft Windows, or macOS. When selecting a GitHub-hosted runner, you can identify the specific OS version, or specify -latest to use the most recent, stable image available. GitHub also offers larger runner versions that have additional features, such as runner groups for access control across an organization and more. 

We recommend using GitHub-hosted runners for the majority of situations (they eliminate overhead and make life easier), but we’ll  see how self-hosted runners can be useful too in a later guide. 

For now, we’ll use Ubuntu-latest on a GitHub-hosted runner:

name: hello-world
on: push
jobs: 
  hello-world-job:
    runs-on: ubuntu-latest

If a self-hosted runner goes 14 days without executing a job, it stops (an ephemeral self-hosted runner is automatically removed from GitHub if it has not connected to Actions for more than one day). To counteract this, we've set events in most of our repos to initiate a job every 12 days or so. This routine maintenance task keeps our self-hosted runners up and running, ensuring we can rely on them when needed.

Bernat Nosas Comerma
Bernat Nosas Comerma // Lead of Global DevOps // Amplifon

Add a step to run your first action

As we mentioned before, each job is made up of one or more steps. Each step can either run a script that you define or run an action, which is a reusable extension that can simplify your workflow. You can write your own custom actions, or you can use actions from GitHub Marketplace, which has thousands of actions for everything from code review to dependency management to security and more. 

Third-party actions not only decrease code repetition, they also let you take advantage of the wider GitHub developer ecosystem. Why write custom integrations when they already exist? 

Similar to how GitHub recommends starter workflows, you’ll also notice that GitHub recommends potentially useful actions directly from the editor on GitHub.com, making it simple to employ actions on the fly.

For our hello-world workflow, we’ll start with the Checkout action, which allows your workflow to access the contents of your repository, so that you can run scripts or other actions against it—such as running build and test tooling. You’ll want to use the checkout action any time your workflow will need to access the repository's contents.

name: hello-world
on: push
jobs: 
  hello-world-job:
    runs-on: ubuntu-latest
    steps: 
      - name: Check out repository code
        uses: actions/checkout@v3

First, we define a name for our step. This is optional, but it’s a good practice, because it will be recorded in the logs and can help you track down errors as your workflows become more complicated. 

Next, uses: actions/checkout@v3 specifies that this step will use @v3 of the checkout action. Similar to any dependency, you should indicate the version of the action you'd like to use based on your comfort with automatically accepting updates to the action. You can find the latest version of an action by finding the action in GitHub Marketplace. If you don’t specify a version, the latest version will automatically be used, and could break your workflow when breaking changes are made to the action.

Say “Hello World”!

Finally, we’ll add a step to run a bash script that echoes our Hello World! phrase, which we’ve stored in a file in our actions-learning-pathway repository. The checkout action is doing its job here, giving our bash script access to the hello-world.txt file to echo its contents.

name: hello-world
on: push
jobs: 
  hello-world-job:
    runs-on: ubuntu-latest
    steps: 
      - name: Check out repository code
        uses: actions/checkout@v3
      - run: echo "$(cat hello_world.txt)"

With the final step added, we’ll save the workflow and see Actions, well, in action!

  1. Click Commit changes…

  2. Enter a commit message and click Commit changes in the dialog box. 

This returns you to the .github/workflows directory of your repository, where you can see your newly created hello-world.yml workflow file. Since you just pushed the file to your repository, and your workflow is set to run on: push, let’s go see if it worked!

  1. Select the “Actions” tab.

  2. Click Say hello world! (the name we gave the final step) to see if we were successful.

The individual steps of the hello-world-job are listed out, with the "Say hello world!" step expanded to show the results.

As long as we did everything correctly, we should see all of the jobs listed out on the screen, with the time it took to run each one listed alongside it. If you run into an error, look closely to see that your code matches the code shared above and remember that whitespace matters, and incorrectly-spaced YAML will produce errors.

With the Say hello world! step expanded, we can see that our workflow file ran echo $(cat hello_world.txt), which echoed “Hello! And congrats, you’ve successfully built and run your first GitHub Actions workflow file!” to the logs. 

Success!

Up next: Building an application with GitHub Actions

Now that we’ve learned about the basic building blocks of GitHub Actions, let’s use them to do something a bit more interesting and useful than writing a “Hello world!” message to the logs—let’s write a simple CI/CD workflow to build, test, and deploy a web application to GitHub Pages using GitHub Actions. We’ll start off by building an application with GitHub Actions.