Escaping the SQLite Migration Maze: Why I Switched from Atlas to Goose

April 30, 2025
gosqliteatlasgoosedatabasemigrationsclidevtoolsdebugging

Escaping the SQLite Migration Maze: Why I Switched from Atlas to Goose

Recently, I set out to implement SQL-based database migrations for my Go-based personal website. What should have been a 15-minute task turned into a multi-hour expedition through CLI bugs, broken binaries, panic messages, and poor documentation. This is a breakdown of what went wrong with Atlas, what I tried to fix it, and why I ultimately switched to using Goose for my SQLite migrations.


๐Ÿงญ Where It All Began: golang-migrate

I actually started this journey with golang-migrate, a Go-native CLI for applying SQL migrations. It seemed ideal โ€” just one binary, no YAML, and direct SQLite support.

I wired it into my deployment flow using:

migrate -database "sqlite3://./data/shanahjr.db" -path ./migrations up

But very quickly I ran into problems:

  • โŒ SQLite driver errors โ€” "unknown driver sqlite3 (forgotten import?)"
  • โŒ Required CGO โ€” had to manually compile the migrate binary with CGO_ENABLED=1 and tag sqlite_omit_load_extension
  • โŒ Internal API limitations โ€” main.go clashed with version.go when I tried to build from source
  • โŒ Permissions hell โ€” running migrations as a systemd user required sudo and full binary paths

After days of hacking together builds and still hitting runtime errors, I decided to abandon it in favor of something that didn't fight me every step of the way.


๐Ÿงฑ The Original Plan: Use Atlas for Migrations

Atlas is a modern schema-as-code migration tool that looked perfect on paper. It supports SQL migrations, powerful linting, and automation tooling. I had already seen its install script recommended everywhere:

curl -sSf https://atlasgo.sh | sh

After installing it, I created my migration files and set up the Makefile like so:

migrate-up:
    atlas migrate apply --dir "file://./migrations" --url "sqlite://./data/shanahjr.db"

The directory structure was clean. The .sql files were valid. Everything should have worked.


๐Ÿ’ฅ The Crash

But every time I ran:

make migrate-up

Atlas would panic with this Go runtime error:

panic: runtime error: index out of range [0] with length 0

No helpful error message. No logs. Just a full-blown crash from inside the tool itself.


๐Ÿงช What I Tried

  1. Validated SQL syntax โ€” Passed atlas migrate validate successfully.
  2. Cleaned state โ€” Deleted the DB, .atlas/, and atlas.sum repeatedly.
  3. Built Atlas from source โ€” Tried v0.14.0 and v0.14.1. Still crashed.
  4. Used Docker image โ€” Even arigaio/atlas:latest gave the same panic.
  5. Pinned a stable version โ€” Install script ignored my requested version and gave me a crashing canary build anyway.
  6. Manual debugging โ€” I even traced through the source code on GitHub to see where the panic occurred (some internal slice was empty).

At this point I had wasted hours just trying to get one migration to apply in SQLite.


๐Ÿ•Š๏ธ The Escape Plan: Switch to Goose

I finally decided to abandon Atlas for this project and try Goose, a battle-tested Go migration tool that's:

  • Lightweight
  • Easy to install
  • SQLite-friendly
  • SQL-first (but supports Go too)

Installation was simple:

go install github.com/pressly/goose/v3/cmd/goose@latest

Then I created a migration:

goose -dir migrations create create_posts_table sql

Each migration file supports -- +goose Up and -- +goose Down sections:

-- +goose Up
CREATE TABLE posts (...);

-- +goose Down
DROP TABLE posts;

Running migrations was effortless:

goose -dir migrations sqlite3 ./data/shanahjr.db up

โœ… No crashes.
โœ… No mysterious state files.
โœ… No Docker needed.
โœ… Just working SQL.


๐Ÿ”ง My Final Setup

I updated my Makefile to support:

migrate-up:
    goose -dir migrations sqlite3 $(DB_PATH) up

migrate-down:
    goose -dir migrations sqlite3 $(DB_PATH) down

reset-db:
    rm -f $(DB_PATH)
    goose -dir migrations sqlite3 $(DB_PATH) up

create-migration:
    @if [ -z "$(name)" ]; then         echo "Usage: make create-migration name=your_name";     else         goose -dir migrations create $(name) sql;     fi

migrate-status:
    goose -dir migrations sqlite3 $(DB_PATH) status

.env:

DB_PATH=./data/shanahjr.db

Now everything is portable, version-controlled, and crash-free.


๐Ÿšซ Why Not Atlas?

To be clear, Atlas is a powerful tool โ€” especially for teams using Postgres or MySQL in cloud environments. But:

  • For SQLite-based personal projects?
  • On a Go CLI that crashes with no fallback?
  • With an install script that silently installs unstable builds?

Atlas is currently a no-go for me.


โœ… Summary

If you're using SQLite and Go, skip the drama and go straight to Goose. You'll:

  • Save time
  • Avoid crashes
  • Own your migrations

...and keep your sanity intact.


Got questions or want to see my setup? Check out the GitHub repo or drop me a message.