atlas-migrations
Authors and runs Atlas database schema migrations - declarative HCL or SQL schema definition with `atlas schema apply` for desired-state apply OR `atlas migrate diff` to generate versioned migrations against a dev DB; `atlas migrate apply` to deploy; `atlas migrate lint` to flag destructive / locking / data-loss patterns; `atlas migrate hash` to detect tampering. Supports PostgreSQL, MySQL, SQL Server, ClickHouse, SQLite, MariaDB, Snowflake, Oracle, Redshift, Spanner, CockroachDB, Databricks. Use when the user wants Terraform-style declarative DB schema management or modern SQL-first migration linting beyond Flyway / Liquibase.
Install with skills.sh (any agent)
npx skills add testland/qa --skill atlas-migrationsatlas-migrations
Overview
Atlas manages and migrates database schemas as code - define the desired schema and Atlas plans, lints, tests, and applies the changes (per atlasgo.io/getting-started (opens in new window)). Two operating modes:
Most production teams use versioned mode for the audit trail; dev loops often use declarative mode for fast iteration.
How to use
Follow Steps 1-9 in order - lint (Step 6) is the gate before apply (Step 5).
Step 1 - Install
Per at-start (opens in new window):
# download the install script, then run it
curl -sSf https://atlasgo.sh -o install-atlas.sh
sh install-atlas.sh
# Homebrew
brew install ariga/tap/atlas
# Direct binary download (per platform):
# atlasbinaries.com/atlas/atlas-{linux-amd64,linux-arm64,
# darwin-amd64,darwin-arm64,
# windows-amd64}-latestStep 2 - Define schema
Per at-start (opens in new window), schema can be SQL (most common) or HCL. SQL example (PostgreSQL):
-- schema.sql
CREATE TABLE users (
id serial PRIMARY KEY,
name varchar(255) NOT NULL,
email varchar(255) UNIQUE NOT NULL,
created_at timestamptz DEFAULT now()
);For the same schema expressed in Atlas's HCL DSL and guidance on when HCL vs SQL matters, see references/hcl-schema.md.
The dev DB referenced via --dev-url is a temporary scratch database Atlas uses to compute the diff (Atlas applies the desired state to the scratch DB, then computes the migration vs production).
Step 3 - Apply schema (declarative mode)
Per at-start (opens in new window):
atlas schema apply --url "$DATABASE_URL" --to file://schema.sql \
--dev-url "docker://postgres/17/dev?search_path=public"--url = target DB; --to = desired schema definition; --dev-url = scratch DB for diff computation. Atlas prints the planned changes and prompts for confirmation by default.
Step 4 - Generate versioned migrations
Per at-start (opens in new window):
atlas migrate diff initial --to file://schema.sql \
--dev-url "docker://postgres/17/dev?search_path=public"This generates a timestamped migration file in migrations/ (e.g., 20260506120000_initial.sql). Subsequent calls with a new schema generate new migrations against the previous state.
Step 5 - Apply versioned migrations
Verify (gate before applying): run Step 6 lint first and assert it reports no destructive / data-loss findings; if it flags one, fix the migration (add a DEFAULT, split the change, or add a new migration) and re-run lint until clean. Only then apply:
atlas migrate apply --url "$DATABASE_URL"Atlas tracks state in atlas_schema_revisions table - each applied migration is recorded with its hash, so tampering is detected on subsequent applies.
Step 6 - Lint migrations
Per at-start (opens in new window):
atlas migrate lint --dev-url "..." --latest 1Lints the most recent migration for: data loss, narrow-column operations (e.g., varchar(50) → varchar(20)), missing default on a NOT NULL add, lock-escalating ops on large tables, schema backwards-incompatibility.
The lint output is the value-add over Flyway / Liquibase - those tools are syntax-only; Atlas knows about destructive patterns.
Step 7 - Hash + integrity
Atlas computes hashes for each migration file. If a developer edits an already-applied migration, atlas migrate apply fails until atlas migrate hash re-syncs (intentional act). This protects against silent migration tampering.
Step 8 - CI integration
- uses: ariga/setup-atlas@v0
- name: Lint migrations
run: atlas migrate lint --dir "file://migrations" \
--dev-url "docker://postgres/17/dev?search_path=public" \
--latest 1
- name: Apply migrations to staging
run: atlas migrate apply --url "${{ secrets.STAGING_DB_URL }}"GitHub Action ariga/setup-atlas@v0 provides Atlas in the runner; the atlas-action orchestrator provides PR-comment integration with diff visualization.
Step 9 - Composition with sister tools
Beyond atlas migrate lint, apply adversarial review with team-specific risk policies that Atlas's built-in lint doesn't capture (e.g., "no DROP TABLE without DBA approval").
Worked example
Add a NOT NULL column status to a 40M-row users table:
Anti-patterns and limitations
The failure modes (editing applied migrations, skipping --dev-url, declarative schema apply straight to production, skipping lint in CI, blind atlas migrate hash) and the tool's limitations (partial HCL for some DBMS features, general-purpose lint rules, --dev-url requiring a real DBMS, paid Atlas Cloud) are catalogued in references/atlas-caveats.md.
References
Atlas migration anti-patterns and limitations
View source (opens in new window)Atlas migration anti-patterns and limitations
Reference catalogue for the atlas-migrations skill. Each anti-pattern maps to a fix; each limitation names a fallback.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Edit a migration after it's been applied to staging/prod | atlas migrate apply fails on hash mismatch | Add a new migration that adjusts |
Skip --dev-url | Atlas can't compute diff; commands fail or produce wrong output | Always pass --dev-url (Steps 3, 4, 6) |
Use declarative schema apply directly to production | No audit trail of applied changes | Use versioned mode in production (Step 4) |
Skip atlas migrate lint in CI | Destructive migrations slip through review | Always lint in CI (Step 8) |
atlas migrate hash after every edit (without team review) | Defeats integrity check | Hash sync only after intentional edit + team review |
Limitations
HCL schema definition (Atlas DSL)
View source (opens in new window)HCL schema definition (Atlas DSL)
Atlas accepts the desired schema as SQL (most common) or as HCL, Atlas's own schema DSL. Both are desired-state inputs to the same atlas schema apply and atlas migrate diff commands (Steps 3-4), passed via --to file://<schema-file>.
HCL example
The users table from Step 2's SQL example, expressed in HCL:
table "users" {
schema = schema.public
column "id" {
null = false
type = serial
}
column "email" {
null = false
type = varchar(255)
}
primary_key { columns = [column.id] }
index "unique_email" { unique = true; columns = [column.email] }
}HCL vs SQL - which to author
Either representation produces the same versioned migrations once diffed against the --dev-url scratch DB.
Related skills
flyway-migrations
Authors and runs Flyway database migrations - versioned (`V1__add_users.sql`), repeatable (`R__refresh_views.sql`), and undo (`U1__remove_users.sql`) migration files in `db/migration/`; runs `flyway migrate` / `info` / `validate` / `clean` / `baseline` / `repair`; tracks state in the `flyway_schema_history` table; supports 50+ databases including Oracle / SQL Server / MySQL / PostgreSQL / MariaDB / Snowflake / BigQuery; integrates with Maven, Gradle, CLI, and Docker. Use when the user works with Flyway-managed schemas, asks about migration ordering, or needs CI gates on schema changes.
liquibase-migrations
Authors and runs Liquibase database migrations - changelog-driven schema management with changesets in XML / YAML / JSON / SQL formats; supports `liquibase update` / `status` / `rollback` / `tag` / `history` lifecycle; offers per-changeset preconditions, contexts and labels for selective execution, and rollback semantics; tracks state in `DATABASECHANGELOG` + `DATABASECHANGELOGLOCK` tables. Use when the user works with Liquibase-managed schemas (Spring Boot heritage, polyglot DB shops), needs cross-DBMS portable migrations, or requires fine-grained rollback control.
migration-operation-taxonomy
Classifies every DDL and DML statement in a database migration into an eight-category operation taxonomy (additive, backwards-compatible alter, locking, lock-escalating, breaking, data-loss, unsafe default, index-missing foreign key) and assigns a Critical, Warning, or Info severity justified by the lock mode and table-rewrite behavior the target engine actually performs. Records where PostgreSQL and MySQL/InnoDB diverge for the same logical statement, and where behavior is version-gated (PostgreSQL 11 removed the table rewrite for a constant DEFAULT; MySQL 8.0.12 made ADD COLUMN instant). Use when a migration file appears in a diff or a review queue and someone must decide, before it reaches a production-sized table, which statements are safe and which will stall writes or destroy data.
sqlmesh-migrations
Authors and runs SQLMesh - data-transformation framework with version control, virtual data environments, automatic breaking-vs-non-breaking change classification, and downstream impact analysis; supports `sqlmesh init` / `plan` / `apply` / `run` / `audit` / `test` lifecycle; covers DuckDB, Postgres, Snowflake, BigQuery, Redshift, Databricks. Use when the user works with SQL data pipelines (warehouse + dbt-adjacent ELT), needs safer model evolution than dbt's deploy-and-pray, or wants the strongest impact-analysis story in the OSS data tooling space.