Django CI CD

How to Build a Simple Django CI/CD pipeline for Heroku using Github Actions

As your Django project starts growing; the process of testing and deployment gets cumbersome with time. If we rely heavily upon manual work, there are high chances that we might forget to follow through a couple of steps and our application crashes. It’s always better to automate redundant work and minimise the chances of application failure in production. The process followed by the industry to achieve this is known as CI/CD. A CI/CD pipeline basically automates the software delivery process. CI (Continuous Integration) is where the pipeline will build and test the code. And, CD (Continuous Delivery) is the part where our application gets deployed on the server. The CI/CD approach shown here might not resonate with exactly what you might be using in your organisation. It is mostly focused on simplicity. To help you understand the basics of it. Once the basics are cleared you can easily modify your workflow as per your need. This article was heavily inspired by a CI/CD article by Raju Ahmed Shetu which can be accessed here. A simple article on CI/CD can be found here.

As always, the tutorial is divided into few small sections. Each section focuses on one tiny step at a time. This approach reduces the overall complexity of the entire tutorial and promotes ease of learning. The sections are mentioned below :

Section A: CI/CD Pipeline Flow

For our CI/CD pipeline to work, we need to follow some basic conventions which were primarily discussed in CI/CD article referred to above. We will do some changes to fit our use case. We will consider 3 branches; main, develop and feature.

  • The branch feature is where we would create new features and when we are satisfied with our work we would create a pull request to develop.
  • The develop branch will always trigger dev.yaml on every push or pull request. The workflow dev.yaml is configured to perform health checkups only. Additionally, you can deploy to a different server on each push to develop. This way you can have one last check that the changes are working as they should.
  • When we are satisfied with our develop branch it’s time to create a pull request to main. On every pull and push request to main a new workflow will be triggered called prod.yaml. Now, prod.yaml will do health check just like dev.yaml; but on every push it will also deploy the new code to Heroku.

A diagram is also provided below to show the CI/CD flow we are following.

Fig. 1: CI/CD Flow

Section B: Github and Heroku Setup

Github

You can use your existing Github repo or create a new repo. Your repository needs to have at least two branches namely main and develop. If it doesn’t exist, go ahead and create a new branch called develop. Now we need to protect our branches so that only after review by an authorised developer the branches can be merged. Go to Repository > Settings > Branches > Add Rule > Require pull request reviews before merging. The image shown below is how the rule would look like.

FIg. 2: Github Branch Rule

Heroku

Simply go to Heroku Dashboard and press the button Create New App. Once the app has started go to addons and add Heroku Postgres. For now, we don’t need to do anything else.

Section C: Workflow Setup

There are two workflows present. We have dev.yaml and prod.yaml. Let us look at the source code of dev.yaml:

name: Dev Worflow -  Only Test

on:
  pull_request:
    branches: [develop]
  push:
    branches: [develop]

jobs:
  health-checkup-job: #Check the healthy by running tests
    runs-on: ubuntu-latest
    strategy:
      max-parallel: 4
      matrix:
        python-version: [3.8]

    steps:
      - uses: actions/checkout@v2
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v2
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install Dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
      - name: Run migrations
        run: |
          python manage.py makemigrations --noinput
          python manage.py migrate --noinput
      - name: Run Tests
        run: |
          python manage.py test
      - name: Check Syntax #We are just testing the syntax in names app; pycodestyle uses pep8 conventions of max line length of 79 characters while Django recommends 119 characters
        run: pycodestyle --statistics names

If you are familiar with Django most of the commands are self-explanatory. I have also added some comments on parts where it might be confusing. Every workflow file needs to have a ‘name’ then we define when it’s triggered with ‘on’. In our case, it’s getting triggered on pull_request and push on develop. Then we define the ‘jobs’ to be performed followed by ‘steps’ within the job. Learn more about the workflow and how to define them from here. Similarly, we will also have prod.yaml which will not only do the testing as in dev.yaml but will also deploy our application on Heroku. The source code is given below:

name: Main Workflow - Test and Deploy

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  health-checkup-job: #Check the healthy by running tests
    runs-on: ubuntu-latest
    strategy:
      max-parallel: 4
      matrix:
        python-version: [3.8]

    steps:
      - uses: actions/checkout@v2
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v2
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install Dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
      - name: Run migrations
        run: |
          python manage.py makemigrations --noinput
          python manage.py migrate --noinput
      - name: Run Tests
        run: |
          python manage.py test
      - name: Check Syntax #We are just testing the syntax in names app; pycodestyle uses pep8 conventions of max line length of 79 characters while Django recommends 119 characters
        run: pycodestyle --statistics names
  #Before deploy job you would usually have the build job in case you are using docker images
  deploy-job:
      runs-on: ubuntu-latest
      needs: [health-checkup-job]
      if: ${{ github.event_name == 'push' }}
      steps:
        - uses: actions/checkout@v2
        - uses: akhileshns/heroku-deploy@v3.12.12
          with:
            heroku_api_key: ${{secrets.HEROKU_API_KEY}}
            heroku_app_name: ${{secrets.HEROKU_APP_NAME}}
            heroku_email: ${{secrets.HEROKU_EMAIL}}

Notice the ‘needs: [health-checkup-job]’ action above, it basically means that to perform the ‘deploy-job’ first the ‘health-checkup-job’ needs to complete. Once your application is deployed you still need to migrate your models and create a superuser. This can be easily performed with Heroku run commands from your terminal or the Heroku Dashboard > Your App > More > Run Console.

Section D: Django Setup

Your Django project won’t deploy automatically unless you make some Heroku specific changes in your repository. It would be redundant work if I try to explain the process here. Instead, visit the link provided here to learn more about the deployment in Heroku.

Section E: Credentials and Environment Variables

If you have seen the ‘deploy-job’ under the prod.yaml file, you might have noticed three variables – ${{secrets.HEROKU_API_KEY}}, ${{secrets.HEROKU_APP_NAME}} and ${{secrets.HEROKU_EMAIL}}. These are special Github secret variables. To define them we need to go to Repository > Settings > Secrets > New repository secret.

Fig. 3: Github Secrets

HEROKU_API_KEY can be found in Heroku Account Settings. HEROKU_APP_NAME is the name of the app you created in Section B and HEROKU_EMAIL is the Email ID of your Heroku account.

If you don’t have any active repository, I would encourage you to clone my Github Repo. Feel free to clone and upload it in your own repository.

Testing

Make some changes in your feature branch and create a pull request to develop branch. Once the Health Checkup Job passes go ahead and merge it with develop. Now, you can create a pull request to main and again Health Checkup Job will run. Once it passes; merge the develop branch with main. Once the merge is successful; prod.yaml will run and first conduct the Health Check Job and when the job succeeds it will run the Deploy Job. When the Deploy Job gets completed our application gets successfully deployed in Heroku.

Our output should look something like this:

Now that our pipeline is working flawlessly we could consider improving our CI/CD pipeline and adding more steps as required. We can also work towards improving it’s performance by introducing cache. This is beyond the scope of this article as our main aim is to have a simple CI/CD pipeline. Since, our deployment is automatically handled by Github we can sit back and relax. Afterall, the machines are working for us 🙂

If you are interested you can also read another article written by me about form validations in Django.

close

Oh hi there 👋
It’s nice to meet you.

Sign up to receive awesome content in your inbox

We don’t spam! Read our privacy policy for more info.

2 thoughts on “How to Build a Simple Django CI/CD pipeline for Heroku using Github Actions

Leave a Reply

Your email address will not be published. Required fields are marked *