Creating a "Deploy to Heroku" button

2 minute read

Heroku makes it a breeze to deploy applications to the cloud on their free instances. Although it’s straightforward, here are my notes of how I set up the “Deploy to Heroku” button for No Cookie Analytics.

Step 1: Choose a Docker image

The No Cookie Analytics comprises separate backend and frontend (SPA) apps. To make the deployment process less complex, I set up a monolith Docker image where the backend served static assets.

Step 2: Define app.json

My app.json looked like this. Some notes:

  • Environment keys are either constants or are generated by Heroku. Generated keys are perfect for secrets.
  • Constant field values are user-configurable in the deploy template page.
  • Post-deploy scripts can run database migrations.
{
  "name": "No Cookie Analytics",
  "description": "Hassle-free privacy-friendly analytics",
  "repository": "https://github.com/nocookie-analytics/core",
  "logo": "https://nocookieanalytics.com/img/logo.png",
  "keywords": ["python", "analytics"],
  "website": "https://nocookieanalytics.com",
  "env": {
    "SECRET_KEY": {
      "description": "A secret key for verifying the integrity of signed cookies.",
      "generator": "secret"
    },
    "FIRST_SUPERUSER": {
      "description": "The first superuser to be created",
      "value": "admin@example.com"
    },
    "FIRST_SUPERUSER_PASSWORD": {
      "description": "The password for the first superuser",
      "value": "something-very-secure-please-change"
    }
  },
  "stack": "container",
  "success_url": "/login",
  "scripts": {
    "postdeploy": "sh /app/prestart.sh"
  },
  "addons": [
    {
      "plan": "heroku-postgresql"
    }
  ]
}

Step 3: Define heroku.yml

It’s confusing that the “Deploy to Heroku” button needs two files to work, but this one is simple and it’s a pointer to the Dockerfile.

build:
  docker:
    web: Dockerfile

Result

The “Deploy to Heroku” button can now be embedded in the README. I chose the easier route, where Heroku reads the referrer to find out the Github repository to deploy from:

 [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)

 <!-- Alternative, explicit template -->

 [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://www.heroku.com/deploy?template=https://github.com/<USER>/core/tree/<BRANCH>)

Step 4: Speed things up

Everything already works, but I noticed that Heroku was rebuilding the image for every single deploy. This was taking about 5 minutes to build the monolith Docker image. To make this process faster, I set up a Github Actions workflow to publish the monolith Docker image to the Github Container Registry. Then I added a one-line Dockerfile.heroku to my project:

FROM ghcr.io/nocookie-analytics/nocookie-analytics:main

# This dockerfile is just a reference to the pre-built docker image on ghcr,
# so Heroku does not have to build the image every time.

And I updated heroku.yml like this:

build:
  docker:
    web: Dockerfile.heroku