Skip to main content
< Back to Blog

Automate Your Code with GitHub Actions #4: Creating Custom GitHub Actions

GitHub Actions provides a wide range of built-in actions that you can use in your workflows. However, there may be cases where you need to create custom actions to perform specific tasks or integrate with external services.

Custom actions allow you to encapsulate reusable code and share it across different workflows or repositories.

Who is Bas Steins?

Bas is a software developer and technology trainer based in the triangle between the Netherlands, Germany, and Belgium. For almost two decades, he has been helping people turn their ideas into software and helping other developers write better code. This series is based on his mini-guide "Automate Your Code with GitHub Actions".

Custom Action Types

  • JavaScript Actions: These actions are written in JavaScript and can be used to perform a wide range of tasks, such as building, testing, and deploying code. JavaScript actions are executed in a Node.js environment.
  • Docker Actions: These actions are packaged as Docker containers and can be used to run code in any language or environment. Docker actions provide a high degree of flexibility and can be used to perform complex tasks.
  • Composite Actions: These actions are composed of other actions and can be used to create reusable workflows. Composite actions allow you to define a sequence of steps that can be reused across different workflows.

Creating a JavaScript Action

GitHub Actions allow you to create custom actions to perform specific tasks or integrate with external services. JavaScript actions are written in JavaScript and can be executed in a Node.js environment. In this section, we'll walk through the process of creating a JavaScript action and using it in a workflow.

Setting up the Action Repository

To create a JavaScript action, you need to set up a new repository on GitHub. This repository will contain the code for your action, as well as the necessary configuration files.

Creating the Action Code

  1. Initialize a new Node.js project in your action repository by running the following command in your terminal:
npm init -y
  1. Create an Actions metadata file named action.yml in the root of your repository. This file defines the metadata for your action, such as the name, description, inputs, and outputs. Here's an example of an action.yml file:
name: 'Hello World Action'
description: 'A simple GitHub Action that prints "Hello, World!"'
runs:
  using: 'node12'
  main: 'index.js'
  1. Create the actual action code in a file named index.js. This file will contain the logic for your action. Here's an example of a simple index.js file that prints "Hello, World!":
console.log('Hello, World!');
  1. Commit the changes to your repository and push them to GitHub.

Using the Action in a Workflow

Once you've set up your JavaScript action repository, you can use it in a workflow by referencing the action's repository and version in your workflow file. Here's an example of a workflow that uses the Hello World Action we created:

name: 'Hello World Workflow'
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: 'Checkout code'
        uses: actions/checkout@v4

      - name: 'Run Hello World Action'
        uses: ./ # Reference the action in the current repository

In this workflow, the Hello World Action is referenced using the uses keyword with the path to the action in the current repository. When the workflow runs, it will execute the action code defined in the index.js file.

To use this action in other repositories, you can publish the action to the GitHub Marketplace or a package registry and reference it by its repository and version. For more information on publishing actions, refer to the GitHub Actions documentation.

Like third-party actions, we can specify the repository for a new action to use it in a workflow of another repository.

name: 'Hello World Workflow'
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: 'Checkout code'
        uses: actions/checkout@v4

      - name: 'Run Hello World Action'
        uses: your-github-username/hello-world-action@v1

Creating a Docker Action

In the previous chapter, we learned how to create a JavaScript-based GitHub Action. Since JavaScript is the only natively supported language for GitHub Actions, creating custom actions in other languages can be a bit tricky. However, you can use Docker to create actions in any language or environment.

Docker actions are packaged as Docker containers and can be used to run code in any language or environment. Docker actions provide a high degree of flexibility and can be used to perform complex tasks. In this chapter, we'll walk through the process of creating a Docker-based GitHub Action.

Setting up the Action Repository

  1. Create a new repository on GitHub to host your Docker action.

  2. Create a Dockerfile:

FROM alpine:3.12
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
  1. Create an entrypoint.sh script that contains the logic for your action:
#!/bin/sh
echo "Hello, World!"
  1. Create the actions metadata file named action.yml in the root of your repository. This file defines the metadata for your action, such as the name, description, inputs, and outputs.
name: 'Hello World Action'
description: 'A simple GitHub Action that prints "Hello, World!"'
runs:
  using: 'docker'
  image: 'Dockerfile'
  1. Commit the changes to your repository and push them to GitHub.

Using the Action in a Workflow

Just like the JavaScript action from the previous chapter, you can use the Docker action either locally (in the same repository) or in another repository:

name: 'Hello World Workflow'
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: 'Checkout code'
        uses: actions/checkout@v4

      - name: 'Run Hello World Action'
        uses: ./ # Reference the action in the current repository

Outputs and Inputs

You can also define inputs and outputs for your Docker action. Inputs are passed to the action when it is executed, and outputs are returned from the action after it has completed.

Defining Inputs

To define inputs for your Docker action, you can add an inputs section to the action.yml file. Here's an example of how to define inputs for the Docker action:

