Hello! Welcome back to our final module on CI/CD and Scalability.
In our last lesson, we explored the "why" behind automation, contrasting the slow, error-prone nature of manual deployments with the speed and reliability of Continuous Integration and Continuous Deployment. You learned that CI is the practice of automating builds and tests for every code change, ensuring the main branch is always stable.
Today, we move from theory to practice. The learning outcome for this lesson is to create a basic CI pipeline using GitHub Actions to automatically run tests on code changes. You will take the Django application you've built and configure a workflow that validates its correctness every time you push code to your repository. This is your first concrete step into implementing DevOps automation.
This lesson will cover:
- An introduction to GitHub Actions and its core concepts.
- How to structure and write a basic workflow file in YAML.
- The essential steps for a Django CI pipeline: checking out code, setting up Python, installing dependencies, and running tests.
- How to monitor and interpret the results of your CI pipeline.
Let's start by understanding our tool of choice: GitHub Actions.
1. What is GitHub Actions?
GitHub Actions is a CI/CD platform built directly into GitHub. It allows you to automate your software development workflows right from your repository. You can use it to build, test, and deploy your code, but it can also automate other tasks like managing issues or labeling pull requests. For our purposes, we'll focus on its CI capabilities.
To understand how GitHub Actions works, it's essential to learn its core vocabulary.
Automate Your Workflow: A Guide to CI/CD with GitHub Actions
The article 'Automate Your Workflow: A Guide to CI/CD with GitHub Actions' provides an excellent glossary of key terms. Understanding these will make the workflow file we build much easier to comprehend.
Read the sections 'Benefits of GitHub Actions' and 'Glossary'. Focus on the definitions for Workflow, Event, Job, Step, and Runner.
To summarize the key terms:
- Workflow: The entire automated process, defined in a YAML configuration file.
- Event: A specific activity in your repository that triggers a workflow run (e.g., a
pushto a branch or opening apull_request). - Runner: A server (a fresh virtual machine) that runs your workflows. GitHub provides runners with Linux, Windows, and macOS environments.
- Job: A set of steps that execute on the same runner. By default, a workflow with multiple jobs will run them in parallel.
- Step: An individual task that can run commands or use a pre-built Action.
- Action: A reusable piece of code that performs a complex but common task, like checking out your repository code (
actions/checkout) or setting up a specific version of Python (actions/setup-python).
With this vocabulary in hand, we're ready to create our first workflow.
2. Building a CI Workflow for Django
A workflow is defined in a YAML file located in a specific directory within your project.
Directory Structure
GitHub Actions looks for workflow files in the .github/workflows/ directory at the root of your repository. You can name the file anything you like, but it must have a .yml or .yaml extension. For our Django project, let's create a file named .github/workflows/ci.yml.
This strict directory structure is how GitHub discovers and executes your automated workflows.
Writing the Workflow File
Let's build our ci.yml file step-by-step. The syntax is declarative, which should feel familiar given your computer science background. You define the desired end state, and GitHub Actions handles the execution.
A great, simple example of a Django CI workflow is provided in the following article. We'll use it as our primary guide.
Automating Django Tests with GitHub Actions: A Beginner’s CI Guide
The article 'Automating Django Tests with GitHub Actions' by Dhanpal Singh Rajpurohit presents a clear, minimal workflow for a Django project. It's a perfect starting point.
Review the section '9. GitHub Actions CI Workflow'. Read through the YAML code block and the explanations for each part, from the workflow name to the final test command. We will construct our own file based on this pattern.
Now, let's synthesize that information and create our ci.yml file. Here is the complete workflow, followed by a breakdown of each section.
# .github/workflows/ci.yml
name: Django CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out the code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Django Tests
run: python manage.py test
Let's break this down:
-
name: Django CI- This gives our workflow a human-readable name that will appear in the "Actions" tab of your GitHub repository.
-
on:- This section defines the events that trigger the workflow.
- We've configured it to run on any
pushto themainbranch, and also when anypull_requestis opened or updated that targets themainbranch. This ensures tests run both before and after merging code.
-
jobs:- This is where we define the work to be done. We have a single job named
test.
- This is where we define the work to be done. We have a single job named
-
runs-on: ubuntu-latest- This tells GitHub to provision its latest standard Ubuntu Linux runner for this job. This gives us a clean, predictable environment for every run.
-
steps:- This is the sequence of tasks our
testjob will execute. Each step is executed in order. uses: actions/checkout@v4: This is a crucial first step. It uses a pre-built Action to download a copy of your repository's code onto the runner. Without this, the runner wouldn't have yourmanage.pyfile or any of your Django apps.uses: actions/setup-python@v5: This Action installs a specific version of Python (3.11 in this case) onto the runner. This guarantees that your tests run with the same Python version you use in development and production.run: ...: Therunkeyword executes shell commands. We first upgradepipand then install all the project dependencies listed in yourrequirements.txtfile. The|allows for a multi-line script.run: python manage.py test: This is the final and most important step. It executes the exact same command you would use locally to run your Django test suite. If any test fails, this command will exit with a non-zero status code, which causes the step—and the entire job—to fail.
- This is the sequence of tasks our
3. Running the Pipeline and Reading the Results
Once you commit and push this ci.yml file to your GitHub repository, the workflow will automatically trigger. You can monitor its progress by navigating to the "Actions" tab in your repository.
You will see your "Django CI" workflow listed. Clicking on it will show you each run, and clicking on a specific run reveals the test job.

