CI/CD Pipeline Using GitHub, Docker, CircleCI, and Heroku

DZone 's Guide to

CI/CD Pipeline Using GitHub, Docker, CircleCI, and Heroku

This post will walk you through how to setup a Continuous Integration and Continuous Deployment (CI/CD) pipeline with CircleCI, Docker, and Heroku.

· DevOps Zone ·
Free Resource

This post will walk you through how to setup a Continuous Integration & Continuous Deployment (CI/CD) pipeline with CircleCI, Docker & Heroku easily. In the end of this tutorial, you should be able to setup your own CI/CD as shown in the diagram above.

This tutorial assumes that you have:

  • A Heroku Account - sign up for free.
  • A Heroku CLI installed.
  • A CircleCI Account - sign up for free with your Github account.
  • Python version 3.5 installed locally.

The source code of the application used in this demo is available on my GitHub.

1 - Heroku

First, login in using the email address & password you used when creating your Heroku account:

heroku login

To clone the sample application so that you have a local version of the code that you can then deploy to Heroku, execute the following commands in your local command shell or terminal

git clone https://github.com/mlabouardy/circleci-heroku-flask.git
cd circleci-heroku-flask/

Note: in case you are using your own app, you should add the following files to your code repository:

  1. Procfile: It tells Heroku what commands should be run.
  2. requirements.txt: In this file, you will list the packages/dependencies that pip should install for you.

Create an app on Heroku, which prepares Heroku to receive your source code:

heroku create movies-store-demo

Provision a MySQL database add-on:

heroku addons:add cleardb:ignite --app movies-store-demo

Heroku will automatically add a config var with the database credentials in the form of a URL. You find the config vars under the Settings tab, and click the button to " Reveal config vars "

Now deploy the application:

git remote add heroku https://git.heroku.com/movies-store-demo.git

Go to Heroku Dashboard, click on the "Open App" button:

You should see:

Note: As a handy shortcut, you can open the application as follows:

heroku open

2 - CircleCI

The following sections walk through how CI/CD steps are configured for this application, how to run unit tests, build & push the Docker Image to DockerHub, and how to deploy the demo application to Heroku:

The .circleci/config.yml contains CI/CD steps:

  • We will use Python 3.5 as the primary container & MySQL for the build environment:
version: 2
      - image: python:3.5
          MYSQL_DB: mydb
          MYSQL_USER: root
          MYSQL_PASSWORD: root
      - image: mysql:5.6
          MYSQL_ROOT_PASSWORD: root
          MYSQL_DATABASE: mydb
  • The first step of the pipeline is checking out the project:

      - checkout
  • To speed up the builds, we place the Python virtualenv into the CircleCi cache and restores cache before running pip install

      - restore_cache:
          key: deps1-{{ .Branch }}-{{ checksum "requirements.txt" }}

      - run:
          name: Install dependencies
          command: |
            python3 -m venv venv
            . venv/bin/activate
            pip install -r requirements.txt
      - save_cache:
          key: deps1-{{ .Branch }}-{{ checksum "requirements.txt" }}
            - "venv"
  • Unit Tests requires MySQL database, therefore, we need to wait for the container to be ready:

    - run:
          name: Install dockerize
          command: wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
            DOCKERIZE_VERSION: v0.3.0

      - run:
          name: Wait for MySQL
          command: dockerize -wait tcp://localhost:3306 -timeout 1m

      - run:
          name: Run unit tests
          command: |
            python3 -m venv venv
            . venv/bin/activate
            python app/tests.py
  • We will install the Docker Client, build the docker image from the Dockerfile stored in the GitHub repository, and then Push the image to DockerHub.

      - setup_remote_docker

      - run:
          name: Install Docker client
          command: |
            set -x
            curl -L -o /tmp/docker-$VER.tgz https://get.docker.com/builds/Linux/x86_64/docker-$VER.tgz
            tar -xz -C /tmp -f /tmp/docker-$VER.tgz
            mv /tmp/docker/* /usr/bin
      - run:
         name: Build Docker image
         command: docker build -t mlabouardy/movies-store:$CIRCLE_SHA1 .

      - run:
          name: Push to DockerHub
          command: |
            docker login -u$DOCKERHUB_LOGIN -p$DOCKERHUB_PASSWORD
            docker push mlabouardy/movies-store:$CIRCLE_SHA1
  • Finally, we install Heroku CLI & push the changes to Heroku

      - run:
          name: Setup Heroku
          command: |
            chmod +x .circleci/setup-heroku.sh
      - run:
          name: Deploy to Heroku
          command: |
            cd app/
            git push heroku master

As shown in the configuration file above, we will need to set some environment variables, so navigate to the Project settings:

Finally, to enable the connection to the Heroku Git Server from CircleCI we need to create an SSH Key without passphrase. Issue the following command:

ssh-keygen -t rsa

Then, add the private key in the CircleCI UI SSH Permissions page with a hostname of git.heroku.com as follows:

The public key is added to Heroku on the Account page:

Now every time you push changes to your Github repo, CircleCI will automatically deploy the changes to Heroku. Here's a passing build:

The CI/CD pipeline steps as described in config.yml file:

The Docker Image repository on DockerHub:

Heroku last build from CircleCI:

ci/cd, circleci, devops, docker, github, heroku, jenkins, pipelines, tutorial

Published at DZone with permission of Mohamed Labouardy , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}