Continuous deployment

Deploying the app is an activity that is best done continuously and automatically. I'd optimise to make it as easy and as quick as possible to deploy, this way when bugs affect users they only do so for a short period. We can enable continuous deployment for our app by adding it to the Gitlab CI/CD script.

To do this we first need credentials to interact with Heroku to be available to the Gitlab CI runner. We can do this by adding the following to infrastructure/gitlab.tf,

resource "gitlab_project_variable" "heroku_app" {
  key       = "HEROKU_APP"
  value     = heroku_app.tozo.name
  project   = gitlab_project.tozo.id
  protected = true
}
resource "gitlab_project_variable" "heroku_token" {
  key       = "HEROKU_API_KEY"
  value     = var.heroku_api_key
  project   = gitlab_project.tozo.id
  protected = true
}
resource "gitlab_project_variable" "heroku_username" {
  key       = "HEROKU_USERNAME"
  value     = var.heroku_username
  project   = gitlab_project.tozo.id
  protected = true
}

The deployment requires four steps, firstly we need to login to Heroku's docker repository, then build the docker image, followed by pushing it to Heroku's respository, and finally instruct Heroku to use our new image for the app. This is achieved by adding the following to .gitlab-ci.yml,

heroku-cd:
  stage: deploy
  image: docker:latest

  services:
    - docker:dind

  before_script:
    - apk add --update curl

  script:
    - >
        docker login registry.heroku.com
        --username $HEROKU_USERNAME
        --password $HEROKU_API_KEY

    - >
        docker build --build-arg CI_COMMIT_SHA=$CI_COMMIT_SHA
        -t registry.heroku.com/$HEROKU_APP/web .

    - docker push registry.heroku.com/$HEROKU_APP/web

    - >
        curl --fail
        -X PATCH "https://api.heroku.com/apps/$HEROKU_APP/formation"
        -H 'Content-Type:application/json'
        -H 'Accept:application/vnd.herokujson; version=3.docker-releases'
        -H "Authorization:Bearer $HEROKU_API_KEY"
        -d '{"updates":[{"type":"web","docker_image":"'$(docker inspect registry.heroku.com/$HEROKU_APP/web --format={{.Id}})'"}]}'
  only:
    - main

To prevent us accidently trying to deploy a broken commit I've introduced stages to the CI, with the above being in the deploy stage. This is achieved by adding the following to .gitlab-ci.yml,

stages:
  - lint-test
  - deploy

and adding,

stage: lint-test

to the existing CI jobs.