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_KEYDEBUGDATABASE_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:
- Code style compliance
- Import/order issues
- Migrations are committed
- Django configuration is valid
- Unit tests pass
- Integration tests pass
- 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:
- Lint - Fail fast on code style issues
- Migration check - Ensure no missing migrations
- Django check - Validate configuration
- Tests - Run unit and integration tests
- Coverage - Report test coverage
- 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-runflag) - Run on both
pushandpull_requestevents - 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!