GUIDE

How to Set Up Automated Environment Scans

Catch drift early with CI checks and scheduled scans.

The SupaForge CLI is open source and on npm: npm install -g @akal/supaforge. A hosted dashboard is on the way - to hear when it opens.

Why Automate Scans?

A manual diff catches drift when someone remembers to run it. An automated one catches it the day it appears - especially in teams where changes are made in the Supabase dashboard as well as through migrations.

CI Mode

supaforge diff --ci is built for pipelines:

  • Annotations - each finding becomes a GitHub Actions error or warning, on stderr.
  • JSON - a machine-readable summary is the only thing on stdout, so supaforge diff --ci > report.json gives you a clean artifact.
  • Exit codes - 0 clean, 1 drift, 2 a check failed to run.
  • Threshold - --fail-on=critical (the default), warning or any.

Credentials

$VAR and ${VAR} in supaforge.config.json are read from the environment, so the config can be committed and the secrets supplied by CI:

supaforge.config.json
{
  "environments": {
    "staging": {
      "dbUrl": "$STAGING_DATABASE_URL",
      "projectRef": "STAGING_REF",
      "accessToken": "$SUPABASE_ACCESS_TOKEN"
    },
    "production": {
      "dbUrl": "$PRODUCTION_DATABASE_URL",
      "projectRef": "PROD_REF",
      "accessToken": "$SUPABASE_ACCESS_TOKEN"
    }
  },
  "source": "staging",
  "target": "production"
}

Option 1 - GitHub Actions

The SupaForge action runs diff --ci: findings become annotations, the job page gets a summary of the drift, and the step fails at your threshold.

.github/workflows/drift-check.yml
name: Drift Check
on:
  pull_request:
  schedule:
    - cron: '0 8 * * *'  # daily

permissions:
  contents: read

jobs:
  diff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: akalsoftware/supaforge-action@v1
        id: drift
        with:
          args: diff --ci --fail-on=warning
        env:
          STAGING_DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
          PRODUCTION_DATABASE_URL: ${{ secrets.PRODUCTION_DATABASE_URL }}
          SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: drift-report
          path: ${{ steps.drift.outputs.report }}

Its outputs are the exit code and the path of the JSON report; version pins the SupaForge release it runs.

Option 2 - GitLab CI

.gitlab-ci.yml
drift-check:
  image: node:22
  script:
    - npx @akal/supaforge diff --ci --fail-on=warning
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_PIPELINE_SOURCE == "schedule"

Option 3 - Cron + Webhook

Without CI, run it from cron and send the summary wherever your team looks:

crontab -e
# every 6 hours
0 */6 * * * cd /path/to/project && npx @akal/supaforge diff 2>&1 | curl -s -X POST --data-binary @- https://hooks.slack.com/services/YOUR/WEBHOOK/URL

Reading the Results

  • Drift score - out of 100, where 100 means the environments match.
  • Posture score - the target on its own: tables without RLS, migrations it hasn't applied. Scored separately, and only part of the exit code with --fail-on-posture.
  • Per-check status - drifted, clean, or skipped with the reason. A skipped check is never counted as clean.
  • Findings - each with a severity and its fix.

Best Practices

  • Diff in the direction changes flow - staging as the source, production as the target.
  • Start daily, then gate pull requests once the baseline is clean.
  • Scope it when you need speed - --check, --skip and --tables keep a pull-request check fast.
  • Pin the version - the action's version input, or @akal/supaforge in your devDependencies, so CI upgrades when you choose to.

Summary

With --ci, a drift check is one line in a pipeline: annotations for reviewers, JSON for tooling and an exit code that fails the build when the environments diverge.

Ready to try SupaForge?

Install the CLI and run your first diff in a minute. Nothing is changed until you add --apply.

npm install -g @akal/supaforge
View on GitHub →