You can use GitHub Actions as your Continuous Integration (CI) / Continuous Deployment (CD) pipeline for your GitHub source code repositories.
This gives you the advantages of...
- Source code control for your CI/CD (e.g. Pull Requests)
- Collocating it with your application code that uses it
- Combining with other GitHub Features such as webhooks and secrets
You can also use GitHub Actions (GH Actions) with your free GitHub account to learn how to develop and add CI/CD for your personal applications.
In this post you will learn the basics of a GitHub Actions workflow which can run several different and parallel jobs.
π For all the things GitHub Actions, see the GitHub Actions Documentation
π In the repository's root directory...
.github/workflows/
GitHub will look for and automatically run workflow files located in the
repository's .github/workflows
directory. You can have multiple workflow
files and multiple workflows can run simultaneously.
π Unfortunately GitHub does not provide a native ability to test your GH Actions workflows. This is especially painful for merge-only workflows where the only option is to merge to see if the workflow runs correctly (or even runs).
There is a third-party library nektos/act which apparently allows for local running of GH Actions files, but I have not yet used it.
Some techniques that I have used to test GitHub Actions include...
-
Putting the GH Actions configuration under test in an on-Pull-Request workflow letting you to run it when pushing a new commit on a Pull Request (PR). However, this approach will not help if there is any data or state that is only present during merges or other events.
-
Using and calling scripts in your GH Actions workflows where these scripts can be run and tested outside of GitHub Actions.
-
Creating a separate (and disposable) GitHub repository as a testbed for your GH Actions workflows. This is especially useful when developing and testing merge workflows.
GH Actions workflow files must be written in YAML syntax and have either a
.yml
or .yaml
file extension.
π See GitHub Documentation on Workflow syntax for GitHub Actions for more specifics.
Here is an example of a GitHub Actions workflow file from https://github.com/brianjbayer/example-gh-actions...
name: Example Workflow on PR and Push
# What events trigger this workflow
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
# Define Any Workflow Global Variables
env:
GREETEE: "World"
GITHUB_REPOSITORY: ${{ github.repository }}:latest
# Define parallel (and file-dependent) jobs
# Each job can run on a different worker so
# state is not shared
jobs:
greeting:
runs-on: ubuntu-latest
steps:
- name: Hello world
run: echo "Hello ${GREETEE}!"
workflow-env-variables:
runs-on: ubuntu-latest
env:
GREETEE: Sarah
steps:
- name: Hello greeted one
run: echo "Hello ${GREETEE}!"
- name: Greeted local one
env:
GREETEE: Sailor
run: echo "Hello ${GREETEE}!"
some-info:
name: Display some git information
runs-on: ubuntu-latest
steps:
# Use an exiting action (git checkout for source)
- uses: actions/checkout@v1
- name: Git log
run: git log
- name: Show GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
dependent-workflow:
needs: [greeting, some-info]
runs-on: ubuntu-latest
env:
GREETEE: Bob
steps:
- uses: actions/checkout@v1
- name: Run a (workflow) script in source code
run: ./.github/scripts/greetings-world-script ${GREETEE}
- name: It has docker
run: docker --version
π For more information on
name
, see the GitHub Documentation on Workflow syntax for GitHub Actions
name
is used to specify the name of the GH Actions workflow which is
displayed on your repository's pages like the Actions page
(and PR if workflow is run).
name: Example Workflow on PR and Push
Here the workflow name is shown in the repository's Actions tab...
Here the workflow name is shown in the Pull Request page...
If you do not specify name
, GitHub will set it to the relative
workflow file path in the repository.
π For more information on
on
, see the GitHub Documentation on Workflow syntax for GitHub Actions
on
is used to specify the specific GitHub events that will trigger
the workflow to run. You can specify...
- Single events
- Multiple events
- Time schedule
And you can limit a workflow to only run for specific files, tags, or branches.
Here the workflow only runs on pushes and Pull Requests for the main
branch...
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
You can also use the alternate YAML array syntax...
on:
pull_request:
branches:
- main
π For more information on
jobs
, see the GitHub Documentation on Workflow syntax for GitHub Actions
jobs
is used to start the specification of the parallel
jobs that will be run during your workflow.
jobs:
greeting:
runs-on: ubuntu-latest
steps:
- name: Hello world
run: echo "Hello ${GREETEE}!"
Let's take a closer look at specifying a job.
By default, GitHub will set the name of the job to the YAML Map
for the job (i.e. greeting
).
Here the (default) workflow name is shown in the Pull Request page...
If you want to specify a job name other than the default,
you can use name
.
some-info:
name: Display some git information
runs-on: ubuntu-latest
π For more information on
runs-on
, see the GitHub Documentation on Workflow syntax for GitHub Actions
runs-on
is used to specify the type of machine on which the job will
be run. For example...
ubuntu-latest
to run on Linuxwindows-latest
to run on Windows Servermacos-latest
to run on macOS
π For more information on
needs
, see the GitHub Documentation on Workflow syntax for GitHub Actions
needs
is used to specify that the job is dependent on the completion
of another job. By default the dependency job must complete successfully
otherwise the dependent job will be skipped from running.
For example in the dependent-workflow
job shown here, the greeting
and
some-info
jobs will run first and must both complete successfully in
order for the dependent-workflow
job to run.
dependent-workflow:
needs: [greeting, some-info]
runs-on: ubuntu-latest
π For more information on
steps
, see the GitHub Documentation on Workflow syntax for GitHub Actions
steps
is used to specify the sequential tasks comprising the job
in the workflow.
steps:
- name: Hello world
run: echo "Hello ${GREETEE}!"
Generally a step has at least a name (- name
) and the command
to run run
.
Here the step name for the selected job is shown in the Pull Request Actions page...
Examples of commands that you can run include...
- (Job Runner) Machine-native commands:
run: echo "Hello World!"
- (Source Code) Scripts:
run: ./.github/scripts/greetings-world-script ${GREETEE}
- Docker Commands:
docker --version
You can also use other GitHub Actions in your job with uses
.
For example you can use the actions/checkout
action to
do a git checkout
in your job so that your job has
the repository's source code available.
Here we are using version 1 of actions/checkout
...
some-info:
name: Display some git information
runs-on: ubuntu-latest
steps:
# Use an exiting action (git checkout for source)
- uses: actions/checkout@v1
- name: Git log
run: git log
π₯ To see available actions including those from third-parties, see the GitHub Actions Marketplace
You can define reusable values to use in your workflow with GitHub Actions workflow environment variables.
There are some limitations to GitHub Actions workflow environment variables.
-
You can not use a workflow environment variable defined in another workflow file
-
You can not use a workflow environment variable defined previously in the workflow file to define a later workflow environment variable. For example, here the output would actually be
Hello ${GREETEE}!
...this-will-not-work: runs-on: ubuntu-latest env: GREETEE: Sarah steps: - name: Greeting Fail env: MESSAGE: "Hello ${GREETEE}!" run: echo "${MESSAGE}"
-
Although you can define and/or set a (workflow) environment variable in one job, that environment variable (or set value) will not be available in another job in the workflow. Because that actual instance of a workflow environment variable and its value only exist on that machine that is running that job.
You can define workflow environment variables that are available to...
- The entire workflow (and all jobs in it)
name: Example Workflow on PR and Push on: push: branches: [ main ] env: GREETEE: "World" jobs:
- The entire job (and all steps in it)
workflow-env-variables: runs-on: ubuntu-latest env: GREETEE: Sarah steps: - name: Hello greeted one run: echo "Hello ${GREETEE}!"
- A single step in a job
- name: Show GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT"
β¨ The more specific definition of a workflow environment variable is used.
If you define the same name (i.e. override) of a GH Actions workflow environment variable at multiple levels or places in your workflow file, the executing workflow will use the more specific definition, that is in the following order...
- Step-level definition
- Job-level definition
- Workflow (file) level
For example, this workflow when it runs...
name: Example Workflow on PR and Push
on:
pull_request:
branches: [ main ]
# Define Any Workflow Global Variables
env:
GREETEE: "World"
jobs:
greeting:
runs-on: ubuntu-latest
steps:
- name: Hello world
run: echo "Hello ${GREETEE}!"
workflow-env-variables:
runs-on: ubuntu-latest
env:
GREETEE: Sarah
steps:
- name: Hello greeted one
run: echo "Hello ${GREETEE}!"
- name: Greeted local one
env:
GREETEE: Sailor
run: echo "Hello ${GREETEE}!"
will have this output...
Job | Step | Output |
---|---|---|
greeting | Hello world | Hello World! |
workflow-env-variables | Hello greeted one | Hello Sarah! |
workflow-env-variables | Greeted local one | Hello Sailor! |
You can set a Workflow environment variable in one step and have it available for a later step in the same job. using...
echo "{environment_variable_name}={value}" >> $GITHUB_ENV
For example...
name: A workflow
on: push
jobs:
set-env:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set env
run: echo "LATEST_COMMIT=$(git log -1 --pretty=%H)" >> $GITHUB_ENV
- name: Test
run: echo $LATEST_COMMIT
π I learned this from Stackoverflow and the GitHub Actions Documentation