Deploy Flask App to Heroku using Github Actions


In some earlier blogposts, we experimented a bit with deploying the same Flask app to various different platforms. We deployed the app to Docker directly or to Docker via Hashicorp Waypoint or even to Elastic Beanstalk. We also deployed the app to Heroku before using the built in GIT functionality within Heroku.

In this post, we will focus again on deployment of our app to Heroku but rather through making use of Github Actions instead of using the Heroku git implementation.

Running the application locally

I always like to make sure my application works on my local dev environment. No point in deploying an app that is failing already locally. If you have been following along earlier posts, you know the drill. In case you are new, just clone the code (here). Then create a Python virtual environment and install the requirements as follows:

~/Flask-Basic-CICD-GithubActions-Heroku ❯ python3 -m venv venv
~/Flask-Basic-CICD-GithubActions-Heroku ❯ source venv/bin/activate
(venv) ~/Flask-Basic-CICD-GithubActions-Heroku ❯ pip3 install -r requirements.txt
(venv) ~/Flask-Basic-CICD-GithubActions-Heroku ❯ gunicorn --bind wsgi:app -w 1
[2021-10-28 08:48:36 +0200] [83787] [INFO] Starting gunicorn 20.0.4
[2021-10-28 08:48:36 +0200] [83787] [INFO] Listening at: (83787)
[2021-10-28 08:48:36 +0200] [83787] [INFO] Using worker: sync
[2021-10-28 08:48:36 +0200] [83788] [INFO] Booting worker with pid: 83788

If all is well, you should see our application on http://localhost:5000


Uploading to Github

Next we will create a Github repository for our application. In my case, it’s called flask-basic-cicd-github-heroku.


You will find also some instructions to configure git and upload the code to the repository.


(venv) ~/Flask-Basic-CICD-GithubActions-Heroku ❯ git init
Initialized empty Git repository in Flask/Flask-Basic-CICD-GithubActions-Heroku/.git/
(venv) ~/Flask-Basic-CICD-GithubActions-Heroku ❯ git branch -m main
(venv) ~/Flask-Basic-CICD-GithubActions-Heroku ❯ git add .
(venv) ~/Flask-Basic-CICD-GithubActions-Heroku ❯ git commit -m "Initial commit"
[main (root-commit) 5dba779] Initial commit
 9 files changed, 71 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 Dockerfile
 create mode 100644 Procfile
 create mode 100644 __pycache__/wsgi.cpython-39.pyc
 create mode 100644 app/__pycache__/app.cpython-39.pyc
 create mode 100644 app/
 create mode 100644 app/templates/index.html
 create mode 100644 requirements.txt
 create mode 100644
(venv) ~/Flask-Basic-CICD-GithubActions-Heroku ❯ git remote add origin
(venv) ~/Flask-Basic-CICD-GithubActions-Heroku ❯ push origin main
Enumerating objects: 15, done.
Counting objects: 100% (15/15), done.
Delta compression using up to 12 threads
Compressing objects: 100% (10/10), done.
Writing objects: 100% (15/15), 2.37 KiB | 1.19 MiB/s, done.
Total 15 (delta 0), reused 0 (delta 0), pack-reused 0
 * [new branch]      main -> main

Github Actions

Next, let’s focus on the Github Actions part a bit.


Click on setup a workflow yourself. Next you will see a proposal main.yml file from Github Actions. Go ahead and commit that file to the main repository. You will see that this file is being put in a .github/workflows folder.


As a next step, you need to pull the latest changes from Github to your local git repository.

(venv) ~/Flask-Basic-CICD-GithubActions-Heroku ❯ git pull origin main
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 5 (delta 1), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (5/5), 1.25 KiB | 213.00 KiB/s, done.
 * branch            main       -> FETCH_HEAD
   5dba779..59d309e  main       -> origin/main
Updating 5dba779..59d309e
 .github/workflows/main.yml | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)
 create mode 100644 .github/workflows/main.yml