When a step fails, it will be marked with a red "X". You can expand the step to view the log output and debug what went wrong, just as you would on your local terminal.
To see this process in action, the following video provides a live demonstration of pushing a workflow file, creating a pull request, and watching the CI pipeline execute.
Pro Django - Tutorial 8 - GitHub Actions and Workflows
The video 'Pro Django - Tutorial 8 - GitHub Actions and Workflows' provides a fantastic real-time demonstration of the concepts we've just discussed. Seeing it live will solidify your understanding.
Watch the segment from 12:25 to 14:49. This shows the developer pushing the new workflow file, creating a pull request, and then navigating the GitHub UI to watch the steps of the CI job execute successfully.
4. Extending the Workflow with a Matrix Strategy
Our current workflow is great, but it only tests against one version of Python on one operating system. What if you want to ensure your application works across multiple environments? This is where a matrix strategy comes in. It allows you to run the same job with different configurations.
For a backend developer, this is a powerful technique to ensure library compatibility and cross-platform support. Let's see how you would modify the job to test on Python 3.10 and 3.11.
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11"] # Define the matrix
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }} # Use the matrix variable
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }} # Use the matrix variable
# ... rest of the steps are the same
With this strategy and matrix configuration, GitHub Actions will automatically create two separate job runs: one for Python 3.10 and one for Python 3.11. They will run in parallel, giving you feedback much faster.
Test your understanding!
Imagine you need to ensure your Django application not only works on different Python versions but also on both Linux and Windows. How would you modify the matrix to achieve this?
Show answer
You would add an os key to the matrix like so:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ["3.10", "3.11"]
This configuration would cause GitHub Actions to run four jobs in parallel:
- Ubuntu with Python 3.10
- Ubuntu with Python 3.11
- Windows with Python 3.10
- Windows with Python 3.11
Conclusion
Congratulations! You have just designed your first CI pipeline. You've taken a critical step from being a developer who writes code to a backend engineer who builds robust, automated systems to ensure code quality. By automating your test suite, you've created a safety net that allows you and your team to develop features more quickly and with greater confidence.
Key Takeaways:
- GitHub Actions workflows are defined in YAML files located in the
.github/workflowsdirectory. - Workflows are triggered by events like
pushandpull_request. - A workflow consists of one or more jobs, which run on virtual machines called runners.
- Each job contains a sequence of steps that check out code, configure the environment, and execute commands.
- The
actions/checkoutandactions/setup-pythonActions are fundamental building blocks for a Python CI pipeline. - The core of our CI pipeline is the
python manage.py testcommand, which automatically validates our code. - A matrix strategy can be used to efficiently test your code across multiple environments (e.g., different Python versions or operating systems).
Preview of the Next Lesson:
Our CI pipeline now ensures our code is testable, but it doesn't produce a deployable artifact. In the next lesson, we will extend this pipeline to take the next step in the CD process: you will configure a CD step to automatically build a Docker image and push it to a container registry. This will prepare our application for automated deployment.