database schema drift detection with atlas

Block deployments on drifted databases. Detect out-of-band changes within minutes.

What it covers

Drift detection, check by check

Database schema drift occurs when a live database no longer matches the schema its migrations define because changes were made outside of Atlas. Atlas can detect drift at various points in time and names the out-of-place objects that are found.

Before a deployment

A pre-apply check runs at the start of every migration, compares the target against the expected state at the last applied revision, and aborts the deployment before any migration file executes.

Pre-Apply Check

On a schedule

The same comparison runs outside a deployment, from a cron job or a CI step. It never changes the database, and it exits non-zero when the database has drifted. Its report carries a fingerprint of the drifted state that stays stable while the drift is unchanged, a key for deduplicating alerts.

Background Check

Between deployments

Agent-based monitoring periodically compares a live database against a deployed migration target or another schema, and reports every change it finds.

Schema Monitoring

Alerting

Slack notifications and webhooks fire when the monitor detects drift, with the details attached. The scheduled background check exits non-zero, so a cron job or CI step alerts through whatever already watches it.

Drift Notifications

Reporting

Every drifted object is listed with the DDL that reproduces the change, classified as extra, missing, or modified. The Cloud view adds an ERD and HCL or SQL diffs.

Drift Observability

Intentional deviations

Instruct Atlas to ignore objects that live outside the migration scope, such as manually installed extensions, audit tables owned by another service, or a schema owned by another team.

Excluding Objects
See the setup path
The Problem

Schema drift is the silent killer of versioned migrations

Versioned migrations are fast because they only track which files have already run, then execute the next one in order. They never re-inspect the database, never replan, never diff. By the time a migration breaks, drift has been compounding for weeks.

Trust without verificationMigration runners only track which files have run, then execute the next one blindly. They never re-inspect the database itself, so drift is invisible until something breaks.
Every shortcut bypasses reviewEmergency hotfixes, manual ALTERs, ORM auto-migrators, second-tool writers. Each one lands without a PR or a CI run.
Half-migrated productionA migration written against an expected state hits a different reality, fails halfway, and leaves a database nobody knows how to recover.
Chain of custody breaksSOC 2, ISO 27002, PCI DSS, and HIPAA all require a record of every change. Drift is, by definition, an unrecorded change.
deploy@prod-postgres
HALF-MIGRATED
$ tail -n 4 /var/log/postgresql/ddl.log
03:41:12[admin]ALTER TABLE orders DROP COLUMN legacy_id;
03:42:38[deploy_bot]GRANT ALL ON *.* TO analytics_role;
03:43:51[unknown]ALTER ROLE superuser WITH SUPERUSER;
04:01:07[admin]CREATE TABLE users_history (id int);
$ pg_dump --schema-only prod | diff migrations/state.sql -
--- migrations/state.sql (expected)
+++ prod (actual)
@@ -0,0 +1,3 @@
+CREATE TABLE "users_history" (
+ "id" integer
+);
# 6 hours later, the scheduled deploy runs...
$ ./deploy.sh
[migrator] applying version 20260423130000
-> ALTER TABLE users ADD COLUMN email text;
-> CREATE TABLE users_history (id integer NOT NULL);
ERROR: relation "users_history" already exists
[migrator] aborted mid-batch · database left in partial state
How Atlas solves this
Pre-apply drift check

Verify before every migration runs

Before applying any migration files, Atlas makes sure your database wasn't silently changed outside of migrations, a manual ALTER, an emergency hotfix, an ORM auto-migration. If it was, the deploy is aborted and the exact diff is shown so you know what drifted.

Synchronous pre-execution gateAtlas inspects the database itself, before the migration runner touches a single file. The gate is a real-time check, not a separate scan job.
Single-block setupEnabled with one block of configuration. No agents to deploy, no separate services to operate, no schedule to maintain.
Per-environment policiesHard-fail in production, advisory in staging, off in local. Switch behavior based on the environment without forking the config.
atlas migrate apply
drift detected
migrate to v20260423130000against postgres://prod-db
3 changes
Pre-execution checks
drift against v20260423120000
atlas.hcl:10 · drift detected
3.2ms
What drifted
@@ -14 +14 @@ "orders"
14
- "legacy_id" varchar(64),
@@ -42,0 +43,3 @@
43
+CREATE TABLE "users_history" (
44
+ "id" bigint NOT NULL
45
+);
@@ -78,0 +79,1 @@
79
+GRANT ALL ON SCHEMA public TO "analytics_role";
Migration safely aborted
No SQL was executed against the database. Revert the change, or capture it in a new migration.
Continuous Drift Monitoring

