Your app is live and people are using it. Now the structure has to change: a new column on orders, a table that should be split in two, a field that should have existed from day one. The data those users already created is sitting in the database, so you cannot just reshape it and hope. This chapter gets you changing a live database's structure safely, the same controlled way every time, and undoing the change cleanly when it goes wrong.
10.5.1Every schema change is a versioned file
A migration is one change to your database's structure, written as a file and saved alongside your code. Add a column, rename a table, split one table into two: each becomes its own migration, numbered so the changes run in a fixed order.
That order is what makes them safe to trust. Your own machine, a test copy, and the live server all apply the same numbered files in the same sequence. Every one of them ends up with an identical structure.
Your agent writes these files and a migration tool applies them. Your stack almost certainly ships one already, Prisma Migrate in TypeScript, Alembic in Python, Rails and Laravel their own, with dbmate as the standalone option when yours has none. You never type the changes into the database yourself.
10.5.2Never edit the live database by hand
This is the one rule that keeps the whole system honest: never open the live database and change its structure by hand. Not a quick ALTER TABLE in a console, not a column added through a hosting dashboard.
A hand-edit exists in exactly one place, that single server. It is not in your code, so no one can review it and your test copy never receives it. The next time new code goes live, it has no idea the change ever happened. Structure changes only ever through a committed migration file, applied by the tool.
Watch out: The costly database disasters almost always start with a well-meaning manual fix on the live server. If it did not go through a migration, it lives on that one box alone, and it will collide with your next release.
10.5.3Roll forward and roll back
Every migration has two halves. The up half rolls the change forward, applying it; the down half rolls it back, undoing it. When a change turns out bad, the tool runs the down half and the structure returns to exactly where it was.
Your agent applies it with the tool's up command, dbmate up here, and reverses it with dbmate down, never by hand. The chapter on undoing a bad code change did this for your code; a migration does the same for your database's structure.
That one line is also the classic way to freeze a live app. On a big table, adding a NOT NULL column with a default can lock every read and write while it rewrites each row. Whether it does depends on your database and its version.
The safe shape there is three steps: add the column nullable, backfill in batches, then add the constraint. Ask your agent which of your migrations takes a lock, and never run one that does in the middle of a deploy.
One catch: some changes cannot be undone perfectly. Dropping a column throws its data away, so its down half cannot bring that data back. Make those one-way changes deliberately, and take a backup, a saved copy you can restore from, before you run them.
Hand the change to your agent with the rules already set:
Do this now: the next time your live app's structure has to change, paste the prompt with your change. Let your agent write the up-and-down migration instead of touching the database directly.