I’m planning to implement multi tenancy for my app using query prefixes. I won’t have many tenants (single digits, maybe low double digits), and I’ll be onboarding them gradually.
I want to keep some tables in the public schema, and the rest in per-tenant schemas. The docs do cover this specific use case, but don’t cover how to handle migrations.
When covering migrations elsewhere, it suggests running mix ecto.migrate --prefix "prefix_1", but that would create all tables inside the prefixed schema.
I have a couple ideas on how I could handle this, but I was hoping I could get a yay or nay from anyone who has done something similar?
Idea 1)
Add an if prefix() == "public" check to some of my migrations, and run mix ecto.migrate --prefix "prefix_1"? My concern is this approach is prone to human error.
Idea 2)
Create a seperate tenant_migrations folder. However, this involves adjusting my tasks and deployment.
For example, I’m using fly.io. It runs a release_command, that calls MyApp.Release.migrate() during deployment.
def migrate do
load_app()
for repo <- repos() do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, "PATH_TO_tenant_migrations", :up, all: true)) # something I'd add
end
end
This change assumes that tenant schemas can reference the public schema, but not the other way around, because migrations would run in order: public migrations first, then tenant migrations - rather than being interspersed.
And for local development, mix ecto.migrate won’t be sufficient as it would only run it for the ‘public’ schemas. I’ll need to create a new mix task to handle migrating (an augmentation of Mix.Tasks.Ecto.Migrate)
Idea 1 seems simpler and I suspect im over complicating it with Idea 2, but any opionions on these, or any better ideas, would be appreciated!






















