Setting Up CI/CD Pipelines with GitHub Actions for Django and FastAPI
Introduction
Continuous Integration and Continuous Deployment (CI/CD) are essential practices for modern software development. GitHub Actions provides a powerful, integrated solution for automating your development workflow. In this guide, we'll set up comprehensive CI/CD pipelines for both Django and FastAPI applications.
🚀 Why CI/CD? Automating your workflow ensures consistent code quality, catches bugs early, and enables rapid, reliable deployments.
Understanding GitHub Actions
GitHub Actions allows you to automate workflows directly in your repository. Workflows are defined in YAML files stored in .github/workflows/ and can be triggered by various events like pushes, pull requests, or scheduled times.
Key Concepts
- Workflows: Automated processes defined in YAML files
- Jobs: A set of steps that run on the same runner
- Steps: Individual tasks that run commands
- Actions: Reusable units of code that perform specific tasks
Setting Up CI/CD for Django
Basic Workflow Structure
Create .github/workflows/django-ci.yml:
name: Django CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-django pytest-cov
- name: Run migrations
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
SECRET_KEY: test-secret-key
run: |
python manage.py migrate
- name: Run tests
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
SECRET_KEY: test-secret-key
run: |
pytest --cov=myproject --cov-report=xml --cov-report=term
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
Advanced Django Workflow with Linting
name: Django CI/CD
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install linting tools
run: |
pip install black flake8 isort mypy
- name: Run Black
run: black --check .
- name: Run isort
run: isort --check-only .
- name: Run Flake8
run: flake8 .
- name: Run MyPy
run: mypy .
test:
runs-on: ubuntu-latest
needs: lint
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-django pytest-cov
- name: Run migrations
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
SECRET_KEY: test-secret-key
run: python manage.py migrate
- name: Run tests
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
SECRET_KEY: test-secret-key
run: |
pytest --cov=myproject --cov-report=xml --cov-report=term
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install safety
run: pip install safety
- name: Run safety check
run: safety check --json
continue-on-error: true
Setting Up CI/CD for FastAPI
FastAPI Workflow
Create .github/workflows/fastapi-ci.yml:
name: FastAPI CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-asyncio httpx pytest-cov
- name: Run tests
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
SECRET_KEY: test-secret-key
run: |
pytest --cov=app --cov-report=xml --cov-report=term
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
Complete FastAPI CI/CD Pipeline
name: FastAPI CI/CD
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
workflow_dispatch:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install linting tools
run: |
pip install black ruff mypy
- name: Run Black
run: black --check .
- name: Run Ruff
run: ruff check .
- name: Run MyPy
run: mypy .
test:
runs-on: ubuntu-latest
needs: lint
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-asyncio httpx pytest-cov
- name: Run database migrations
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
run: |
alembic upgrade head
- name: Run tests
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
SECRET_KEY: test-secret-key
run: |
pytest --cov=app --cov-report=xml --cov-report=term -v
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
build:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
yourusername/your-app:latest
yourusername/your-app:${{ github.sha }}
Deployment Workflows
Deploy to Production
name: Deploy to Production
on:
push:
branches: [ main ]
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Deploy to server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.PROD_HOST }}
username: ${{ secrets.PROD_USER }}
key: ${{ secrets.PROD_SSH_KEY }}
script: |
cd /var/www/your-app
git pull origin main
docker-compose down
docker-compose up -d --build
docker-compose exec web python manage.py migrate
docker-compose exec web python manage.py collectstatic --noinput
Deploy to AWS ECS
name: Deploy to AWS ECS
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: your-app
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
- name: Deploy to Amazon ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: task-definition.json
service: your-app-service
cluster: your-app-cluster
wait-for-service-stability: true
Best Practices
1. Use Secrets for Sensitive Data
Never hardcode secrets. Use GitHub Secrets:
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
SECRET_KEY: ${{ secrets.SECRET_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
2. Cache Dependencies
Speed up workflows by caching dependencies:
- name: Cache pip packages
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
3. Use Matrix Strategies
Test against multiple Python versions:
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
4. Conditional Steps
Run steps conditionally:
- name: Deploy
if: github.ref == 'refs/heads/main'
run: echo "Deploying to production"
5. Job Dependencies
Ensure jobs run in the correct order:
jobs:
lint:
runs-on: ubuntu-latest
# ... steps
test:
needs: lint
runs-on: ubuntu-latest
# ... steps
deploy:
needs: test
runs-on: ubuntu-latest
# ... steps
Monitoring and Notifications
Slack Notifications
- name: Notify Slack on failure
if: failure()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Workflow failed!'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
Email Notifications
- name: Send email on failure
if: failure()
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.gmail.com
server_port: 465
username: ${{ secrets.EMAIL_USERNAME }}
password: ${{ secrets.EMAIL_PASSWORD }}
subject: 'CI/CD Pipeline Failed'
to: team@example.com
from: ci@example.com
body: 'The CI/CD pipeline has failed. Please check the logs.'
Conclusion
Setting up CI/CD pipelines with GitHub Actions automates your development workflow, ensures code quality, and enables reliable deployments. By following these patterns, you'll create robust pipelines that catch issues early and deploy with confidence.
🚀 Next Steps: Consider setting up automated security scanning, performance testing, and staging environments for even more comprehensive CI/CD coverage.
Resources:
Related Articles: