Deploying to Heroku from GitHub Actions

Joe Nash - Jan 7 '20 - - Dev Community

GitHub Actions give us a new way to deploy to Heroku, and to integrate Heroku with other parts of our development workflows. In a single GitHub Actions workflow, we could lint our Dockerfile and package configs, build and test the package on a variety of environments, compile release notes, and publish our app to Heroku.

Today we are going to build a simple workflow that will use the Heroku CLI to deploy a project with a Dockerfile to Heroku via the Heroku Container Registry. During the course of building this workflow, we will see how to create jobs, configure the runner environment with GitHub secret store, use public actions, and react to and filter events.

What is GitHub Actions?

GitHub Actions are a way to trigger custom workflows in response to events on GitHub. For example, a push could trigger Continuous Integration (CI), a new issue being opened could trigger a response from a bot, or a pull request being merged could trigger a deployment.

Workflows, jobs, and actions

Workflows are made up of jobs. These jobs are performed by a runner in a virtual environment. Jobs are composed of individual steps, and those steps can be scripts executed on the runner, or an action. Actions are composable units of workflows, that can be built in Docker containers or JavaScript, placed directly in your repository, or included via the GitHub Marketplace or Docker registry.

Getting started

To get started, push a project with a Dockerfile to GitHub, or fork an existing repository. Don’t forget that Heroku provides an environment variable, $PORT, for apps to bind to for HTTP traffic, so be sure to adjust the project to listen to that variable, rather than an explicitly set port.
Within the repository, navigate to the Actions tab.

A screenshot of the Actions tab within a new GitHub repository. Displayed is “Get started with GitHub Actions”, beneath which is the option to set up a simple starter workflow.

On first opening the Actions tab, we’ll be presented with some options for getting started. These include CI setups for popular languages, as well as a simple example workflow. Let’s start by selecting that simple workflow. Rename the file to workflow.yml, and hit “Start commit”.

Your first workflow

A screenshot of the Code tab after selecting to set up a simple workflow. A code editor holds a simple workflow, repeated in the page below this image. A side bar explains the workflow and presents the option to commit it to the repository.

After the workflow is setup, the repository will have a .github/workflows folder, with a workflow.yml file inside.

This workflow defines one job, build, with 3 steps. One of those steps uses an existing, public action, checkout. The others are defined within the job, with a name, and a run field. run executes commands on the runner performing the job. This job will use an Ubuntu machine. All GitHub Actions environments have the same specs, but you can run jobs on Ubuntu, MacOS, and Windows.

checkout is an important action that you will use in most workflows that work on the code in the repository. checkout fetches the contents of your repository to $GITHUB_WORKSPACE, an environment variable that maps to /home/runner/work on the runner. checkout, like many first-party actions provided by GitHub, is open source and viewable on the Actions GitHub Organization.

This workflow runs on the push event, that is any time new contents are pushed to the repository. You can see the workflow and the status of its run under the Actions tab.

A screenshot of the Actions tab after the workflow has been triggered. The actions tab shows the steps of the workflow having completed successfully with a green tick.

Inside the workflow, you can see that each of the named steps has logs that can be expanded. Naming and describing the steps in your workflows can help this interface provide rich information on the state of your deployments.

Whilst the repository contents are fetched to the runner with checkout, the workflow does not yet make any use of them. Let’s start working towards having this Dockerised project published to a Heroku app.

Environment variables and GitHub Secret store

You can deploy to the Heroku Container Registry with either the Heroku CLI, or Docker. Fortunately, both are available within the virtual environment provided to the runners, when using Ubuntu. Using the Heroku CLI will give us access to other useful utilities, so let’s start there.

Browserless Heroku CLI Authorization

In order to build or deploy, you will have to login to the Container Registry. When using the Heroku CLI locally, login is via the browser, but the Heroku CLI can also be authenticated by providing an OAuth token.

To create an OAuth authorisation, on your local machine, run:
heroku authorizations:create.

This will create a long-lived user authorization, whose token can be used to authenticate the Heroku CLI in our workflow.

The Heroku CLI expects this token to be found in an environment variable, HEROKU_API_KEY. You can define environment variables within job steps, but you don’t want to insert the key directly and commit it to the repository. Luckily, GitHub Actions comes with a new Secrets store, within the repository settings.

A screenshot of the Secrets menu within the Settings tab of a GitHub repository.

There is a prompt to add a new secret.

Within Secrets, create a new secret, HEROKU_API_KEY, and insert the token given by the Heroku CLI.

A screenshot of creating a new secret. There are two inputs, a name field, and a value field. The name filled is filled with  raw `HEROKU_API_KEY` endraw , and the OAuth token is to fill the value field.

Going back to our workflow.yml, add a new step named “Login to Heroku Container Registry”. Within this step you need to define the environment variable HEROKU_API_KEY by grabbing the secret, followed by running container:login with the Heroku CLI.

Workflow environment variables

Environment variables are defined with env. The workflow can access the contents of the repository secret store through a Context. There are many Contexts available which hold information about the workflow run, the job being performed, the runner environment, and the secret store. Retrieve the secret from the secrets Context and assign it to an environment variable like so:

With that environment variable available, you can now run heroku container:login to log into the Heroku Container Registry with the OAuth token.

Commit that code and push it to the repository to trigger the workflow.

Back under the Actions tab, your step will execute and log you in successfully:

The Actions tab showing a green tick against every step of the job to login to Heroku Container Registry.

Build and Release to Heroku Container Registry

Now that the Heroku CLI is authenticated against the Heroku Container Registry, you can push your project to be built, and then release the resulting container.

Create two new steps, one for the push, and the other for release. Each will also need a declaration of the HEROKU_API_KEY environment variable, as environment variables are not persisted between steps.

When pushing and releasing the container, you can specify an app name to target. This could be included safely in the workflow .yml, but as the app name is used in multiple commands, and you may want to change it later, let’s add it to the secret store and access it via the secrets context.

Commit and push those changes, and return to the GitHub repository Actions tab to check on the build:

The Actions tab showing a green tick against every step of the job to build and release to Heroku Container Registry. The Release step is expanded to show the output of the successful command.

The build has completed successfully, and you should now be able to visit your deployed app on Heroku.

Events and Filters

Right now, the push event is triggering this workflow, regardless of branch, or file, that new code is pushed to. You will likely want to tailor this trigger depending on your development practice. For example, if using the GitHub Flow, you may want to deploy to a staging environment when a pull request is merged to master.

Workflow syntax gives us the ability to filter on branches, and files, as well as to trigger on all of the available GitHub webhook events. Let’s modify our push event to filter only for pushes to master.

Commit that, and push to master. Now any subsequent commits to branches other than master will not trigger this workflow.

Summary

You’ve now created a GitHub Actions workflow, that:

  • On a push to master
  • Fetches the contents of the repository
  • Logs into Heroku Container Registry
  • Builds the container on Heroku
  • Publishes the project to an Heroku app

What’s next?

From this workflow, you can add new steps, and jobs, for other tasks in your development process. For example, why not use the Docker Lint action to lint the Dockerfile, before pushing to Heroku Container Registry?

There are also other ways to implement Heroku in a GitHub Actions workflow. This example used the Heroku Container Registry, via the Heroku CLI already installed on the runner virtual environment. The virtual environments also come with Git, so with minimal modification, you could use this workflow to deploy projects without a Dockerfile.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .