In this blog we’re going to take a look at Github Actions. We’ll explore the terminology and take a bit of closer look at workflows and actions. We’ll finish with how you can get your first workflow up and running in GitHub.
If you want to skip the boring parts and get straight to the good parts. Head to the bottom of this blog post where you can get started writing your first workflow.
Before we start let’s start by defining at what Github Actions actually is. According to the Github documentation, Github Actions is described as:
GitHub Actions help you automate tasks within your software development life cycle. GitHub Actions are event-driven, meaning that you can run a series of commands after a specified event has occurred. For example, every time someone creates a pull request for a repository, you can automatically run a command that executes a software testing script.
Building blocks of GitHub Actions
Workflows
A workflow is the core definition that you will write to execute actions. It is made up of one or more jobs and contains the definition of events that trigger the workflow to execute.
Events
An event is an activity that triggers a workflow. For example when code is pushed to a repository or a pull request is created. Please see the Github Actions documentation for a full list of events that trigger workflows.
Jobs
A job is a set of steps that execute on the same runner. By default, workflows with multiple jobs will run in parallel.
Steps
A step is an individual task within a job. It can be either an action or a command running in the default shell of the specified runner.
Actions
Actions are standalone commands that are combined to make up the steps of a job. You can create your own custom actions or use ones created by the Github community.
Runners
A runner is a server that has the GitHub Actions runner application installed. You can use a runner hosted by GitHub, or you can host your own. A runner listens for available jobs, runs one job at a time, and reports the progress, logs, and results back to GitHub.
The three action types
Let’s take a bit of a deeper look at actions. There are a few different types of actions we can define and they are:
- Docker container (Linux)
- JavaScript (Linux, macOS, Windows)
- Composite run steps (Linux, macOS, Windows)
Docker container actions
A Docker container actions allows you to specify the environment, tools, dependencies and operating system of the container you want to execute in. Due to the nature of Docker they are slower as the container will need to be pulled on each run.
Docker container actions can only run on linux runners.
Javascript actions
Javascript actions run directly on the runner itself. They must be written in pure Javascript and not contain any dependent binaries. It should be noted that Javascript actions can be written in Typescript and transpiled to Javascript.
Javascript actions can be run on runners with any OS.
Composite run steps actions
Composite run steps allow you to combine multiple workflow steps into a single action. At this point in time they only support running steps in the default shell of the runner. Note: Composite run steps do not currently support the uses
keyword and therefore cannot be used to combine multiple actions.
Composite run steps actions can be run on runners with any OS.
Anatomy of an action
Now let’s take a bit of a closer look at an action. Each of the three action types share the same core name, description, inputs and outputs definition. Where they differ is in the definition of their run sections. First we’ll look at the syntax common to all action types, then we’ll look at the syntax specific to each of the three action types.
Common syntax
name: 'Goodbye Bob'
description: 'Says goodbye to someone and outputs their name'
inputs:
who-to-farewell:
description: 'Who to farewell'
required: true
default: 'Bob'
outputs:
who-was-farewelled:
description: 'Who was farewelled'
name
gives the action a name that will be displayed in the Github Actions UI.
description
a short description of the action.
inputs
is the start of the input section, which contains the definitions of the input variables that will be passed to the action. Each input variable is defined with it’s key giving it it’s name.
who-to-farewell
is an input variable defined in this action. It will be available to the action with the name who-to-farewell.
description
a short description of the input variable.
required
a boolean flag to determine if the variable is required or not.
default
a default value to pass if no value is passed from the workflow.
outputs
is the start of the output section, which contains the definitions of the output variables that will be returned by the action. Each output variable is defined with it’s key giving it it’s name.
who-was-farewelled
is an output variable defined in this action. It will be available to the workflow with the name who-was-farewelled.
description
a short description of the output variable.
Docker specific syntax
runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.who-to-farewell }}
runs
is the start of the section defining which action type to use and the configuration to use.
using 'docker'
sets the action type of the action to docker.
image
Specifies the name of the docker image to use for this action.
args
Specifies any arguments that should be passed when running the container.
Javascript specific syntax
runs:
using: 'node12'
main: 'index.js'
runs
is the start of the section defining which action type to use and the configuration to use.
using 'node12'
sets the type of action to JavaScript and the node version to 12. Note: at the time of writing the node versions is limited to 12.
main
sets the JavaScript file to use as the entry point of the action.
Composite specific syntax
runs:
using: 'composite'
steps:
- run: echo Goodbye ${{ inputs.who-to-farewell }}.
shell: bash
- run: echo Thanks for visiting!
shell: bash
runs
is the start of the section defining which action type to use and the configuration to use.
using 'composite'
sets the action type of the action to composite.
steps
is the start of the step list definition section that will contain all the steps of this composite action.
Anatomy of a workflow
Let’s take a look at the anatomy of a Github Actions workflow. The following code snippet is the base building block of a Github Actions workflow. It has everything needed to trigger and execute the workflow.
name: Cool name
on: [push]
env:
MY_COOL_ENV: my-cool-value
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- run: echo hello
Let’s take a closer look at each piece of this workflow.
name
gives the workflow a name that will be displayed in the GitHub Actions UI.
on
is a comma separated list of events that will trigger the workflow to execute.
env
is the set of environment variables that will be made available to all jobs and steps in the workflow.
jobs
is the start of the job definition section that will contain all the workflow jobs.
build
is the first job definition in the workflow and takes its name from its definition.
runs-on
defines the runner that this job will run on. In this case ubuntu-latest
steps
is the start of the step list definition section that will contain all the steps of this job.
uses
is the first action this job will use. In this case it tells the workflow to use the actions/checkout
Github Action and to checkout the main
branch of the current repository.
run
will run the specified command echo hello
on the default shell of the runner.
Writing your first workflow
To get started all you need to do is:
- Login to your GitHub account
- Create a new repository
- Give it a name and hit Create Repository
- Click the create a new file button
- Name it: .github/workflows/my-first-workflow.yaml
- Paste in the following code and hit Commit new file
name: My first workflow
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo hello from my first workflow!!!
Now navigate to the tab and you should see your first GitHub action workflow.