CI/CD Integration¶
Integrate Django Safe Migrations into your CI/CD pipeline to catch unsafe migrations before they're merged.
GitHub Actions¶
Basic Setup¶
Create .github/workflows/check-migrations.yml:
name: Check Migrations
on:
pull_request:
paths:
- "**/migrations/**"
- "**models.py"
jobs:
check-migrations:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
pip install django-safe-migrations
pip install -r requirements.txt # Your project dependencies
- name: Check migrations
run: python manage.py check_migrations --format=github
With PostgreSQL¶
For PostgreSQL-specific rules:
name: Check Migrations
on:
pull_request:
paths:
- "**/migrations/**"
jobs:
check-migrations:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: test_db
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/test_db
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
pip install django-safe-migrations[postgres]
pip install -r requirements.txt
- name: Check migrations
run: python manage.py check_migrations --format=github --fail-on-warning
GitHub Annotations¶
Using --format=github creates annotations that appear directly in your pull request files view and check runs.
GitHub PR Comment¶
Using --format=github-pr renders a Markdown summary (grouped by migration
file) that you can post as a single, sticky pull-request comment. The reporter
does no network I/O — a workflow step posts the rendered body:
on: [pull_request]
jobs:
migration-safety:
runs-on: ubuntu-latest
permissions:
pull-requests: write # required to post the comment
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- run: pip install django-safe-migrations
- name: Render report
run: python manage.py check_migrations --format=github-pr > dsm-comment.md
- name: Post PR comment
env:
GH_TOKEN: ${{ github.token }}
run: gh pr comment "${{ github.event.pull_request.number }}" --body-file dsm-comment.md
The comment reports "No migration safety issues found." when the run is clean, so it doubles as a green check.
GitLab CI¶
Basic Setup¶
# .gitlab-ci.yml
check-migrations:
image: python:3.12
stage: test
script:
- pip install django-safe-migrations
- pip install -r requirements.txt
- python manage.py check_migrations --format=gitlab > gl-code-quality-report.json
artifacts:
reports:
codequality: gl-code-quality-report.json
only:
changes:
- "**/migrations/**"
New in v0.5.0: Use
--format=gitlabfor native GitLab Code Quality format, which integrates directly with merge request code quality widgets.
check-migrations:
stage: test
script:
- pip install django-safe-migrations
- python manage.py check_migrations --format=gitlab > gl-code-quality-report.json
artifacts:
reports:
codequality: gl-code-quality-report.json
With PostgreSQL Service¶
# .gitlab-ci.yml
check-migrations:
image: python:3.12
stage: test
services:
- postgres:15-alpine
variables:
POSTGRES_DB: test_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_pass
DATABASE_URL: postgres://test_user:test_pass@postgres:5432/test_db
script:
- pip install django-safe-migrations[postgres]
- pip install -r requirements.txt
- python manage.py check_migrations --format=gitlab --fail-on-warning
artifacts:
when: always
reports:
codequality: gl-code-quality-report.json
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- "**/migrations/**"
- "**/models.py"
SARIF Output for GitLab SAST¶
# .gitlab-ci.yml
check-migrations-sast:
image: python:3.12
stage: test
script:
- pip install django-safe-migrations
- pip install -r requirements.txt
- python manage.py check_migrations --format=sarif --output=gl-sast-report.json
artifacts:
reports:
sast: gl-sast-report.json
allow_failure: true
CircleCI¶
# .circleci/config.yml
version: 2.1
jobs:
check-migrations:
docker:
- image: cimg/python:3.12
steps:
- checkout
- run:
name: Install dependencies
command: |
pip install django-safe-migrations
pip install -r requirements.txt
- run:
name: Check migrations
command: python manage.py check_migrations
workflows:
version: 2
test:
jobs:
- check-migrations
Jenkins¶
Basic Declarative Pipeline¶
// Jenkinsfile
pipeline {
agent any
stages {
stage('Check Migrations') {
steps {
sh '''
pip install django-safe-migrations
pip install -r requirements.txt
python manage.py check_migrations --format=json > migration-report.json
'''
}
post {
always {
archiveArtifacts artifacts: 'migration-report.json'
}
}
}
}
}
With Docker and PostgreSQL¶
// Jenkinsfile
pipeline {
agent {
docker {
image 'python:3.12'
}
}
environment {
DATABASE_URL = 'postgres://postgres:postgres@postgres:5432/test_db'
}
stages {
stage('Setup') {
steps {
sh '''
pip install django-safe-migrations[postgres]
pip install -r requirements.txt
'''
}
}
stage('Check Migrations') {
steps {
script {
def result = sh(
script: 'python manage.py check_migrations --format=json',
returnStatus: true
)
if (result != 0) {
unstable('Migration safety issues detected')
}
}
}
}
}
post {
always {
archiveArtifacts artifacts: 'migration-report.json', allowEmptyArchive: true
}
}
}
Warnings Plugin Integration¶
// Jenkinsfile - with Warnings Next Generation plugin
pipeline {
agent any
stages {
stage('Check Migrations') {
steps {
sh '''
pip install django-safe-migrations
pip install -r requirements.txt
python manage.py check_migrations --format=json > migration-issues.json || true
'''
}
post {
always {
recordIssues(
tools: [issues(pattern: 'migration-issues.json', name: 'Migration Safety')]
)
}
}
}
}
}
Azure Pipelines¶
Basic Setup¶
# azure-pipelines.yml
trigger:
paths:
include:
- '**/migrations/**'
- '**/models.py'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.12'
- script: |
pip install django-safe-migrations
pip install -r requirements.txt
displayName: 'Install dependencies'
- script: |
python manage.py check_migrations --format=json > $(Build.ArtifactStagingDirectory)/migration-report.json
displayName: 'Check migrations'
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: '$(Build.ArtifactStagingDirectory)/migration-report.json'
artifactName: 'migration-report'
condition: always()
With PostgreSQL Service Container¶
# azure-pipelines.yml
trigger:
paths:
include:
- '**/migrations/**'
pool:
vmImage: 'ubuntu-latest'
services:
postgres:
image: postgres:15
ports:
- 5432:5432
env:
POSTGRES_DB: test_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_pass
variables:
DATABASE_URL: 'postgres://test_user:test_pass@localhost:5432/test_db'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.12'
- script: |
pip install django-safe-migrations[postgres]
pip install -r requirements.txt
displayName: 'Install dependencies'
- script: |
python manage.py check_migrations --fail-on-warning
displayName: 'Check migrations'
PR Validation with Comments¶
# azure-pipelines.yml
trigger: none
pr:
paths:
include:
- '**/migrations/**'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.12'
- script: |
pip install django-safe-migrations
pip install -r requirements.txt
displayName: 'Install dependencies'
- script: |
python manage.py check_migrations --format=json > migration-report.json
displayName: 'Check migrations'
continueOnError: true
- task: PublishPipelineArtifact@1
inputs:
targetPath: 'migration-report.json'
artifact: 'MigrationReport'
condition: always()
# Optional: Post results as PR comment using Azure DevOps API
- script: |
if [ -f migration-report.json ]; then
ISSUES=$(cat migration-report.json | python -c "import sys,json; d=json.load(sys.stdin); print(d.get('total', 0))")
if [ "$ISSUES" -gt 0 ]; then
echo "##vso[task.logissue type=warning]Found $ISSUES migration safety issues"
fi
fi
displayName: 'Report results'
Bitbucket Pipelines¶
# bitbucket-pipelines.yml
image: python:3.12
pipelines:
pull-requests:
'**':
- step:
name: Check Migrations
caches:
- pip
script:
- pip install django-safe-migrations
- pip install -r requirements.txt
- python manage.py check_migrations --format=json > migration-report.json
artifacts:
- migration-report.json
condition:
changesets:
includePaths:
- '**/migrations/**'
- '**/models.py'
With PostgreSQL¶
# bitbucket-pipelines.yml
image: python:3.12
definitions:
services:
postgres:
image: postgres:15
environment:
POSTGRES_DB: test_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_pass
pipelines:
pull-requests:
'**':
- step:
name: Check Migrations
services:
- postgres
script:
- pip install django-safe-migrations[postgres]
- pip install -r requirements.txt
- export DATABASE_URL=postgres://test_user:test_pass@localhost:5432/test_db
- python manage.py check_migrations --fail-on-warning
JSON Output¶
For programmatic processing, use JSON output:
Output:
{
"total": 2,
"issues": [
{
"rule_id": "SM001",
"severity": "error",
"operation": "AddField(user.email)",
"message": "Adding NOT NULL field 'email' without default",
"suggestion": "Add a default value or make the field nullable",
"file_path": "myapp/migrations/0002_add_email.py",
"line_number": 15,
"app_label": "myapp",
"migration_name": "0002_add_email",
"operation_index": 0
}
],
"summary": {
"errors": 1,
"warnings": 1,
"info": 0,
"by_rule": { "SM001": 1, "SM002": 1 },
"by_app": { "myapp": 2 }
}
}
CI Best Practices¶
Incremental Checking with Diff Mode¶
Use --diff to only check migrations changed in the current branch:
# GitHub Actions
- name: Check changed migrations only
run: python manage.py check_migrations --diff origin/main --format=github
# GitLab CI
check-migrations:
script:
- python manage.py check_migrations --diff origin/main --format=gitlab > gl-code-quality-report.json
For pipelines that record the last successful commit, --since-commit checks
only what was committed in the range COMMIT..HEAD (ignoring any
uncommitted working-tree edits):
# GitHub Actions - lint only migrations committed since the last green build
- name: Check migrations since last release
run: python manage.py check_migrations --since-commit "$LAST_GREEN_SHA" --format=github
--diff and --since-commit are mutually exclusive.
Baseline for Existing Projects¶
When adopting django-safe-migrations on an existing project, generate a baseline to suppress known issues: