- Published on
Database Schema Migrations 2026 Deep Dive - Atlas (Ariga) · Flyway · Liquibase · Bytebase · dbmate · golang-migrate · Sqitch · Knex · Prisma Migrate
- Authors

- Name
- Youngju Kim
- @fjvbn20031
Intro — Why Migrations Are Still a Mess in May 2026
Database schema migrations have been an unsolved problem for more than a decade. Code lives in Git with PR review; schema does not — it remains opaque about who changed what, when, and why. A one-line ALTER TABLE can hold a four-hour lock and tank a service. A misplaced index can fill a disk overnight. A single foreign key can break replication on a slave.
By May 2026 this problem has split into two clear branches. The first is the rise of declarative schema tooling. Atlas, pgroll, and Reshape let you declare the desired final state and let the tool produce a safe, ordered diff. The second is the mainstreaming of DB branching. Neon, PlanetScale, Supabase, and Atlas Cloud now hand out isolated preview DBs per PR so you can validate migrations against real data shapes.
This article does not push a single answer. It maps every category of tool in production use as of May 2026, and then tells you which combination fits which workload.
Why Migrations Are Hard — The Real List
Code deployment and schema migration are fundamentally different. With code you spin up the new container, shift traffic, kill the old one. Schema does not work that way.
- Statefulness: the database carries state. A bad migration is data loss.
- Locks: ALTER TABLE acquires table or metadata locks. Adding a column to a 10 GB table can take four hours.
- Compatibility: the old code and the new code see the same DB at the same time. Drop a column and the old container crashes.
- Replication lag: large DDL on MySQL async replication or Postgres streaming replication is brutal.
- Rollback: code rolls back to the previous image. After DROP COLUMN, the column is gone.
- Multi-region: Southeast Asia, US, and EU regions must converge on the same schema.
The goal of any 2026 migration tool is to absorb these constraints into automation.
The Lineup at a Glance — May 2026
| Category | Tool | Primary language | Core model |
|---|---|---|---|
| Declarative | Atlas (Ariga) | Go | HCL/SQL diff |
| Declarative | pgroll | Go | Postgres multi-step |
| Declarative | Reshape | Rust | Postgres multi-step |
| Imperative SQL | Flyway 10 | Java | Versioned SQL |
| Imperative SQL | Liquibase 4.x | Java | XML/YAML changeset |
| Imperative SQL | dbmate | Go | CLI only |
| Imperative SQL | golang-migrate | Go | Library + CLI |
| Imperative SQL | Sqitch | Perl | Git-style |
| Platform | Bytebase | Go | DBA workflow |
| Platform | Atlas Cloud | Go | SaaS |
| ORM bundle | Prisma Migrate | TypeScript | Schema → SQL |
| ORM bundle | Drizzle Kit | TypeScript | TS → SQL diff |
| ORM bundle | TypeORM | TypeScript | Decorator diff |
| ORM bundle | Knex.js | JS | Builder migrations |
| ORM bundle | Sequelize | JS | Model diff |
| ORM bundle | Alembic | Python | SQLAlchemy diff |
| ORM bundle | Django | Python | Model → migration |
| ORM bundle | Rails AR | Ruby | DSL DDL |
| ORM bundle | Phoenix Ecto | Elixir | DSL DDL |
| ORM bundle | Diesel | Rust | SQL up/down |
| ORM bundle | GORM | Go | AutoMigrate |
| ORM bundle | EF Core | C# | Model → migration |
| Online change | gh-ost | Go | MySQL triggerless |
| Online change | pt-osc | Perl | MySQL triggers |
The grid is built from actual production adoption signals, not vendor marketing pages.
Atlas (Ariga.io) — Modern Declarative + GitOps
Atlas was open-sourced by Ariga in 2022, and as of May 2026 it is the fastest-growing migration platform. Three pillars define it.
- Declarative HCL or SQL: declare the desired final schema, Atlas produces the diff.
- Imperative migrations too: existing Flyway-style versioned SQL files are first-class.
- CI/CD integration: GitHub Actions, GitLab CI, and CircleCI plugins. Each PR gets a visual diff.
A Postgres schema declared in HCL looks like this.
schema "public" {}
table "users" {
schema = schema.public
column "id" {
type = bigint
null = false
}
column "email" {
type = varchar(255)
null = false
}
column "created_at" {
type = timestamptz
null = false
default = sql("now()")
}
primary_key {
columns = [column.id]
}
index "users_email_idx" {
columns = [column.email]
unique = true
}
}
Computing a diff with the CLI looks like this.
atlas schema diff \
--from "postgres://user:pass@localhost:5432/app?sslmode=disable" \
--to "file://schema.hcl" \
--dev-url "docker://postgres/15/dev"
atlas migrate diff add_users \
--dir "file://migrations" \
--to "file://schema.hcl" \
--dev-url "docker://postgres/15/dev"
Atlas's killer feature is the dev-url container. Migrations are applied inside an ephemeral container to compute diffs, so the result does not depend on the state of your local DB. atlas migrate lint adds static analysis on top: it flags DROP COLUMN, NOT NULL additions, and large backfills at PR time so they never reach production unreviewed.
Atlas Cloud wraps this in a SaaS with a visual schema explorer, automated backfills, RBAC, and audit logs.
Flyway 10 — The De Facto Standard in JVM Land
Flyway has been the default in the Spring Boot world since the early 2010s. As of May 2026 the 10.x line ships with the following characteristics.
- SQL-first: V1__create_users.sql, V2__add_email_index.sql, and so on.
- Java migrations when dynamic data transformations are required.
- Checksum validation: applied migrations are immutable; modifications fail fast.
- Flyway Hub: remote metadata and team collaboration.
- 70+ DBMS support: Postgres, MySQL, Oracle, SQL Server, Snowflake, BigQuery, and more.
A canonical Flyway workflow looks like this.
mkdir -p db/migration
cat > db/migration/V1__create_users.sql <<'SQL'
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
SQL
flyway -url=jdbc:postgresql://localhost:5432/app \
-user=app -password=secret \
-locations=filesystem:db/migration migrate
Flyway 10's true strength is Spring Boot integration. Drop flyway-core into a Spring Boot project and migrations run automatically on startup. For Java/Kotlin backends this is essentially the default choice.
The weakness is the absence of a declarative mode. You always write explicit ALTER TABLE statements, and synchronizing across environments needs separate tooling. More teams pair Flyway with Atlas to cover the declarative gap.
Liquibase 4.x — The XML/YAML Champion
Liquibase abstracts a layer above raw SQL. Instead of writing SQL directly, you describe changes as changesets. XML, YAML, JSON, and SQL formats are all supported.
databaseChangeLog:
- changeSet:
id: 1
author: youngju
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: bigint
autoIncrement: true
constraints:
primaryKey: true
- column:
name: email
type: varchar(255)
constraints:
nullable: false
unique: true
- column:
name: created_at
type: timestamptz
defaultValueComputed: NOW()
constraints:
nullable: false
Liquibase's advantages include the following.
- DB-agnostic: the same changeset runs on Postgres, MySQL, Oracle, and SQL Server.
- Auto-generated rollback: many change types include automatic rollback SQL.
- Pro features: policy checks, data migration, Snowflake/Redshift integration.
- Liquibase Hub: a cloud dashboard and change history.
The downside is verbosity. Adding a single column requires ten lines of YAML. The 4.x line has strengthened the SQL format in response, moving closer to Flyway in style.
Bytebase — DBA + Migration Platform
Bytebase is an open-source DBA platform that launched in 2022. Where Liquibase and Flyway are "tools," Bytebase is a "platform." Key features include the following.
- SQL Review: 100+ rule sets. Blocks NOT NULL additions, warns on missing indexes, detects large UPDATEs.
- PR-based workflow: GitHub, GitLab, and Bitbucket integration. Submit a SQL file as a PR and Bytebase reviews it.
- RBAC: separate DBA, developer, and release manager roles.
- Multi-stage deployment: dev → staging → production pipelines.
- GitOps mode: the Git repo is the source of truth; Bytebase applies changes automatically.
- AI SQL Review: GA in late 2025. Generative AI flags risky patterns and proposes improvements.
Bytebase runs as a single binary.
docker run --rm --init \
--name bytebase \
--publish 8080:8080 \
--volume ~/.bytebase/data:/var/opt/bytebase \
bytebase/bytebase:latest
Korean DBA teams at Coupang, Toss, and NCsoft have reported Bytebase adoption. In Japan, Mercari and LINE Yahoo are running internal evaluations.
dbmate — A Language-Agnostic CLI
dbmate, built by amacneil, is a small CLI tool. It is written in Go and ships as a single binary. The guiding philosophy is "tools should be simple."
- Language-agnostic: usable from Node, Python, Ruby, or Go.
- Multi-DB: Postgres, MySQL, SQLite, and ClickHouse.
- Plain SQL files: separators are -- migrate:up / -- migrate:down comments.
- State tracking: applied migrations live in a
schema_migrationstable.
dbmate new create_users
# creates db/migrations/20260516120000_create_users.sql
dbmate up
dbmate down
dbmate dump # current schema → db/schema.sql
A generated migration file looks like this.
-- migrate:up
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- migrate:down
DROP TABLE users;
dbmate is explicit and easy to debug. It is a common pick for ORM-free Go and Node backends.
golang-migrate — Near-Standard Library Status in Go
golang-migrate is the de facto standard migration tool in the Go ecosystem. It can be embedded as a library or run as a CLI.
migrate create -ext sql -dir db/migrations -seq create_users
migrate -path db/migrations -database "postgres://localhost:5432/app?sslmode=disable" up
Embedded from Go code it looks like this.
package main
import (
"github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
func main() {
m, err := migrate.New(
"file://db/migrations",
"postgres://user:pass@localhost:5432/app?sslmode=disable",
)
if err != nil { panic(err) }
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
panic(err)
}
}
golang-migrate is simple but has sharp edges. Migrations apply one at a time by default, and transaction handling is explicit. Outside of Postgres it can feel non-idiomatic.
Sqitch — The Origin of Git-Style Migrations
Sqitch is a Perl tool whose core idea is that "migrations form a dependency graph, like Git commits."
- No version numbers: migrations are identified by name.
- Dependencies: declared explicitly, e.g.
sqitch add appusers --requires base. deploy.sql,verify.sql,revert.sql: three files form one set.
sqitch init flipr --uri https://github.com/example/flipr --engine pg
sqitch add appusers --requires base -n "Creates appusers schema"
sqitch deploy db:pg://localhost/flipr
sqitch verify db:pg://localhost/flipr
sqitch revert db:pg://localhost/flipr
Sqitch's user base is small, but its design has been influential. Atlas's serialization model partially echoes Sqitch.
Prisma Migrate 6 — ORM-Bundled for TypeScript
Prisma is the default ORM for the TypeScript backend. Declare your models in schema.prisma, and prisma migrate dev generates and applies SQL.
model User {
id Int @id @default(autoincrement())
email String @unique
createdAt DateTime @default(now()) @map("created_at")
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
authorId Int @map("author_id")
author User @relation(fields: [authorId], references: [id])
}
The workflow looks like this.
npx prisma migrate dev --name init
npx prisma migrate deploy # production
npx prisma migrate resolve --rolled-back 20260516120000_init
Prisma Migrate 6's strength is type safety with schema.prisma as a single source of truth. The weaknesses are these.
- Horizontal scaling is weak: multi-schema and multi-tenant remain limited.
- Complex migrations require manual SQL: it is common to run
prisma migrate diff, then hand-edit the SQL.
Drizzle Kit — TypeScript-Native and Close to SQL
Drizzle ORM grew fast in 2024 and 2025. Drizzle Kit is its migration tool.
import { pgTable, serial, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: serial("id").primaryKey(),
email: text("email").notNull().unique(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
The workflow looks like this.
npx drizzle-kit generate # TS schema diff → SQL
npx drizzle-kit migrate # apply
npx drizzle-kit studio # visualization
Drizzle Kit is closer to SQL than Prisma. The generated SQL is readable and survives subsequent generate runs even if you edit it by hand. Drizzle is particularly strong in serverless and edge environments (Cloudflare Workers, Vercel Edge).
Knex.js — Builder + Migrations
Knex.js is a SQL query builder, but it also ships with migrations. Express and Fastify teams that go without an ORM often pick it.
exports.up = async function(knex) {
await knex.schema.createTable("users", (t) => {
t.bigIncrements("id").primary();
t.string("email", 255).notNullable().unique();
t.timestamp("created_at").notNullable().defaultTo(knex.fn.now());
});
};
exports.down = async function(knex) {
await knex.schema.dropTable("users");
};
The strength of Knex migrations is the DB-agnostic builder. The same code runs against Postgres, MySQL, SQLite, and MS SQL Server. The weakness is leaky abstraction: any DB-specific feature ends up in knex.raw().
Sequelize CLI — The JS ORM Classic
Sequelize is the classic Node.js ORM. Sequelize CLI handles migrations.
npx sequelize-cli migration:generate --name create-users
The generated file has up and down functions.
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Users", {
id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
email: { type: Sequelize.STRING(255), allowNull: false, unique: true },
createdAt: { type: Sequelize.DATE, allowNull: false },
updatedAt: { type: Sequelize.DATE, allowNull: false },
});
},
down: async (queryInterface) => {
await queryInterface.dropTable("Users");
},
};
Sequelize is not where greenfield projects start anymore, but it is heavily embedded in legacy codebases.
Alembic — The Python SQLAlchemy Standard
Alembic, built by Mike Bayer (the author of SQLAlchemy), is the de facto migration tool for Python backends outside Django.
alembic init alembic
alembic revision --autogenerate -m "create users"
alembic upgrade head
alembic downgrade -1
A generated migration script looks like this.
from alembic import op
import sqlalchemy as sa
revision = "1a2b3c4d5e6f"
down_revision = None
def upgrade() -> None:
op.create_table(
"users",
sa.Column("id", sa.BigInteger, primary_key=True),
sa.Column("email", sa.String(255), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.UniqueConstraint("email", name="users_email_key"),
)
def downgrade() -> None:
op.drop_table("users")
Alembic's --autogenerate compares SQLAlchemy models with the DB to produce a diff. Powerful but imperfect — always review generated scripts. With FastAPI + SQLAlchemy, Alembic is essentially required.
Django Migrations — Generated From Models
Django integrates the ORM and migrations completely. Edit models.py, run makemigrations, and a migration file is produced; migrate applies it.
from django.db import models
class User(models.Model):
email = models.EmailField(unique=True)
created_at = models.DateTimeField(auto_now_add=True)
python manage.py makemigrations
python manage.py migrate
python manage.py migrate --plan # preview pending changes
python manage.py sqlmigrate myapp 0001 # print SQL
Django migrations are powerful but have a few traps.
- RunPython: data migrations must be written explicitly.
- AtomicMigration: Postgres runs DDL inside a transaction; large backfills should set
atomic = False. - squashmigrations: collapses accumulated migrations.
Django shops use Django migrations almost universally. Plugging Alembic on top is rare.
Rails Active Record Migrations — The Most Influential DSL
Rails set the migration DSL standard back in 2005, and nearly every tool since has been influenced by it.
class CreateUsers < ActiveRecord::Migration[7.1]
def change
create_table :users do |t|
t.string :email, null: false
t.timestamps
end
add_index :users, :email, unique: true
end
end
rails generate migration CreateUsers email:string
rails db:migrate
rails db:rollback STEP=1
The strength is the change method: write it once and Rails infers both up and down. The weakness is the strong convention — non-standard changes must be expressed explicitly with up and down.
Phoenix Ecto Migrations — Elixir's Functional DSL
Elixir and Phoenix use Ecto. Ecto.Migration exposes a DSL that resembles Rails AR.
defmodule MyApp.Repo.Migrations.CreateUsers do
use Ecto.Migration
def change do
create table(:users) do
add :email, :string, null: false
timestamps()
end
create unique_index(:users, [:email])
end
end
mix ecto.gen.migration create_users
mix ecto.migrate
mix ecto.rollback
BEAM's runtime and Elixir's functional style produce particularly clean migration code. Multi-tenant setups are handled via dynamic Repo configuration.
Diesel Migrations — Rust's Compile-Time Migrations
Diesel is Rust's ORM and query builder. Its migration tool ships alongside the ORM.
diesel migration generate create_users
# creates migrations/<timestamp>_create_users/{up.sql,down.sql}
diesel migration run
diesel migration revert
-- up.sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- down.sql
DROP TABLE users;
Diesel verifies schemas and queries at compile time. diesel print-schema generates Rust types automatically, and if queries do not match those types, compilation fails.
Sea-ORM is an alternative to Diesel — async-first, with the migration DSL in Rust code.
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.create_table(Table::create()
.table(Users::Table)
.col(ColumnDef::new(Users::Id).big_integer().not_null().auto_increment().primary_key())
.col(ColumnDef::new(Users::Email).string().not_null().unique_key())
.to_owned()).await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.drop_table(Table::drop().table(Users::Table).to_owned()).await
}
}
EF Core Migrations — The .NET Standard
Entity Framework Core is the default ORM in the .NET ecosystem. Migrations are managed via the dotnet ef CLI.
dotnet ef migrations add CreateUsers
dotnet ef database update
dotnet ef migrations script
dotnet ef migrations remove
A generated migration is C# code.
public partial class CreateUsers : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<long>(nullable: false).Annotation("SqlServer:Identity", "1, 1"),
Email = table.Column<string>(maxLength: 255, nullable: false),
CreatedAt = table.Column<DateTime>(nullable: false, defaultValueSql: "GETUTCDATE()")
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
table.UniqueConstraint("UQ_Users_Email", x => x.Email);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(name: "Users");
}
}
EF Core supports SQL Server, Postgres (Npgsql), MySQL (Pomelo), and SQLite well.
GORM AutoMigrate — Go ORM's Automatic Mode
Go's GORM offers a simple API named AutoMigrate.
type User struct {
ID uint `gorm:"primarykey"`
Email string `gorm:"uniqueIndex;size:255;not null"`
CreatedAt time.Time
}
db.AutoMigrate(&User{})
AutoMigrate is fast but leaves a lot out. It does not perform column drops or type changes safely. So even GORM users typically pair it with golang-migrate or Atlas in production.
Online Schema Changes — gh-ost, pt-online-schema-change, pgroll, Reshape
ALTER TABLE on large tables is a lock bomb. Several tools work around it.
gh-ost (GitHub Online Schema Transmogrifier) is GitHub's trigger-free MySQL tool. It tails the binlog and builds a shadow table.
gh-ost \
--max-load=Threads_running=25 \
--critical-load=Threads_running=1000 \
--chunk-size=1000 \
--max-lag-millis=1500 \
--user="ghost" --password="secret" \
--host=replica.example.com \
--database="myapp" \
--table="users" \
--alter="ADD COLUMN nickname VARCHAR(64)" \
--switch-to-rbr --allow-master-master --cut-over=default \
--exact-rowcount --concurrent-rowcount --default-retries=120 \
--panic-flag-file=/tmp/ghost.panic.flag \
--postpone-cut-over-flag-file=/tmp/ghost.postpone.flag \
--execute
pt-online-schema-change (Percona Toolkit) is trigger-based but has more than a decade of battle testing.
pt-online-schema-change \
--alter "ADD COLUMN nickname VARCHAR(64)" \
D=myapp,t=users \
--execute
pgroll (Xata) is Postgres's answer to zero-downtime schema change. It exposes the same schema in two versions concurrently (via views) so applications can switch traffic gradually.
pgroll start migrations/001_add_nickname.json
pgroll complete # finalize after traffic shift
pgroll rollback # revert if needed
Reshape is a Rust tool with similar ideas. Both productize the Expand-Contract pattern.
pg_repack is not a migration tool but rebuilds tables and indexes lock-free after ALTERs.
Expand-Contract — The Default Pattern for Zero-Downtime
Most zero-downtime migrations follow the Expand-Contract pattern.
- Expand: add the new column or table. Old code and new code both work.
- Backfill: copy data from the old column to the new in batches.
- Dual write: new code writes to both. Roll out gradually.
- Read switch: shift reads to the new column. Canary the rollout.
- Contract: remove the old column.
Where code deployment is one step, schema change is five. The core value of pgroll, Reshape, and Atlas Cloud is that they automate this pattern.
DB Branching — Neon, PlanetScale, Supabase, Atlas Cloud
DB branching became mainstream after 2025. Each PR gets an isolated branch and migrations are validated against real data.
- Neon: Postgres copy-on-write branches. A new branch in seconds.
- PlanetScale: MySQL/Vitess. Branches are the unit of schema change.
- Supabase Branching: per-PR preview DBs with migrations auto-applied.
- Atlas Cloud: branch previews for any DB managed by Atlas.
Branch DBs are the most powerful tool for verifying migration safety. Running migrations on an anonymized copy of production data before merging has become the de facto standard.
Schema Visualization — dbdiagram.io, DrawSQL, Mermaid ER
Schema is at its most legible when visualized. The leading tools as of May 2026 are these.
- dbdiagram.io: a DBML text DSL that generates ER diagrams.
- DrawSQL: a collaborative ER designer with simultaneous editing.
- Mermaid erDiagram: embeds straight into a GitHub README.
- DBeaver, DataGrip: IDE-class clients that produce ER diagrams automatically.
- SchemaSpy: generates static HTML schema documentation.
- DBdocs: hosted documentation backed by dbdiagram.
- eraser.io: a diagrams + notes + markdown collaboration tool.
These tools do not apply migrations themselves, but they make the before/after view of a change easy to review in a PR.
Migration Testing — testcontainers, schemathesis
Migrations are testable too. The standard tooling in May 2026 looks like this.
- testcontainers: spins up a real Postgres or MySQL container per integration test. Apply migrations, then run queries.
- schemathesis: auto-generates tests from an OpenAPI schema to validate API + DB consistency.
- Atlas migrate lint: static safety analysis on migrations.
- Bytebase SQL Review: a 100+ rule static checker.
In Go with testcontainers it looks like this.
ctx := context.Background()
pgC, err := postgres.RunContainer(ctx,
testcontainers.WithImage("postgres:15-alpine"),
postgres.WithDatabase("test"),
postgres.WithUsername("test"),
postgres.WithPassword("test"),
)
defer pgC.Terminate(ctx)
dsn, _ := pgC.ConnectionString(ctx, "sslmode=disable")
m, _ := migrate.New("file://./migrations", dsn)
require.NoError(t, m.Up())
Multi-DB Migration — Postgres, MySQL, SQLite, MS SQL Server
When you need to support several engines at once, compatibility matters. The following table shows where each tool stands.
| Tool | Postgres | MySQL | SQLite | MS SQL | Oracle |
|---|---|---|---|---|---|
| Atlas | full | full | full | Cloud-only | partial |
| Flyway 10 | full | full | full | full | full |
| Liquibase 4.x | full | full | full | full | full |
| Bytebase | full | full | full | full | full |
| dbmate | full | full | full | partial | no |
| golang-migrate | full | full | full | full | full |
| Prisma | full | full | full | full | partial |
| Alembic | full | full | full | full | full |
The stronger the DB-agnostic abstraction, the weaker the exposure of engine-specific features. To use Postgres-only features (GENERATED ALWAYS AS, partitioning, BRIN indexes) you frequently drop down to raw SQL.
CI/CD Integration — GitHub Actions, GitLab CI, ArgoCD
Migrations should be first-class citizens in CI/CD.
A GitHub Actions workflow with Atlas looks like this.
name: Migrate
on:
push:
branches: [main]
jobs:
migrate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ariga/setup-atlas@v0
- run: |
atlas migrate apply \
--dir "file://migrations" \
--url "${{ secrets.DATABASE_URL }}" \
--tx-mode all
Running Flyway as an ArgoCD Hook is another common pattern. A PreSync hook applies the migration before the new container ships.
apiVersion: batch/v1
kind: Job
metadata:
name: flyway-migrate
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: flyway
image: flyway/flyway:10
args: ["migrate"]
envFrom:
- secretRef:
name: flyway-config
AI-Augmented Migrations — Bytebase Copilot, Atlas Copilot
Since 2025 migration tools have started absorbing AI features.
- Bytebase SQL Review AI: generative AI explains risky migration patterns in Korean and English.
- Atlas Copilot: generates HCL schemas from natural language requirements and narrates SQL diffs.
- CodeRabbit, Greptile: PR reviewers that include migration-safety checks.
AI delivers value in two places. The first is natural-language risk explanation: "this ALTER may hold a four-hour lock on a 100M-row table." The second is pattern recommendation: refactor suggestions that nudge a one-step ALTER into an Expand-Contract sequence.
Korean Practice — NCsoft, Coupang, NAVER
Among Korean tech companies the patterns look like this.
- NCsoft: gh-ost is widely used across the MySQL clusters that power game backends.
- Coupang: Flyway or Liquibase per microservice; internal PR workflows have been augmented with Bytebase evaluations.
- NAVER: large MySQL/MyRocks footprint with internal DBA tooling combined with pt-online-schema-change.
- Kakao: Postgres + Liquibase workflow; some teams are evaluating Atlas.
- Toss: an internal RDS operations team runs a homegrown GitOps pipeline with Flyway.
Broadly speaking, Korea adopts Korean-friendly SaaS like Bytebase more aggressively than Japan.
Japanese Practice — Mercari, LINE Yahoo, CyberAgent, DeNA
Large Japanese services show the following patterns.
- Mercari: reports of evaluating and adopting Atlas and golang-migrate; main backend is Go.
- LINE Yahoo: after the merger, multiple migration tools coexist — Flyway, Liquibase, and golang-migrate mix in production.
- CyberAgent: gh-ost is the dominant approach for MySQL behind game backends.
- DeNA: long history of large-scale MySQL going back to Mobage, combining gh-ost with internal tooling.
- SmartHR: Rails + Active Record Migrations as the default, with some Liquibase usage.
Japan's Go ecosystem and MySQL operational depth make gh-ost adoption particularly fast.
Recommended Combinations — Best Fit by Workload
A final per-workload cheat sheet.
- Spring Boot + a single Postgres/MySQL: Flyway 10. No real alternative.
- Go backend + multiple DBs: Atlas as the declarative front, golang-migrate as the engine.
- TypeScript + serverless/edge: Drizzle Kit.
- TypeScript + traditional backend: Prisma Migrate 6 or Drizzle Kit.
- Python FastAPI: Alembic.
- Django: Django Migrations, plus pgroll where needed.
- Rails: Active Record Migrations.
- .NET: EF Core Migrations.
- Large MySQL: the above + gh-ost.
- Large Postgres: the above + pgroll or Reshape.
- Multi-team with strong governance: introduce Bytebase as a platform.
- Heavily GitOps-driven workflow: Atlas Cloud + GitHub Actions.
Closing — Tools Diverge, Patterns Converge
As of May 2026 migration tools have split along ecosystem lines. JVM lives on Flyway, .NET on EF Core, Python on Alembic + Django, TypeScript on Prisma/Drizzle, Go on golang-migrate/Atlas.
But the patterns converge. Whatever tool you pick, the same principles apply.
- Every change goes through PR and review.
- Migrations are verified in automated CI.
- Large changes are decomposed into Expand-Contract.
- Online schema change tools avoid lock-induced outages.
- A rollback plan ships with every migration.
- DB branches validate against real data.
Tools are means to satisfy these principles at lower cost. Pick the one that fits your workload and team, but do not relax the principles.
References
- Atlas official documentation (Ariga.io)
- Atlas GitHub
- Flyway official documentation
- Flyway GitHub
- Liquibase official documentation
- Liquibase GitHub
- Bytebase official documentation
- Bytebase GitHub
- dbmate GitHub
- golang-migrate GitHub
- Sqitch official site
- Prisma Migrate documentation
- Drizzle Kit documentation
- TypeORM Migrations documentation
- Knex.js Migrations documentation
- Sequelize CLI documentation
- Alembic official documentation
- Django Migrations documentation
- Rails Active Record Migrations guide
- Phoenix Ecto Migrations documentation
- Diesel Migrations documentation
- Sea-ORM Migrate documentation
- EF Core Migrations documentation
- GORM AutoMigrate documentation
- gh-ost GitHub
- pt-online-schema-change documentation
- pgroll GitHub (Xata)
- Reshape GitHub
- pg_repack GitHub
- Neon Branching documentation
- PlanetScale Branching documentation
- Supabase Branching documentation
- dbdiagram.io
- DrawSQL
- Mermaid erDiagram documentation
- SchemaSpy
- testcontainers Go
- schemathesis documentation