Found a Good Database Migration Tool: Atlas

Waken 2023-12-02

Discovered Atlas today. It’s a database schema management tool that actually makes sense.

What It Does

Instead of writing manual migration files, you declare your desired schema and Atlas figures out the migrations.

// schema.hcl
table "users" {
  column "id" {
    type = int
    auto_increment = true
  }

  column "email" {
    type = varchar(255)
    null = false
  }

  primary_key {
    columns = [column.id]
  }

  index "idx_email" {
    columns = [column.email]
    unique = true
  }
}

Then:

atlas schema apply \
  -u "mysql://user:pass@localhost:3306/mydb" \
  --to "file://schema.hcl"

It calculates the diff and applies it.

Why I Like It

1. Declarative

Don’t need to remember what migrations you’ve written. Just define the end state.

2. Automatic Diff Generation

atlas migrate diff add_email_column \
  --dir "file://migrations" \
  --to "file://schema.hcl" \
  --dev-url "docker://mysql/8/dev"

Generates the SQL for you.

3. Built-in Lint

atlas migrate lint \
  --dir "file://migrations" \
  --dev-url "docker://mysql/8/dev"

Catches stuff like:

  • Dropping columns (destructive!)
  • Adding NOT NULL without defaults
  • Missing indexes on foreign keys

4. Works with ORMs

# Generate schema from GORM models
atlas migrate diff \
  --dir "file://migrations" \
  --to "ent://models" \
  --dev-url "docker://mysql/8/dev"

No more manual sync between code and database schema.

Basic Workflow

# 1. Install
brew install ariga/tap/atlas

# 2. Define schema (schema.hcl or use existing SQL)

# 3. Generate migration
atlas migrate diff initial \
  --dir "file://migrations" \
  --to "file://schema.hcl" \
  --dev-url "docker://mysql/8/dev"

# 4. Apply to database
atlas migrate apply \
  --dir "file://migrations" \
  --url "mysql://user:pass@localhost:3306/mydb"

Multi-Environment Config

Create atlas.hcl:

env "local" {
  src = "file://schema.hcl"
  url = "mysql://root:pass@localhost:3306/dev"
  dev = "docker://mysql/8/dev"
}

env "production" {
  src = "file://schema.hcl"
  url = env("DATABASE_URL")
  dev = "docker://mysql/8/dev"

  lint {
    destructive {
      error = true  // Block destructive changes
    }
  }
}

Then: atlas migrate apply --env production

CI/CD Integration

GitHub Actions example:

- name: Lint migrations
  run: |
    atlas migrate lint \
      --dir "file://migrations" \
      --dev-url "docker://mysql/8/test"

- name: Apply to staging
  run: |
    atlas migrate apply \
      --dir "file://migrations" \
      --url "$"

Catches bad migrations before they hit production.

Comparison

vs Flyway/Liquibase:

  • More modern
  • Better error messages
  • Supports declarative schemas

vs golang-migrate:

  • Auto-generates migrations
  • Built-in safety checks
  • ORM integration

vs Alembic (Python):

  • Language-agnostic
  • Better multi-database support

What I’m Using It For

Managing schema changes across dev/staging/prod. No more “did we run this migration?” or “what’s the current schema state?”

Atlas keeps everything in sync.

One gotcha: Needs a “dev database” for diff calculation. Just use Docker:

--dev-url "docker://mysql/8/dev"

It spins up a temporary container automatically.

Worth checking out if you’re tired of manual database migrations.