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
- Section B: Github and Heroku Setup
- Section C: Workflow Setup
- Section D: Django Setup
- Section E: Credentials and Environment Variables
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.
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.
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/[email protected]
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.
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.
Md. Saifur Rahman is a Full Stack Django and Laravel Developer. Additionally, he loves to spend time learning and researching about the Internet of Things (IoT). He loves to share his work and contribute to helping fellow developers.
This is a good informative article. Can I please fork the project?
Thanks! Sure go ahead.