Question
Rails Database Tasks Explained: db:migrate vs db:reset vs db:schema:load
Question
In Rails, I understand the difference between rake db:migrate and rake db:reset, but I am not sure how rake db:schema:load differs from them.
Here is my current understanding:
rake db:migrate
Runs any migrations that have not been run yet.
rake db:reset
Clears the database and rebuilds it from scratch, which I assume is similar to:
rake db:drop db:create db:migrate
How is rake db:schema:load different from these two tasks, and when should it be used?
Short Answer
By the end of this page, you will understand what each Rails database task does, how they differ, and when to use each one. You will also learn why db:schema:load is usually faster than replaying every migration, and why db:reset does not simply rerun all migrations in the way many beginners expect.
Concept
Rails provides several database tasks because developers need to manage databases in different situations.
The three tasks in this question solve different problems:
db:migrateupdates an existing database by applying only the migrations that have not run yet.db:schema:loadcreates the database structure directly from the current schema file.db:resetdrops the database, recreates it, and then loads the current schema and seeds.
What db:migrate does
When you run:
rake db:migrate
Rails looks at your migration files and checks which ones have already been applied by reading the schema_migrations table in the database.
It then runs only the missing migrations.
This is useful when:
- you add a new migration
- your app is already running
- you want to evolve the database safely over time
What db:schema:load does
When you run:
rake db:schema:load
Rails does not run your migration files one by one.
Instead, it reads the schema file:
db/schema.rbfor the Ruby schema format, ordb/structure.sqlfor the SQL schema format
Then it creates all tables, columns, indexes, and other supported structure directly from that file.
This is often much faster than replaying a long migration history.
What db:reset does
When you run:
rake db:reset
Rails drops the database, recreates it, loads the schema, and runs seeds.
Conceptually, it is closer to:
rake db:drop db:create db:schema:load db:seed
not:
rake db:drop db:create db:migrate
That difference is important.
Why this matters
In real projects, migrations describe how the database changed over time, while the schema file describes what the database should look like right now.
That means:
- use migrations to move an existing database forward
- use the schema file to quickly build a fresh database in its current state
This distinction matters when:
- onboarding new developers
- rebuilding local development databases
- setting up CI test databases
- recovering from broken local data
- maintaining apps with many old migrations
A key practical idea is this:
- Migrations are history
- Schema is the current blueprint
Mental Model
Think of your database like a house.
db:migrateis like applying renovation instructions one by one: add a wall, remove a window, paint a room.db:schema:loadis like opening the latest blueprint and building the house exactly as it should look today.db:resetis like bulldozing the house, rebuilding it from the latest blueprint, and then furnishing it with starter items from seeds.
This is why db:schema:load is faster on a fresh database: it skips the entire renovation history and just uses the final design.
Another way to think about it:
- migrations = step-by-step journal
- schema = snapshot
- reset = destroy everything and rebuild from the snapshot
Syntax and Examples
Core commands
rake db:migrate
rake db:schema:load
rake db:reset
In newer Rails versions, you will often see bin/rails instead of rake:
bin/rails db:migrate
bin/rails db:schema:load
bin/rails db:reset
Example migration
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :name
t.string :email
t.timestamps
end
end
end
If this migration has not been run yet, db:migrate will create the users table.
After migrations run, Rails updates db/schema.rb to reflect the current structure.
Example schema file
A simplified db/schema.rb might look like this:
ActiveRecord::Schema[7.0].define(version: 20240101010101) do
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
If you run db:schema:load, Rails reads this file and creates the users table directly.
Example seed file
User.create!(name: "Admin", email: "admin@example.com")
When you run db:reset, Rails rebuilds the database and then runs the seed file, so this record gets inserted.
Quick example of behavior
Suppose your app has 150 migration files.
db:migrateruns only the new ones that have not been applied yetdb:schema:loadignores those 150 files and builds the final structure fromschema.rbdb:resetdestroys the database and recreates it using the schema and seeds
Step by Step Execution
Consider this simplified project state:
- Migration 1 creates
users - Migration 2 adds
emailtousers - Migration 3 creates
posts db/schema.rbalready reflects all of these changes
Using db:migrate
rake db:migrate
Step by step:
- Rails checks the
schema_migrationstable. - It sees which migration versions have already run.
- It executes only the missing migration files.
- It updates the database structure.
- It updates
db/schema.rb.
If only Migration 3 is missing, Rails runs only Migration 3.
Using db:schema:load
rake db:schema:load
Step by step:
- Rails opens
db/schema.rb. - It reads the current database structure definition.
- It creates tables, columns, and indexes directly from that file.
- It does not replay Migration 1, 2, and 3 one by one.
This is best for a fresh database when you only need the final structure.
Using db:reset
rake db:reset
Step by step:
- Rails drops the existing database.
- Rails creates a new empty database.
- Rails loads the current schema.
- Rails runs the seed file.
Any existing data is lost.
Small trace example
Suppose your local database currently has test data you no longer want.
If you run:
rake db:reset
Then:
- all tables are removed
- the database is recreated
- the schema is loaded back in
- seed data is inserted again
If you run:
rake db:migrate
Then:
- your existing data stays
- only new schema changes are applied
Real World Use Cases
When to use db:migrate
Use it when you are changing an existing application database.
Examples:
- adding a new column like
statusto anorderstable - creating a new table for
comments - adding an index to improve query performance
- deploying a new app version with database changes
When to use db:schema:load
Use it when you need a fresh database with the current structure quickly.
Examples:
- setting up a new developer machine
- creating test databases in CI pipelines
- rebuilding a local database after corruption
- loading the latest schema without replaying years of old migrations
When to use db:reset
Use it when you want a complete clean start locally.
Examples:
- your development data is messy and you want to start over
- seed data has changed and you want to reload everything
- you want to recreate the database exactly as the app currently expects
Common practical rule
- existing database with real data ->
db:migrate - brand new empty database -> often
db:schema:load - throw away local database and rebuild ->
db:reset
Real Codebase Usage
In real Rails projects, developers usually treat these tasks as part of different workflows.
Common team patterns
Day-to-day development
Most of the time, developers run:
bin/rails db:migrate
This keeps their local database in sync with newly added migrations.
New machine setup
A fresh clone of the project often uses schema loading because it is faster than replaying every migration since the beginning of the app.
Test and CI environments
Automated environments often prefer loading the schema because:
- it is faster
- it is more predictable for a clean database
- it avoids failures caused by old migration code that no longer works well with current models
Important pattern: migrations should not depend on current app code
In real codebases, old migrations can break if they rely on application models that have changed.
For example, this can be risky inside a migration:
class BackfillUserNames < ActiveRecord::Migration[7.0]
def up
User.find_each do |user|
user.update!(name: user.email.split('@').first)
end
end
end
Why risky?
- the
Usermodel may change later - validations may be added later
- callbacks may run differently later
If someone builds a fresh database using migrations years later, this migration might fail.
Loading the schema avoids replaying that historical logic.
Seeds vs migrations
Another common real-project pattern:
- migrations define structure
- seeds define starter data
So db:reset is useful when a team wants both a clean structure and known starter records.
Guard-clause mindset
Developers generally avoid destructive tasks in production unless absolutely necessary.
db:migrateis normal and incrementaldb:schema:loadand especiallydb:resetare more destructive in the sense that they are used for fresh databases or disposable environments
Common Mistakes
Mistake 1: Thinking db:reset runs every migration
Many beginners assume:
rake db:reset
is equivalent to:
rake db:drop db:create db:migrate
Usually, the closer mental model is:
rake db:drop db:create db:schema:load db:seed
How to avoid this mistake:
- remember that reset rebuilds from the current schema snapshot
- do not rely on it to test your full migration history
Mistake 2: Using db:reset when you only need db:migrate
Broken workflow:
# unnecessary and destructive
rake db:reset
If you only added a migration, this destroys all local data.
Better:
rake db:migrate
Mistake 3: Assuming db:schema:load preserves data
It is for building structure, not preserving existing data in a working database.
If you need to update an existing database safely, use migrations.
Mistake 4: Confusing schema with seed data
db:schema:load creates tables and indexes, but it does not automatically insert your sample or starter records.
That is the job of seeds.
Mistake 5: Writing fragile migrations
Problematic migration:
class UpdateUsers < ActiveRecord::Migration[7.0]
def up
User.all.each do |user|
user.update!(active: true)
end
end
end
This may fail later if the User model changes.
Safer approaches include:
- using SQL directly when appropriate
- defining temporary migration-specific models
- keeping migrations independent from evolving app logic
Mistake 6: Forgetting that schema format has limits
schema.rb represents Rails-supported structure, but some database-specific features may require structure.sql.
How to avoid this mistake:
- know which schema format your project uses
- do not assume every advanced database feature is fully represented in
schema.rb
Comparisons
Quick comparison table
| Task | What it uses | Best for | Keeps existing data? | Runs old migrations? |
|---|---|---|---|---|
db:migrate | Migration files | Updating an existing database | Usually yes | Only pending ones |
db:schema:load | db/schema.rb or db/structure.sql | Building a fresh database quickly | No, typically used for fresh setup | No |
db:reset | Drop/create + schema load + seeds | Rebuilding from scratch | No | No |
db:migrate vs db:schema:load
db:migrate | db:schema:load |
|---|---|
| Replays missing migration steps | Builds the final structure directly |
| Good for existing databases | Good for fresh databases |
| Preserves existing data when possible | Usually used when starting from empty |
| Can be slower with long migration history | Usually faster |
db:reset vs db:schema:load
db:reset | db:schema:load |
|---|---|
| Drops and recreates the database | Loads schema into the database |
| Usually runs seeds | Does not imply seeds by itself |
| Good for a clean local rebuild | Good for loading structure only |
Simple rule of thumb
- Need to update what already exists? Use
db:migrate. - Need the latest structure on a new database? Use
db:schema:load. - Need to wipe everything and start over? Use
db:reset.
Cheat Sheet
# Apply only pending migrations
bin/rails db:migrate
# Build database structure from current schema file
bin/rails db:schema:load
# Drop, recreate, load schema, and seed
bin/rails db:reset
Remember
db:migrate= incremental changesdb:schema:load= load current structure snapshotdb:reset= rebuild from scratch
Important details
db:migratechecksschema_migrationsdb:schema:loaddoes not replay migration historydb:resetis closer todb:drop db:create db:schema:load db:seeddb:schema:loadis often faster for fresh databasesdb:migrateis safer for existing databases with real data
Edge cases
- If old migrations are broken,
db:schema:loadmay still work for fresh setup - If your project uses database-specific SQL features, it may use
db/structure.sqlinstead ofdb/schema.rb - Seeds are separate from schema structure
FAQ
Does db:reset run migrations in Rails?
Not in the usual step-by-step sense. It typically rebuilds the database from the current schema and then runs seeds.
Is db:schema:load faster than db:migrate?
For a fresh database, usually yes. It skips the full migration history and loads the final structure directly.
When should I use db:migrate instead of db:schema:load?
Use db:migrate when you already have a database with data and want to apply new changes safely.
Does db:schema:load insert seed data?
No. Schema loading creates structure, not starter records.
Can db:reset delete my data?
Yes. It drops the database and recreates it, so existing data is removed.
Why do Rails apps keep both migrations and a schema file?
Migrations record history, while the schema file represents the current database structure.
What if old migrations fail on a fresh setup?
That is one reason teams often use schema loading for fresh databases instead of replaying every old migration.
Should I use these commands in production?
db:migrate is common in production. Destructive tasks like db:reset should be used very carefully and usually not on production databases.
Mini Project
Description
Build a tiny Rails-style database workflow example to practice choosing the right command. The project simulates a team working on a blog app with users and posts, where you need to decide whether to migrate an existing database, load a fresh schema, or completely reset the database with seeds.
Goal
Understand which Rails database task to use in three common situations: updating an existing database, creating a fresh database quickly, and rebuilding a local database from scratch.
Requirements
- Create two example migration files: one for
usersand one forposts. - Add a simple
db/schema.rbthat reflects both tables. - Add a
db/seeds.rbfile that inserts at least one user. - Explain which command you would run for an existing database with new changes.
- Explain which command you would run for a brand new database.
- Explain which command you would run when you want to wipe local data and start over.
Keep learning
Related questions
Add a Default Value to an Existing Column in a Rails Migration
Learn how to add or change a default value on an existing database column using Rails Active Record migrations safely.
Calling a Class Method from an Instance in Ruby
Learn how to call a class method from an instance in Ruby using self.class, with examples, pitfalls, and practical usage patterns.
Calling an Overridden Monkey-Patched Method in Ruby
Learn how to call the original method when monkey patching in Ruby, including alias_method patterns, examples, pitfalls, and practical usage.