Deploying Docker Containers from GitLab to Heroku

Mar 26, 2019

Yesterday I was setting up a Gitlab CI/CD pipeline to deploy Docker containers to Heroku. Whilst it’s a fairly straightforward task, there are a few pitfalls, and I was surprised by the dearth of good tutorials which explained them.

So to successfully deploy a Docker container to Heroku, we have to do a number of things:

  1. The Docker image needs to be built.
  2. The Docker image needs to be pushed to Heroku.
  3. The container needs to be released, at which point the new image will replace the existing service running on Heroku.

All of these steps are described in a .gitlab-ci.yml file, which is shown in full at the end of this document. The CI file breaks up the deployment logically into the three steps above:

stages:
 - build
 - push
 - deploy

Building the Docker image

The first thing that we need to do is create a Docker image. To do so, we first have to log in to the Gitlab registry, which is a store of Docker images. Having done so, we build the image, and then tag the image as “latest”.

build_image:
  image: docker:latest
  services:
  - docker:dind
  stage: build
  script:
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.gitlab.com
    - docker build -t registry.gitlab.com/yourusername/yourapp .
    - docker push registry.gitlab.com/yourusername/yourapp:latest

The “image” entry in the code above refers to the base image of the environment which will be spun up in order to build our image. So we’re spinning up a Docker container, and then building the image inside it, before pusing it to the Gitlab registry. Got that?

The “docker:dind” service which is specified under “services” allows us to run the full Docker toolset from within a docker container. “DinD” stands for “Docker in Docker”.

Pushing our container to Heroku

push_to_heroku:
  image: docker:latest
  stage: push
  services:
  - docker:dind
  script:
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.gitlab.com
    - docker pull registry.gitlab.com/bencollier/cobol-api:latest
    - docker login --username=_ --password=<a password> registry.heroku.com
    - docker tag registry.gitlab.com/bencollier/cobol-api:latest registry.heroku.com/cobol-api/web:latest
    - docker push registry.heroku.com/cobol-api/web:latest

Releasing our container

Here’s the full .gitlab-ci.yml file:

stages:
 - build
 - push
 - release

build_image:
  image: docker:latest
  services:
  - docker:dind
  stage: build
  script:
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.gitlab.com
    - docker build -t registry.gitlab.com/bencollier/cobol-api .
    - docker push registry.gitlab.com/bencollier/cobol-api:latest
    
push_to_heroku:
  image: docker:latest
  stage: push
  services:
  - docker:dind
  script:
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.gitlab.com
    - docker pull registry.gitlab.com/bencollier/cobol-api:latest
    - docker login --username=_ --password=<a password> registry.heroku.com
    - docker tag registry.gitlab.com/bencollier/cobol-api:latest registry.heroku.com/cobol-api/web:latest
    - docker push registry.heroku.com/cobol-api/web:latest

deploy_to_heroku:
  image: node:latest
  stage: deploy
  services:
  - docker:dind
  script:
    - npm install -g heroku
    - heroku container:release web --app cobol-api