Rollback on MIOSA is fast because nothing has to be rebuilt. The previous release is still in storage. Runtime instances boot quickly. Static artifacts are streamed from object storage. The whole operation is “change one row, propagate to the edge cache.”
When you’d rollback
- New version passed health checks but the app is broken in real traffic (e.g. a feature flag mis-fire).
- A bad migration deployed.
- A regression you missed in staging.
- Hostile incident — quick revert while you investigate.
How fast
| Step | Static | Dynamic |
|---|---|---|
| Update active version | <1s | <1s |
| Edge route cache refresh | <60s default | <60s default |
| New runtime instances spawn? | n/a (no per-app process) | Only if older version’s instances were torn down; the scheduler boots replacements |
| Drain old runtime instances | n/a | Only if rolling forward, not on rollback |
| Total wall clock | seconds | seconds to ~1 minute |
If the older version’s runtime instances are still healthy (recent rollforward where the old instances haven’t been torn down yet), dynamic rollback is also instant — the router switches backends without any VM boots.
How rollback works
API
Server behavior:
1. Validate target version_id belongs to this deployment
2. Validate target version state === "ready" (cannot rollback to failed)
3. Validate target version's release artifact still exists in storage
(returns 410 Gone if GC'd)
4. UPDATE deployments SET active_version_id = $version_id
5. Invalidate edge route cache
6. For dynamic: confirm enough healthy instances for the old version; spawn if needed
7. Return the deployment with the new active_version_id Response:
{
"data": {
"id": "dep_...",
"active_version_id": "ver_01H...",
"state": "running",
"public_url": "https://smile-dental.cliniciq.miosa.app"
}
} Picking the rollback target
Use the Versions API to inspect candidates:
const versions = await miosa.deployments.versions.list(deploymentId, {
state: "ready",
limit: 10,
})
// Find the last known good version
const target = versions.data.find(v => v.id !== deployment.active_version_id) For production, picking the immediately-previous version is usually correct. For incidents, you may need to go further back if multiple recent versions share the bug.
Rolling forward after rollback
After a rollback, the version you rolled back FROM is still in ready state (unless you explicitly archive it). You can re-promote it later if it turns out the issue wasn’t in the code:
await miosa.deployments.versions.promote(deploymentId, originalVersionId) This is a regular promotion, not a rollback. The path is symmetric.
What rollback doesn’t fix
| Issue | Why rollback doesn’t help |
|---|---|
| Bad database migration | The database is outside the version. Roll back the schema separately or restore from backup. See Postgres. |
| Bad managed-data state | Same — data services persist across version changes by design. |
| External service config drift | Stripe webhooks, OAuth callbacks, etc. — not part of the release. |
| Already-served bad responses | Already in your customers’ browsers / caches. Bust caches separately. |
Rollback fixes code; it does not fix state. Plan migrations to be backward-compatible (expand → migrate → contract) so older versions keep working after a forward migration.
Idempotency
Rollback requests carry an Idempotency-Key header. Identical keys within the retention window return the original result without re-running the operation. This matters during incidents when retry storms are common.
Audit
Every rollback emits an audit event with:
- The previous
active_version_id - The target version_id
- The user / API key that performed it
- The
Idempotency-Key - External attribution
Audit events are queryable through the platform audit API and are retained per the tenant’s plan.
See also
- Versions — picking a target
- Domains — what gets repointed
- Runtime Instances — what happens on the compute side
- Releases — why the rollback target’s artifact must still exist