Escaping the SQLite Migration Maze: Why I Switched from Atlas to Goose
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 withCGO_ENABLED=1
and tagsqlite_omit_load_extension
- โ Internal API limitations โ
main.go
clashed withversion.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
- Validated SQL syntax โ Passed
atlas migrate validate
successfully. - Cleaned state โ Deleted the DB,
.atlas/
, andatlas.sum
repeatedly. - Built Atlas from source โ Tried v0.14.0 and v0.14.1. Still crashed.
- Used Docker image โ Even
arigaio/atlas:latest
gave the same panic. - Pinned a stable version โ Install script ignored my requested version and gave me a crashing canary build anyway.
- 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.