Skip to content

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=gitlab for 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:

python manage.py check_migrations --format=json

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:

# One-time: generate baseline
python manage.py check_migrations --generate-baseline .migration-baseline.json
# Commit the baseline file

# CI: use baseline to only catch new issues
python manage.py check_migrations --baseline .migration-baseline.json --format=github