Now the file is available locally and we can edit it.

Changing the Github actions workflow

As we mentioned in a previous post we can just edit the main.yml and add the instructions we want to see executed. This sometimes can be a bit cumbersome. Luckily there is Github Marketplace. Essentially it allows you to broiwse through prepared workflows build by other developers. You could then use these as a basis and/or extend them.

In our case, we will be using a specific Github Actions workflow to deploy easily to Heroku. More info about this plugin can be found here. In the documentation, you’ll notice that all we need to provide is:

  • heroku_api_key
  • heroku_app_name
  • heroku_email

These parameters/secrets will be configuered in one of the next sections.


Below is the entire Github Actions workflow file.

# This is a basic workflow to help you get started with Actions

name: CI

# Controls when the workflow will run
  # Triggers the workflow on push or pull request events but only for the main branch
    branches: [main]

  # Allows you to run this workflow manually from the Actions tab

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
  # This workflow contains a single job called "build"
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v2

      - uses: akhileshns/heroku-deploy@v3.12.12 #
          heroku_api_key: ${{secrets.HEROKU_API_KEY}}
          heroku_app_name: ${{secrets.HEROKU_APP_NAME}}
          heroku_email: ${{secrets.HEROKU_EMAIL}}

Setting up Heroku

Let’s create an application on Heroku and retrieve the correct values for each of the above mentioned parameters.


Next, go to Account Settings and scroll down to API Key and generate a new key:


Configuring Github Actions

As we mentioned above, we need to supply these parameters/secrets to our Github Actions workflow in the form of environment variables. Therefore, go to Settings > Secrets and add the parameters we mentioned in the previous section.


Deploy application

Now it’s time to finally deploy our application. We will just sync our local Git repo with the repository on Github.

(venv) ~/Flask-Basic-CICD-GithubActions-Heroku ❯ git add .
(venv) ~/Flask-Basic-CICD-GithubActions-Heroku ❯ git commit -m "Changing workflow"
[main 3e93db3] Changing workflow
 1 file changed, 6 insertions(+), 12 deletions(-)
(venv) ~/Flask-Basic-CICD-GithubActions-Heroku ❯ git push origin main
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 12 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (5/5), 529 bytes | 529.00 KiB/s, done.
Total 5 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
   59d309e..3e93db3  main -> main

As it contains a .workflows/main.yml file, we expect Github Actions to trigger that automatically.


And in the detailed view you see exactly an overview of the tasks that were executed.


Also in your Heroku account, you will get an overview of what has happened:


And finally, after the application has been deployed at Heroku, you can open the browser directly from Heroku. It will show our application.


Update application

Update the index.html file. It really doesn’t matter what change you make as we just want to see that an update is also handled properly by our workflow. In my case, I have changed the titel to This app is deployed through Github Actions to Heroku (instead of This *will* be deployed...) and I added version 2 to the tagline under the title.

(venv) ~/Flask-Basic-CICD-GithubActions-Heroku ❯  git add .
(venv) ~/Flask-Basic-CICD-GithubActions-Heroku ❯  git commit -m "Update index.html"
[main 136019c] Update index.html
 2 files changed, 4 insertions(+), 4 deletions(-)
(venv) ~/Flask-Basic-CICD-GithubActions-Heroku ❯ git push origin main
Enumerating objects: 15, done.
Counting objects: 100% (15/15), done.
Delta compression using up to 12 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (8/8), 590 bytes | 295.00 KiB/s, done.
Total 8 (delta 4), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (4/4), completed with 4 local objects.
   3e93db3..136019c  main -> main

Again, our workflow will be executed automatically as indicated in below screenshot.


And verify one again in your Heroku account that the app is currently being deployed.


And when finished…indeed you will see an updated version of the application.


Heroku is a really powerful platform, I like it a lot. And also the close integration with Github makes it very straightforward to deploy applications. As always, hope you enjoyed this post. The code can be found here.