See drift the moment it happens

The pre-apply check guards the next migration. Continuous monitoring is the radar between deploys, so an out-of-band change surfaces in seconds, not after the next failed deploy.

Periodic inspectionScans each database on the schedule you set, hourly, every five minutes, or as often as the environment needs.
Alerts where your team livesSlack, webhooks, in-product. The moment an unauthorized change lands in the database, your on-call channel hears about it.
Full diff and audit historyEvery event keeps the SQL/HCL diff and ERD in a timeline, so "when did this start" is one click away.
#acme-oncall
Atlas Bot2:45 PM
⚠️Drift detected on prod-postgres

Production database schema differs from expected state at v20260423120000.

schemaTable users_history added outside migration
columnColumn orders.legacy_id removed in production
grantGRANT ALL ON SCHEMA public TO analytics_role
Shift Left. Watch Right

Preventive and detective controls, in one workflow

The pre-apply check is the safety belt at deploy time. Continuous monitoring is the radar between deploys. Together they cover the full window in which drift can do damage.

One week in production
MonTueWedThuFri

The two highlighted moments show each control catching drift.

deploy v23Mon 09:14Pre-apply check passed
deploy v24 BLOCKEDWed 11:08Drift from Tue not yet remediated
drift remediatedWed 14:21Migration written, GRANT folded back
deploy v24Thu 10:02Pre-apply check passed, clean apply
drift detectedFri 15:47New table created out-of-band

Every change in production is either authorized by review, or flagged within minutes.

SOC 2ISO 27002PCI DSSHIPAA
vs Liquibase & Flyway

What other tools miss

Atlas covers drift across three dimensions where Liquibase and Flyway leave gaps.

01

Built-in, two ways

Drift detection runs as a pre-apply check inside the migration pipeline and as a continuous monitoring layer between deploys.

vs. Liquibase and Flyway ship a drift report only. Separate command, no monitoring layer.

02

Every schema object

Every schema change and every security change. From tables, columns, and views to users, roles, and permissions.

vs. Limited to the schema changes each tool models natively. Security changes (users, roles, grants, RLS) fall outside the diff.

03

Every supported driver

PostgreSQL, MySQL, SQL Server, MariaDB, Oracle, SQLite, ClickHouse, Snowflake, Redshift, Spanner, Databricks, CockroachDB, Aurora DSQL, Azure Fabric, and more.

vs. Coverage varies per database. Many drivers limited or unsupported.

Getting started

From a live database to detected drift

Three steps, no schema rewrite. Atlas reads the database and migration directory you already have, and the check starts guarding the next deployment.

01

Point Atlas at the database

One env block per real environment. The migration directory lives in the Atlas Registry, which supplies the expected state for each applied version that the check compares against.

atlas.hcl
env "prod" { url = env("DATABASE_URL") migration { dir = "atlas://my-app" } }
02

Block a drifted deployment

A check "migrate_apply" block runs the drift check before any migration file. On an existing environment, start with on_error = CONTINUE so the first run reports pre-existing drift in the apply transcript without failing the deploy, then switch to FAIL.

atlas.hcl
check "migrate_apply" { drift { on_error = FAIL } }
03

Check between deployments

atlas migrate drift runs the same comparison outside a deployment and exits non-zero on drift, so a cron job or a CI step fails without parsing the output. Migration files after the applied version are pending, not drift, and the report counts them as ignored.

terminal
$ atlas migrate drift --env prod Drift Status: DRIFTED -- Current Version: 20260423120000 -- Expected State: atlas://my-app (registry) -- Changes: 1 (1 extra) -- extra table "audit_log": -> CREATE TABLE "audit_log" ("id" integer NULL);
Drift Detection Documentation
Block drift at deploy. Detect it between deploys.