How To Create A Ci/Cd Pipeline For Your Django Application Using Github Actions

Introduction

In this short how-to guide, you will set up a Continuous Integration / Continuous Deployment or Delivery (CI/CD) pipeline using Github actions.

When you're finished, you'll be able to set up a CI/CD pipeline for your next project. In this example, we are using a Django app but the concepts can be applied to any other framework or app.

LEVEL - 💻 💻 Intermediate developer - Basic knowledge of Git & Docker required. We only explain the high-level concepts.

Key Terms

Continuous Integration (CI) - The practice of automating the integration of code changes from one or more contributors into a single repository.

Continuous Delivery (CD) - The practice of automating the deployment of all code changes to a testing and/or production environment.

Continuous Deployment (CD) - Similar to Continuous deployment except that all changes that pass the stages in the production pipeline are released to your end-users. This is done with no human intervention.

Github actions - A workflow automation tool that can be used to build, package, deploy, test a project on Github. Relevant terms would include:

  • Workflow - Configurable workflow made up of one or more jobs
  • Runner - is a type of machine that the job runs on.

Prerequisites

To complete this tutorial, you will need:

  • A Django starter project
  • Github account

Set up your project

Set up a simple Django project with basic unit tests. You can also download our starter repo from here to get you started.

Create the workflow

In your project repository root, create the .github/workflows directory and the workflow file django.yml.

Defining the events

First, we define the events that will trigger the workflow. In this case, it will be pushing to the main branch or the develop branch. Or pull requests to the same branches.

name: Django CI

on: # HERE we specify when the action should be performed.
  push: # On pushing to the main or dev branch
    branches:
      - main
      - develop
  pull_request: # When PRs are made to the develop & main branches
    branches:
      - develop
      - main

jobs: TODO
  job_name:
    runs_on: TODO..
    strategy: TODO..
    services: TODO..
    steps: TODO..  

Adding our first job

Next, we add the jobs. Our workflow will have one job called build. The workflow will run on a machine referred to as a runner called ubuntu-latest. This machine can be hosted by Github as in our case or self-hosted. See here for more information.

The strategy creates a build matrix for the jobs, here you can define different variations to run each job in. In our case, we want the job to run in both a python 3.8 and 3.7 environment. See the steps section below for more.

In the services section, we add the containers required for a job. Service containers are useful for creating databases or caches.

...

on: ...
jobs:
  build: # Job
    runs-on: ubuntu-latest
    strategy: # Strategy
      max-parallel: 4
      matrix:
        python-version: [3.7, 3.8]
    services:
      postgres:
        image: postgres
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: postgres
          POSTGRES_USER: postgres
        options: >-  # add health check to ensure the db is ready before next step
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 70s
          --health-retries 5

Creating the steps

Lastly, let's add the steps to our workflow. Each step needs to have either a run argument that runs command-line programs using the system's shell or a uses argument which references an action or reusable unit of code to be run in the step.

In the example below, we use the actions/setup-python@v2 action to set up a Python environment. This action is used in the Set up Python ${{ matrix.python-version }} step. As defined in our strategy section earlier, this will be set up for two environments for Python 3.7 & 3.8.

The rest of the steps are standard Django steps for installing dependencies, running our tests as well as a code linter.

...

on:...
jobs:
  build:
    runs_on:...
    strategy:...
    services:..
    steps:
      - uses: actions/checkout@v2
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v2
        with:
          python-version: ${{ matrix.python-version }}
      - name: psycopg2 prerequisites
        run: sudo apt-get install libpq-dev
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
      - name: Run migrations
        run: python manage.py migrate
      - name: Run tests
        run: |
          pytest
      - name: Run lint with black
        run: |
          black .

Push to Github

That's it, you can push to Github. If you have done everything correctly your workflow should build successfully.

Successful workflow

Troubleshooting

Adding external services like DB and cache service e.g. Redis can complicate the workflow setup process. Check that you have set up the environment variables correctly and have used them consistently in your config files.

Each failed action will (usually) have a detailed stack trace/error message, use that to try and diagnose the error.

Conclusion

You have learnt how to use Github actions to automate your workflows. Automating the workflows within your software development life cycle will save you time and improve the collaboration process for your team. It will also reduce the time it takes to develop and ship new features for your product. For more articles on DevOps visit our articles and resources section.

Last Updated 25 Sep 2022