name: 'Hello World Action'
description: 'A simple GitHub Action that prints "Hello, World!"'
inputs:
  name:
    description: 'Name to greet'
    required: true
    default: 'World'
runs:
  using: 'docker'
  image: 'Dockerfile'

In this example, we define an input named name that is required and has a default value of World. When the action is executed, the value of the name input will be passed to the Docker container.

Accessing Inputs in the Docker Container

To access the inputs in the Docker container, you can use the GITHUB_ACTION_PATH environment variable, which contains the path to the action's working directory. You can read the input values from the GITHUB_ENV file. Here's an example of how to access the input value in the entrypoint.sh script:

#!/bin/sh
echo "Hello, $INPUT_NAME!"

In this example, we use the $INPUT_NAME environment variable to access the value of the name input. When the action is executed, it will print "Hello, [name]!" where [name] is the value passed to the action.

Defining Outputs

To define outputs for your Docker action, you can add an outputs section to the action.yml file. Here's an example of how to define outputs for the Docker action:

name: 'Hello World Action'
description: 'A simple GitHub Action that prints "Hello, World!"'
outputs:
  greeting:
    description: 'The greeting message'
    value: ${{ steps.greeting.outputs.message }}
runs:
  using: 'docker'
  image: 'Dockerfile'

In this example, we define an output named greeting that contains the greeting message. The value of the output is set to the value of the message output from the greeting step.

Setting Outputs in the Docker Container

To set outputs in the Docker container, you can write the output values to the GITHUB_ENV file. Here's an example of how to set the output value in the entrypoint.sh script:

#!/bin/sh
echo "Hello, $INPUT_NAME!" > $GITHUB_ENV
echo "::set-output name=greeting::Hello, $INPUT_NAME!"

In this example, we use the echo "::set-output name=greeting::[value]" command to set the value of the greeting output. When the action is executed, it will set the output value to "Hello, [name]!" where [name] is the value passed to the action.

Using Outputs in a Workflow

Once you've defined outputs for your Docker action, you can use them in your workflow. Here's an example of how to use the output value in a workflow:

name: 'Hello World Workflow'
on:
  push:
    branches:
      - main
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: 'Checkout code'
        uses: actions/checkout@v4

      - name: 'Run Hello World Action'
        id: hello
        uses: ./ # Reference the action in the current repository

      - name: 'Print Greeting'
        run: echo "${{ steps.hello.outputs.greeting }}"

In this example, we use the ${{ steps.hello.outputs.greeting }} expression to access the value of the greeting output from the hello step. When the workflow is executed, it will print "Hello, [name]!" where [name] is the value passed to the action.

Self-Hosted Runners

GitHub Actions is a cloud-based service that allows you to automate your workflows. However, there may be cases where you need to run your workflows on your own infrastructure. GitHub Actions provides support for self-hosted runners, which allow you to run workflows on your own servers or virtual machines.

This comes in handy when you need to access resources that are not available in the GitHub-hosted runners, such as specific hardware, software, or network configurations. Self-hosted runners also provide more control over the environment in which your workflows run.

Additionally, self-hosted runners can help you run your workflows more economically by utilizing existing infrastructure resources. You can scale the number of self-hosted runners based on your workload and requirements.

Setting Up Self-Hosted Runners

To set up self-hosted runners, you need to install the GitHub Actions Runner application on your server or virtual machine. The runner application is responsible for executing workflows and communicating with GitHub.

Here's how you can set up self-hosted runners:

  1. Create a Personal Access Token: To register a self-hosted runner, you need a personal access token with the repo scope. You can create a personal access token in your GitHub account settings.
  2. Download the Runner Application: Download the GitHub Actions Runner application for your operating system from the GitHub Releases.
  3. Install the Runner Application: Install the runner application on your server or virtual machine by following the installation instructions provided by GitHub.
  4. Register the Runner: Register the runner with your GitHub repository by providing the personal access token and repository information. You can specify the runner's name, labels, and other configuration options during the registration process.
  5. Start the Runner: Start the runner application on your server or virtual machine. The runner will now be available to execute workflows for your repository.

For detailed instructions on setting up self-hosted runners, refer to the official documentation.

Configuring Self-Hosted Runners

Once you have set up self-hosted runners, you can configure your workflows to run on these runners. You can specify the runner configuration in your workflow file using the runs-on key.

Here's an example of a workflow that runs on a self-hosted runner:

name: Self-Hosted Runner Example
on:
  push:
    branches:
      - main
jobs:
  build:
    runs-on: self-hosted
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Build project
        run: |
          npm install
          npm run build

Now that we've covered the fundamentals and the building blocks, it's time to put everything into practice.

In the next article, we'll explore real-world examples of GitHub Actions in action. See you there!

Your Download is in Progress…

Giveaways. Cheat Sheets. eBooks. Discounts. And great content from our blog!