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.
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.
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.
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”.
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 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.
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.
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.
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:
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.
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.
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.
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:
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
Commit and push those changes, and return to the GitHub repository Actions tab to check on the build:
The build has completed successfully, and you should now be able to visit your deployed app on Heroku.
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
Commit that, and push to master. Now any subsequent commits to branches other than
master will not trigger this workflow.
You’ve now created a GitHub Actions workflow, that:
- On a push to
- Fetches the contents of the repository
- Logs into Heroku Container Registry
- Builds the container on Heroku
- Publishes the project to an Heroku app
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.