Simple GitHub Actions Techniques

Denis Palnitsky
5 min readJul 2, 2024

I have been using GitHub Actions almost since its release in 2018, and I’m a big fan of the product and its architecture. Here, I want to share some “advanced” tips I have learned over the years.

You can find most of the materials and examples in github-actions-advanced-techniques repo.

Use cache to speed up your workflow

If you use GitHub Actions, you are probably already familiar with caching. If not, add the following to your workflow, adjusting the path as necessary:

  - name: Cache node modules
uses: actions/cache@v4
with:
# find where dependencies are stored to cache them
path: ~/.npm
# use hash of dependency file(package.json, go.mod, pom.xml) for a key
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
# restore-keys will be used when above key not found in cache
restore-keys: |
${{ runner.os }}-node-

Refer to a full example here.

You can grab a specific example for your language from here.

Behind the scenes, this action packs the content into an archive and sends it to Azure storage. You can browse cache objects in Actions -> Caches like here.

Create reusable workflows to avoid yaml duplication and improve maintainability

Reusable workflows allow you to centralize common logic and make it easily reusable across different workflows, reducing duplication and enhancing maintainability.

Here is minimal example of a workflow:

name: Reusable Workflow
on: workflow_call

jobs:
reusable-job:
runs-on: ubuntu-latest
steps:
- name: Print message
run: |
echo "Here is the message"

Save this reusable workflow in your repository at .github/workflows/reusable-workflow.yaml. It has to be in a .github/workflows directory to be recognized by GitHub Actions.

You can call this workflow from another workflow file like this:

name: Call Reusable Workflow
on: [push]
jobs:
executing-reusable-workflow:
# URL structure: [owner]/[repo]/.github/workflows/[workflow].yaml@[branch/tag/commit]
uses: your-org/repo-of-a-workflow/.github/workflows/reusable-workflow.yaml@main

You can see a more advanced example with input and output parameters in this reusable workflow and a workflow calling it.

Refer to:

Create reusable actions to improve structure and maintainability

Reusable actions are similar to reusable workflows, although they tend to have a more specific purpose that can fit in one workflow step. They can be executed as a step.

The custom actions syntax is similar to the regular workflow syntax but includes extra metadata and input/output parameters. Here is a basic example:

name: Short sha
description: Provide short git SHA

outputs:
sha-short:
description: Short git SHA
value: ${{ steps.vars_step.outputs.sha-short }}

runs:
using: "composite"
steps:
- name: Init vars
shell: bash
id: vars_step

# here we set the output variable, not printing it as it may seem
run: |
echo "::set-output name=sha-short::$(git rev-parse --short=7 HEAD)"

Drop that into an action.yamlfile in a repository like here. If it’s a private repository, make it accessible to other repositories of your organization via Repository settings -> Actions -> Access -> Accessible from repositories in the Organization.

Use it in your workflow like this:

  - name: Use custom action
id: custom-action
# this is our custom action's repository name and branch that we want to use
uses: denispalnitsky/custom-github-action@main

You can find a full workflow example here.

Things to keep in mind:

  • You can have only one action per repository, so it would make sense to create a repository specifically for actions.
  • There are multiple ways to create an action. We created a composite action that uses Github Actions yaml syntax. In more complex scenarios you can use a Docker container or JavaScript. Refer to the official documentation for more details.

Use self-hosted runners to improve performance and save money

If your GitHub Actions bill is growing, then getting self-hosted runners could reduce costs dramatically. GitHub charges for the minutes workflow runs on their runners, but when you use your own runners, GitHub does not charge you for that. You still have to pay for the runners compute in the cloud or on your own hardware, though.

Two other benefits are:

  • You get to choose the machine type that your workflow uses.
  • You run the runner on your infrastructure, which can have access to resources that may not be available outside of your network, thus enhancing security.

To run a self-hosted runner, install the runner software and run it on a machine that can access GitHub servers. Go to your organization settings Actions -> Runners -> Add runner and follow the instructions.

You can do this on your local machine to test or in the cloud. Self-hosted runners are pretty flexible, and you can scale them from one runner to thousands. If you need help with scaling, feel free to reach out.

Use third-party runners to improve performance and save money without hassle

If you don’t want to manage your own runners, there are plenty of managed runners available on the market. Refer to a review I did a bit earlier: How to Reduce Your GitHub Actions Bill and do your own research as the field is evolving rapidly.

Publish Docker(OCI) images to GitHub container registry to share them with the world

If you build private images for a specific cloud, you probably push them to the cloud registry. But if you build images for public use or don’t mind pulling them from GitHub, hosting them on GitHub Container Registry could be a good option.

You will need to login to GitHub Container Registry and push the images. Don’t forget to set GitHub token permissions:

permissions:
contents: read
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Log in to the Container registry
uses: docker/login-action@v3.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push Docker image
id: push
uses: docker/build-push-action@v5.4.0
with:
context: .
push: true
tags: ghcr.io/denispalnitsky/github-actions-tips:latest

Further reading on how to add tags and attestations is here.

Run your workflows locally using Act

Act is a tool maintained independently from GitHub that allows running workflows locally

⚠️ Although it’s not perfect and could be hard to configure for complex workflows, it can still be useful for debugging.

- Install act: https://nektosact.com/installation/index.html

- Try it out with act push -v. It could take some time to run the first time as it will pull the build image (3GB+).

Last words

The list can obviously go on, but I wanted to keep it short and straight to the point. If you need more tips or have a specific question, feel free to reach out or leave a comment.

Edit: there was a statement in Custom Github Section section: “You can have only one action per repository, so it would make sense to create a repository specifically for actions.” which not accurate. You can have multiple actions in repository although it’s not recommended for public actions. Thanks to nooby148 for pointing that out.

--

--