How do I roll back a bad deploy without losing data?
Run kamal rollback to boot the previous image, which is still on the server. It takes seconds because nothing is rebuilt or pulled. Your code goes back; your database does not. A deploy that ran a destructive migration cannot be undone this way, which is why additive migrations matter more than the rollback command.
The command
bin/kamal app containers # find the previous version tag
bin/kamal rollback <version>
curl -sI https://yourdomain.com/up | head -1
kamal app containers lists what is on the host, including stopped containers from previous
releases. The version is the git SHA the image was built from, and the image name it matches comes
from config/deploy.yml.
What rolls back and what does not
Your application code rolls back completely. The container is the unit, so gems, assets and configuration all return to the previous state together.
Your data does not. The SQLite file lives on a mounted volume precisely so that it survives container swaps, which is the behavior you want every day except this one. If the bad deploy ran a migration that dropped a column or rewrote rows, rolling the code back leaves the old code looking at a schema it does not expect.
Write migrations that can be rolled back
This is the real answer, and it is a habit rather than a command. Split anything destructive into two deploys:
- Deploy one: add the new column, write to both, read from the old one. Nothing is removed.
- Deploy two: read from the new column, then remove the old one, once deploy one has been live long enough that you would not roll back past it.
Between those two deploys, kamal rollback is safe, because the schema supports both versions of
the code. This is the only reliable way to keep a rollback honest, and it costs one extra deploy.
Check what the migration actually did
bin/kamal app exec 'bin/rails db:migrate:status' | tail -20
That lists which migrations have run on the server, which is the thing to look at before deciding
whether a rollback is enough or whether you need to write a corrective migration instead. Compare it
against db/schema.rb in the commit you are rolling back to: if the two disagree about a column,
the rollback needs a migration in front of it.