← Back to Blog

SchemaLens in Your CI/CD Pipeline

DevOps · April 21, 2026 · 8 min read

Database migrations are the only code changes that can lock tables, truncate data, and break production with a single missing keyword. Yet most teams review migrations the same way they review JavaScript: a quick glance at the diff and an approval. That works until someone drops a column that a background worker still references, or adds NOT NULL to a column with ten million NULL rows.

The fix isn't more manual review. It's automated schema diffing in your CI/CD pipeline — running on every pull request that touches a .sql file, posting a clear report of what changed, and optionally failing the build when a dangerous change slips through.

This post shows exactly how to set it up with SchemaLens, GitHub Actions, and GitLab CI.

What You'll Build

By the end of this guide, your pipeline will:

  1. Detect when a pull request modifies schema files
  2. Extract the old schema from the base branch and the new schema from the PR
  3. Run a semantic diff that understands tables, columns, constraints, and indexes
  4. Post a formatted markdown report as a PR comment or pipeline artifact
  5. Fail the build if the diff contains breaking changes (optional)

The SchemaLens CLI

SchemaLens includes a zero-dependency Node.js CLI at ci/schemalens-diff.js. It embeds the full parser and diff engine — no npm install required. This means it runs anywhere Node.js is available: GitHub Actions, GitLab CI, Docker containers, and local machines.

Basic usage

node ci/schemalens-diff.js \
  schema_old.sql schema_new.sql \
  --dialect postgres \
  --format markdown \
  --output diff-report.md

Supported flags:

Exit codes:

GitHub Actions Setup

Create .github/workflows/schema-diff.yml with the following workflow:

name: Schema Diff

on:
  pull_request:
    paths:
      - '**/*.sql'
      - 'db/migrations/**'

jobs:
  diff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Get base branch schema
        run: |
          git show origin/${{ github.base_ref }}:schema.sql > schema_base.sql || true

      - name: Run SchemaLens diff
        run: |
          node ci/schemalens-diff.js schema_base.sql schema.sql \
            --dialect postgres --format markdown --output report.md
        continue-on-error: true

      - name: Comment PR with diff report
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const report = fs.readFileSync('report.md', 'utf8');
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: '## Schema Diff Report\n\n' + report
            });

This workflow triggers only when .sql files change, checks out both branches, extracts the base schema, runs the diff, and posts the report as a PR comment.

Failing on breaking changes

To make the build fail when specific changes are detected, parse the JSON output and inspect the diff structure:

- name: Check for breaking changes
  run: |
    node ci/schemalens-diff.js schema_base.sql schema.sql \
      --dialect postgres --format json --output diff.json

    node -e "
      const diff = require('./diff.json');
      const breaking = diff.tablesRemoved.length > 0 ||
        diff.tablesModified.some(t =>
          t.columnsRemoved.length > 0 ||
          t.constraintsRemoved.some(c => c.type === 'FOREIGN KEY')
        );
      if (breaking) {
        console.error('Breaking schema changes detected');
        process.exit(1);
      }
    "

Customize the breaking-change heuristic to match your team's risk tolerance. Some teams treat any column drop as breaking; others only care about foreign key removals.

GitLab CI Setup

For GitLab, add this job to your .gitlab-ci.yml:

schema_diff:
  image: node:20-alpine
  stage: test
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      changes:
        - "**/*.sql"
  script:
    - |
      git show origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME:schema.sql \
        > schema_base.sql || true
    - |
      node ci/schemalens-diff.js schema_base.sql schema.sql \
        --dialect postgres --format markdown --output diff-report.md
  artifacts:
    paths:
      - diff-report.md
    expire_in: 1 week

The report is attached to the pipeline as an artifact. Team members can download it from the merge request page or integrate it with GitLab's code quality report format.

Multi-Dialect Repositories

If your project supports multiple databases (for example, PostgreSQL in production and SQLite for local testing), run the diff for each dialect:

strategy:
  matrix:
    dialect: [postgres, mysql, sqlite]

steps:
  - name: Diff ${{ matrix.dialect }}
    run: |
      node ci/schemalens-diff.js schema_base.sql schema.sql \
        --dialect ${{ matrix.dialect }} \
        --format markdown \
        --output report-${{ matrix.dialect }}.md

This catches dialect-specific issues early — like using ALTER COLUMN ... TYPE in a file that gets run against MySQL in some environments.

Where to Store Your Schema Snapshot

The pipeline needs two schema files: the base (from the target branch) and the new (from the PR). There are three common patterns for this:

Pattern 1: Schema dump in version control

Commit a schema.sql file to the repo. The CI diffs the PR's version against the base branch's version. This is the simplest approach and guarantees the diff is always available.

Pattern 2: Generate from migrations

Run your migration tool (Flyway, Liquibase, Prisma Migrate) against an empty database in CI to produce the schema, then diff the result. This is more accurate but slower.

Pattern 3: Snapshot from staging

Download the latest staging schema from a known URL or S3 bucket at the start of the pipeline. This diffs against the real expected production schema, not just the last committed version.

Recommendation: Start with Pattern 1. A committed schema.sql is fast, deterministic, and easy to review. Upgrade to Pattern 2 or 3 when you need higher fidelity.

What the Report Looks Like

A typical markdown report from the CLI includes:

Here's an example of what gets posted to a pull request:

## Schema Diff Report

**Summary:** 1 table modified, 2 columns added, 1 constraint added

### Modified: users
| Change | Detail |
|--------|--------|
| Column added | `email_verified_at TIMESTAMP` |
| Column added | `login_count INT DEFAULT 0` |
| Constraint added | `UNIQUE (email)` |

### Migration SQL
```sql
ALTER TABLE users ADD COLUMN email_verified_at TIMESTAMP;
ALTER TABLE users ADD COLUMN login_count INT DEFAULT 0;
ALTER TABLE users ADD CONSTRAINT users_email_unique UNIQUE (email);
```

Security and Privacy

Because the SchemaLens CLI is zero-dependency and runs entirely in your CI environment, your schema never leaves your infrastructure. There's no API key, no cloud service, no data upload. The diff happens inside your runner.

For teams in regulated industries (healthcare, finance), this is often a requirement. You get automated schema review without adding a new vendor to your security questionnaire.

Scaling Beyond One Repository

Once schema diffing works in one repo, you can scale it:

Try It Now

The CLI is already in your repository if you're using SchemaLens. Copy the workflow files above, adjust the dialect and schema path, and open your next pull request. You'll have automated schema review in under ten minutes.

Start diffing in CI today

Download the SchemaLens CLI or open the browser app to validate your schemas before adding them to CI.

Open SchemaLens

Summary

SchemaLens is a browser-based SQL schema diff tool with a zero-dependency CLI for CI/CD. No signup, no upload, no backend. Your schema never leaves your machine. Try it free →