Skip to content

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.