Rollback¶
Every deploy that produced a real image stays rollback-able as long
as Harbor still has the tag. The rollback path reuses the same
RollingUpdate machinery as a forward deploy — same readiness probe,
same maxSurge=1/maxUnavailable=0 — so a rollback is just a forward
deploy that happens to point at an older image tag.
How it works¶
sequenceDiagram
participant U as Operator
participant CP as Control Plane
participant K as Kubernetes
participant H as Harbor
participant DB as Postgres (paas_audit_events)
U->>CP: POST /v1/apps/{app}/deploys/{old}/rollback
CP->>CP: list_deployments → find target by id
CP->>CP: validate image_ref (not empty, not :building)
CP->>K: deploy(image_ref of old deploy)
K->>H: pull image (must still exist)
K->>K: rolling update (maxSurge=1, maxUnavailable=0)
CP->>DB: paas_audit_events INSERT action="deploy.rollback"
CP->>DB: count migrations_applied_since(old.created_at)
CP-->>U: { …new deploy fields, warnings: ["Schema mismatch…"] }
The control plane returns the new Deployment row (the rollback
created a brand new entry) plus a warnings array — empty in the
normal case, populated when something the operator should know about
happened between the old deploy and now.
When to rollback¶
- Production regression caught seconds/minutes after a deploy.
- 1-click recovery target: <30s end-to-end (POST to ready) for apps with a fast image pull.
- Anything you can also achieve by re-deploying the previous image tag manually — rollback is the same thing with audit trail and a schema-mismatch warning.
What does NOT restore¶
By design, a rollback only restores the image. The following are intentionally not reverted:
| Resource | Why |
|---|---|
| Environment variables | A var change usually accompanies a code change; keeping the new env on rollback avoids surprising the new-version code paths after manual fixes. |
| Secrets | Same reason as env vars. Operators rotate secrets independently of code. |
| Database schema | Forward-only migrations (sub-brique 20d). Rolling back code into a database with newer schema can work, can fail at runtime, and is a per-app judgment call. |
| Persistent volumes | Volumes are stateful and live longer than any single deploy. |
If you need to "rollback everything", restore env vars and secrets separately (see Secrets & Env Vars) and coordinate the schema move via your migration tool.
Schema migration warning¶
When a rollback target predates one or more applied migrations, the control plane attaches a warning to the response and the dashboard toast highlights it:
Rollback started — Warning: Migrations have been applied since this deploy. Schema mismatch possible.
The warning is informational — the rollback is not blocked. The operator decides whether the schema change is backward-compatible (adding a nullable column, for instance) or whether they need to apply a corrective migration before retrying.
Audit trail¶
Every rollback writes a row to paas_audit_events:
SELECT created_at, actor_id, target_id, details_jsonb
FROM paas_audit_events
WHERE action = 'deploy.rollback'
ORDER BY created_at DESC
LIMIT 10;
The details_jsonb payload includes app_id, from_deploy_id (the
rollback target), to_deploy_id (the brand-new deploy created by the
rollback) and image_ref. actor_id is the JWT subject when the API
was called via the dashboard; null for system-initiated rollbacks
(none in cycle 2).
API curl example¶
TOKEN=$(curl -sf -X POST https://runtime.di2amp.com/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"…"}' | jq -r '.data.access_token')
curl -sf -X POST \
-H "Authorization: Bearer $TOKEN" \
"https://runtime.di2amp.com/api/v1/apps/$APP_ID/deploys/$DEPLOY_ID/rollback" \
| jq '{ id, image_ref, status, warnings }'
Sample response:
{
"data": {
"id": "0bd91…",
"image_ref": "harbor/octave/hello:v2",
"status": "deploying",
"replicas": { "desired": 2, "ready": 0, "available": 0 },
"warnings": [
"Migrations have been applied since this deploy. Schema mismatch possible."
]
}
}
| Status | Meaning |
|---|---|
200 OK |
Rollback accepted; new deploy created and rolling out |
400 invalid_rollback_target |
Target deploy never produced a real image (:building) |
404 not_found |
Target deploy doesn't exist for this app |
409 conflict |
Another deploy is already in progress — wait, then retry |
UI dashboard¶
Open /apps/{app}/deploys, find the deploy row, click ↩ Rollback.
A confirmation modal lists the target image tag and a warning banner
for the schema-mismatch case if applicable. Confirming triggers the
same POST as the curl above; the toast either says "Rollback started"
(no warnings) or "Rollback started — Warning: …".
Harbor tag retention (Phase 2)¶
Today the rollback only succeeds if Harbor still has the target tag. Phase 2 will enforce a retention policy (last 30 tags or 90 days, whichever is longer) so rollback windows are predictable; until then, operators relying on long rollback windows should keep their Harbor project's GC schedule conservative.
Related¶
- Rolling Deploy — the engine the rollback reuses
- Build Cache — what runs before every deploy, rollback included