Introduction
Continuous Integration and Continuous Deployment (CI/CD) are foundational practices in modern software engineering. They allow teams to catch bugs early, automate tedious release steps, and ship code with confidence. In this guide I'll walk you through setting up a production-ready CI/CD pipeline for a Python/Django project using GitHub Actions and Docker.
Step 1 โ Set Up Your GitHub Repository
Start by creating a GitHub repository for your Django project. Make sure your project has a requirements.txt and a working test suite before continuing. Good CI/CD starts with a solid test baseline.
Step 2 โ Create the GitHub Actions Workflow
Create the file .github/workflows/main.yml at the root of your repository. This YAML file defines your entire pipeline:
name: CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install -r requirements.txt
- run: python manage.py test
build-and-push:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Push to Docker Hub
run: |
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
docker push myapp:${{ github.sha }}
Step 3 โ Configure Docker
Create a Dockerfile in your project root. A good practice is to use a multi-stage build to keep your production image lean:
FROM python:3.11-slim AS base WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]
Step 4 โ Auto-Deploy on Push
Add a deploy job to your workflow that SSHes into your server (or uses a Kubernetes rolling update) to pull the new Docker image and restart the service. Store all credentials as GitHub Secrets โ never hard-code them in the YAML file.
Conclusion
With this pipeline in place, every push to main automatically runs your tests, builds a Docker image, and deploys to production. This dramatically reduces manual release risk and accelerates your delivery cadence.