Skip to content
AI Engineering
AI Engineering11 min read0 views

Schema Migrations with Prisma + Atlas: Safe Postgres Changes for AI Apps (2026)

Prisma owns the schema model, Atlas owns the migration plan, lint, and CI/CD. Together you get declarative schema, automatic diff plans, and zero-downtime production deploys for AI-heavy Postgres.

TL;DR — Prisma's built-in migrations work for solo devs; Atlas handles the production-grade pipeline (linting, CI checks, RLS, triggers, declarative drift detection). Run Prisma for the schema, Atlas for the plan and apply.

What you'll build

A repo where schema.prisma is the single source of truth, Atlas auto-generates SQL migrations, lint rules block dangerous changes (DROP COLUMN without IF EXISTS, missing CONCURRENTLY, etc.), and GitHub Actions applies them safely.

Schema

// schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model Conversation {
  id        BigInt   @id @default(autoincrement())
  tenantId  String   @map("tenant_id") @db.Uuid
  agentId   String   @map("agent_id")  @db.Uuid
  body      String
  embedding Unsupported("vector(1536)")?
  createdAt DateTime @default(now()) @map("created_at")
  @@map("conversations")
  @@index([tenantId, createdAt(sort: Desc)])
}

Architecture

flowchart LR
  DEV[Edit schema.prisma] --> DIFF[atlas migrate diff]
  DIFF --> SQL[Generated SQL]
  SQL --> LINT[atlas migrate lint]
  LINT --> CI[GitHub Actions]
  CI --> STAGE[Staging apply]
  STAGE --> PROD[Production apply<br/>CONCURRENTLY-safe]

Step 1 — Wire Atlas to Prisma

# atlas.hcl
data "external_schema" "prisma" {
  program = ["npx", "prisma", "migrate", "diff", "--from-empty", "--to-schema-datamodel", "prisma/schema.prisma", "--script"]
}

env "local" {
  src = data.external_schema.prisma.url
  url = "postgres://postgres:postgres@localhost:5432/dev"
  dev = "docker://postgres/17/dev"
  migration { dir = "file://migrations" }
}

Step 2 — Generate the next migration

atlas migrate diff add_embedding_index --env local

Output:

-- migrations/20260507142100_add_embedding_index.sql
CREATE INDEX CONCURRENTLY IF NOT EXISTS conversations_embedding_hnsw
  ON conversations USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);

Step 3 — Lint dangerous changes

atlas migrate lint --env local --latest 1

Atlas catches:

Hear it before you finish reading

Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.

Try Live Demo →
  • DROP COLUMN on non-empty tables without backfill
  • ALTER TYPE that locks the table
  • Missing CONCURRENTLY on big-table indexes
  • Backwards-incompatible RENAME

Step 4 — Apply in CI

# .github/workflows/migrate.yml
- uses: ariga/atlas-action/migrate/lint@v1
  with:
    dir: 'file://migrations'
    dev-url: 'docker://postgres/17/dev'
- uses: ariga/atlas-action/migrate/apply@v1
  with:
    url: ${{ secrets.DATABASE_URL }}
    dir: 'file://migrations'

Step 5 — Manage RLS / triggers declaratively

Atlas understands RLS, triggers, functions — Prisma doesn't. Add a schema.hcl for those:

policy "tenant_isolation" {
  on        = table.conversations
  as        = "PERMISSIVE"
  for       = "ALL"
  using     = "tenant_id = current_setting('app.tenant_id', true)::uuid"
  with_check = "tenant_id = current_setting('app.tenant_id', true)::uuid"
}

Step 6 — Zero-downtime patterns

For renames or splits, use the expand/contract pattern:

  1. Expand — add new column, dual-write app code.
  2. Backfill — async job copies data.
  3. Contract — drop old column once new column is canonical.

Each step is a separate Atlas migration.

Pitfalls

  • Manual edits to migration SQL — break Atlas's understanding of schema state. Prefer regenerating.
  • Skipping lint — production crashes from one missing CONCURRENTLY are Friday-night classics.
  • Drift between Prisma and DBatlas schema inspect catches it before deploy.
  • Long-running migrations in transactions — Postgres holds locks. Split into separate concurrent steps.

CallSphere production note

CallSphere ships every schema change through Prisma + Atlas across 115+ DB tables. Healthcare (Prisma healthcare_voice) gets a separate atlas env so HIPAA migrations are reviewed independently; OneRoof's RLS policies live in schema.hcl; UrackIT's Supabase migrations sync via the same pipeline. 37 agents · 90+ tools · 6 verticals, never a Friday rollback. Plans: $149/$499/$1,499 — 14-day trial, 22% affiliate.

Still reading? Stop comparing — try CallSphere live.

CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.

FAQ

Q: Atlas or just Prisma migrate? Atlas if you need lint, RLS/triggers, or CI/CD guards. Prisma migrate is fine for personal projects.

Q: Drizzle migrations? Drizzle has its own kit, similar shape to Atlas; ecosystem narrower for advanced lint.

Q: Can Atlas manage extensions? Yes — CREATE EXTENSION in schema.hcl is supported.

Q: Rollback strategy? Atlas supports down migrations; in practice prefer forward-only with expand/contract.

Q: How fast are large migrations? CREATE INDEX CONCURRENTLY on 100M rows: 30-90 min. Plan during low-write windows.

Sources

Share

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.

Related Articles You May Like