Typical Django CI Setup for Production-Grade Projects

Typical Django CI Setup for Production-Grade Projects

When code is pushed or a pull request is opened, an automated pipeline should run to verify that your Django project is still healthy. A well-configured CI (Continuous Integration) pipeline catches bugs, enforces code quality, and prevents broken deployments before they reach production.

For a Django project, CI typically automates the following steps:

CI Pipeline Steps

1. Checkout Code

The CI server pulls your repository to begin the pipeline.

2. Setup Python

Install the required Python version (e.g., 3.11 or 3.12):

python-version: "3.12"

3. Install Dependencies

Usually from requirements.txt or pyproject.toml:

pip install -r requirements.txt

4. Setup Environment Variables

Django often needs configuration variables:

  • SECRET_KEY
  • DEBUG
  • DATABASE_URL
  • Email configuration
  • Third-party API keys

In CI, these are usually fake/test values or stored as repository secrets.

5. Start Required Services

If your app uses PostgreSQL, Redis, or other services, CI starts them as service containers:

  • PostgreSQL for database testing
  • Redis for cache/Celery testing

6. Run Django Checks

Validate project configuration:

python manage.py check

Ensure migrations are committed:

python manage.py makemigrations --check --dry-run

This catches the common mistake of forgetting to commit migration files.

7. Run Linting & Formatting

Common tools:

  • ruff - Fast Python linter
  • black - Code formatting (black --check)
  • isort - Import sorting (isort --check-only)
  • flake8 - Legacy linting

8. Run Tests

Using Django's built-in test runner:

python manage.py test

Or with pytest:

pytest --ds=project.settings

9. Code Coverage (Optional)

Measure how much of your code is tested:

coverage run -m pytest
coverage report

10. Security Scans (Optional)

Common security tools:

  • bandit - Static security analysis
  • pip-audit - Dependency vulnerability scanning
  • safety - Package security checks

Real-World CI Flow for Django

A senior-level Django CI typically validates:

  1. Code style compliance
  2. Import/order issues
  3. Migrations are committed
  4. Django configuration is valid
  5. Unit tests pass
  6. Integration tests pass
  7. Security scan passes

Example GitHub Actions CI for Django

Here's a complete, production-ready GitHub Actions workflow:

name: Django CI

on:
  push:
    branches: [main, develop]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_DB: test_db
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        ports:
          - 5432:5432
        options: >-
          --health-cmd="pg_isready -U postgres -d test_db"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=5

    env:
      SECRET_KEY: test-secret-key
      DEBUG: "False"
      DB_NAME: test_db
      DB_USER: postgres
      DB_PASSWORD: postgres
      DB_HOST: 127.0.0.1
      DB_PORT: 5432

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt

      - name: Run lint
        run: |
          pip install ruff
          ruff check .

      - name: Check migrations
        run: |
          python manage.py makemigrations --check --dry-run

      - name: Run Django checks
        run: |
          python manage.py check

      - name: Run tests
        run: |
          python manage.py test

Recommended Pipeline Order

I recommend structuring your CI steps in this order:

  1. Lint - Fail fast on code style issues
  2. Migration check - Ensure no missing migrations
  3. Django check - Validate configuration
  4. Tests - Run unit and integration tests
  5. Coverage - Report test coverage
  6. Security scan - Check for vulnerabilities

This order ensures bad code fails as early as possible, saving CI time and resources.

Best Practices for Django CI

A proper Django CI should also:

  • Use separate settings for CI/testing (e.g., settings.ci.py)
  • Never use production secrets in CI
  • Use an ephemeral test database that's recreated each run
  • Cache pip dependencies for faster builds
  • Fail if migrations are missing (the --check --dry-run flag)
  • Run on both push and pull_request events
  • Keep CI fast, ideally under a few minutes

CI vs CD

Understanding the distinction:

  • CI (Continuous Integration) = Verify code automatically through tests and checks
  • CD (Continuous Deployment) = Deploy automatically after CI passes

For Django projects:

  • CI checks code quality and functionality
  • CD may deploy to staging or production environments

Production-Grade Extras

In more mature pipelines, you may also add:

  • Matrix testing for multiple Python versions (3.10, 3.11, 3.12)
  • Docker image build validation
  • collectstatic dry-run to catch static file issues
  • Celery worker tests for async task validation
  • API schema validation to ensure contract compliance
  • Container vulnerability scanning for deployed images

Simple Summary

A Django CI pipeline normally follows this flow:

Install app → Start test DB → Run lint → Check migrations → Run Django checks → Run tests

With this foundation, you can progressively add coverage reporting, security scans, multi-version testing, and deployment automation as your project matures.


What does your Django CI pipeline look like? Share your configurations and tips in the comments below!