Running Laravel Migrations in Production Without Downtime

Production database migrations need care — locked tables, long ALTERs, and mismatched code can cause outages. Here's a practical playbook for safe Laravel migrations.

Richard GamoraRichard GamoraFullstack developer·5 min read
LaravelMigrationsDevOpsProduction

Migrations in development are forgiving — drop the database, re-run, move on. In production, a single ALTER TABLE on a 10 million row table can take down your site for minutes. Over the years I have built a small playbook for safe migrations. Here it is.

Avoid the dangerous operations on big tables

Adding a NOT NULL column with a default value, renaming a column, and adding indexes can all lock a MySQL table for the full migration. On small tables, this is fine. On large ones, it is an outage.

  • Add new columns as NULLABLE first, then backfill in a separate job, then make NOT NULL.
  • Use online schema change tools (pt-online-schema-change, gh-ost) for big tables.
  • Postgres handles many of these better than MySQL — verify your engine's behavior.

Deploy code and migrations in the right order

If your code expects a new column that does not exist yet, requests will fail during the deploy window. The fix is to release in two phases: first ship code that tolerates the old schema, then run the migration, then ship code that uses the new schema.

It is more deploys, but each one is reversible. A single deploy that combines both is fast in the happy path and painful when anything goes wrong.

Always test the rollback

Every migration must have a working down() method, and you should test it locally before shipping. Laravel's migrate:rollback only works if down() actually reverses what up() did. A migration with empty down() is a one-way door — fine if you mean it, dangerous if you don't.

Run migrations before, not during, the deploy

If you run php artisan migrate as part of the deploy script and it fails halfway through, you are now in a half-migrated state with new code already on some servers. Run migrations as a separate, observed step. Most deploy tools support this — Forge, Envoyer, GitHub Actions all let you split the migrate step from the code release.

The shared theme: small, reversible, observable changes. Production migrations are not the place for cleverness. They are the place for boring, safe, verified procedure.

About the author

Richard Gamora

Richard Gamora

Fullstack developer based in the Philippines, working mostly with Laravel and Vue.js, with eight years of production experience across web and mobile.

me@richardgamora.comUpwork ↗

More on Laravel