Skip to content

Command Palette

Search for a command to run...

Core workflow

Versions & rollback

Promote only healthy deployments and recover binaries together with their environment state.

Versioned builds

Every successful build - whether via phelix build, phelix rebuild, or a zero-downtime deploy - creates a numbered version (v1, v2, v3, ...) rather than overwriting. Versions store both the binary and its paired encrypted env snapshot, so rollback always restores a known-good binary + env pair - never binary-only.

The build-and-deploy ordering guarantee

Versions use a two-phase commit to ensure safety:

  1. Build succeeds
    The version is created on disk (builds/vN/binary, env/vN.enc) and recorded in versions.json with is_current set to false.
  2. Deploy succeeds
    The instance starts and the health check passes: PromoteVersion flips is_current to true and updates the current symlink.
  3. Deploy fails
    The version exists on disk for inspection or retry, but is_current stays false and the current symlink is never moved. The active running instance is untouched.

Version metadata

Each version row in versions.json carries an additive build_report object with the metrics captured during that build:

versions.json (excerpt)json
{  "version": 12,  "built_at": "2026-08-26T03:20:00Z",  "size_bytes": 15518924,  "git_commit": "8f31c2a...",  "is_current": false,  "build_report": {    "language": "go",    "compiler": "go",    "compiler_version": "1.27",    "started_at": "2026-08-26T03:19:29Z",    "ended_at": "2026-08-26T03:20:00Z",    "duration_ms": 31200,    "cache": { "status": "cold", "source": "go-build-cache" },    "artifact": { "type": "binary", "size_bytes": 15518924, "platform": "linux/amd64" }  }}

The field is optional and additive: versions recorded by older Phelix releases (and Docker-image versions) simply lack it. Missing report metadata only makes that version unavailable for regression comparisons - rollback, rollback listing, status, version loading, retention/pruning, and the current symlink logic all keep working unchanged. A malformed optional build_report payload degrades gracefully to "no report" instead of breaking the version index.

Rollback

phelix rollback reverts to a previous versioned build and automatically chooses the right strategy: classic stop-and-start for plain builds, or zero-downtime for apps deployed with blue-green/rolling.

FlagDefaultPurpose
--to-Target: v3, 3, or a tag name (bypasses the interactive picker)
--listfalseList all retained versions with metadata (non-interactive)
--dry-runfalsePreview the rollback plan without changing application, process, proxy, or deployment state
--reason-Record why the rollback was performed (stored in rollback history; max 500 characters)
--verify-Observe rollback stability for a Go duration (e.g. 30s, 1m, 2m30s) after the rollback completes
Terminal
phelix rollback myapp              # interactive picker (TTY), else previous versionphelix rollback myapp --to v2      # roll back to a specific version (no picker, no prompt)phelix rollback myapp --to 3       # version number without 'v' prefix also worksphelix rollback myapp --to hotfix-auth-bug   # roll back by tag namephelix rollback myapp --to v3 --dry-run      # preview only - no changes

The --to flag accepts either a version ID (v3, 3) or a unique tag name. If a tag matches exactly one version, it resolves automatically. If a tag matches zero or more than one version, an error is returned - use a version ID to disambiguate.

The interactive picker

In a TTY, phelix rollback <App> without --to opens an interactive picker instead of silently choosing the previous version. It lists valid rollback targets newest to oldest (the current version and versions whose binary is missing are excluded), shows built-time metadata per entry, and after you press Enter displays the rollback preview and asks for confirmation before executing.

Picker
Rollback 'myapp' Current: v12 Select version to rollback to: ❯ v11   hotfix-auth           2 min ago  v10   release-2.4.0         1 hour ago  v9    stable                yesterday  v8    -                     3 days ago ↑/↓ select   Enter continue   Esc cancel

Moving the cursor updates a details footer for the focused version, rendered from stored build metadata only - no process is started and no network check runs while navigating. Values that are not stored show -:

Details footer
v11├── Tag: hotfix-auth├── Commit: 8f31c2a├── Built: 2 min ago├── Binary: 14.8 MB├── Health: -└── Deploy: blue-green

Esc / Ctrl-C cancels cleanly (Rollback cancelled. is printed, nothing is deployed, and it is not reported as a failure). Explicit mode (--to v7) resolves and executes the requested target exactly - no picker, no confirmation prompt - keeping scripts and automation deterministic. In non-interactive environments (CI, redirected stdin) the picker is skipped and rollback falls back to the previous-version default, so automation never hangs waiting for input.

Preview with --dry-run

--dry-run previews exactly what a rollback would do - target resolution, strategy, traffic transition, health checks, and the step-by-step plan - without making any changes. Nothing is started, stopped, switched, promoted, or written: no process, proxy, versions.json, deploy.json, current symlink, or rollback.log mutation, and no rollback audit entry is recorded. It is safe to run any number of times, including while the app serves traffic.

Preview (illustrative)
→ Rollback Preview   Application    myapp  Current        v12  Target         v7  Built          2026-08-31 14:22:10  Commit         8f31c2a  Deploy Mode    blue-green  Health Check   Tier 1 (/health, 2xx required)  Environment    v7 snapshot available Changes:  Version        v12 → v7  Binary         15.2 MB → 14.8 MB  Environment    v7 snapshot available Traffic:  Public         :3000  Current        green  Target         blue Rollback Plan:   1. Ensure the proxy daemon is running   2. Load the v7 artifact (.../builds/v7/binary)   3. Restore the v7 environment snapshot (env/v7.enc)   4. Start the new instance on the inactive slot blue (internal port assigned at startup)   5. Run health checks against the new instance   6. Switch proxy traffic on public port 3000 from slot green to slot blue   7. Promote v7 as current (versions.json + current symlink)   8. Drain and stop the old slot green instance (grace 30s) ✓ No changes will be made.

The preview is a plan and validation preview, not a guarantee: it reads the same metadata the real rollback resolves, validates that the target artifact exists, and reports warnings - but it does not start instances, perform live health checks, or predict ports that are only assigned at startup. For classic apps the preview shows the stop-and-start plan and an explicit Downtime: Expected yes line; for rolling apps it lists one replacement step per replica, in the order the real rollback replaces them.

The same target resolution as a real rollback applies, so --dry-run fails on an invalid or missing target with the same error codes a real rollback would return - with nothing modified. Warnings (a missing env snapshot, a large rollback distance, a deploy lock held by another operation) are shown in a Warnings: section without blocking the preview.

Rollback reasons

The --reason text becomes part of the rollback history/audit record, so phelix rollback history explains why each rollback happened:

Terminal
phelix rollback myapp --to v7 --reason "Login endpoint returning 500"

The reason is optional; --dry-run displays it in the plan but persists nothing. Explicitly supplied-but-empty reasons are rejected (rollback reason cannot be empty), whitespace is collapsed, and the text is capped at 500 characters. It is serialized through encoding/json - never concatenated - so it cannot corrupt the structured history or inject log lines. In the interactive picker the reason is requested after the target version is chosen (Enter skips it); an explicit --reason never re-prompts.

Verification windows

A rollback should not count as successful merely because the process started. With --verify <duration>, Phelix observes the application for the requested period after the rollback reaches its committed, traffic-serving state (target loaded, environment restored, instance healthy, proxy switched, deployment state persisted) and fails if it does not remain healthy:

Terminal
phelix rollback myapp --to v7 --verify 30s
Output
→ Verifying rollback stability...  5s    ✓ healthy  10s   ✓ healthy  ...✓ Rollback remained healthy for 30s
  • Verification observes the instances actually serving traffic - the active blue-green slot, all rolling replicas, or the classic process - never a drained slot.
  • It reuses the existing tiered health checks and the per-app health configuration; the duration is the observation window, not a request timeout, and it is parsed as a real Go duration (30 is rejected - use 30s).
  • Verification failure is distinct from rollback execution failure. The CLI prints Rollback execution: SUCCESS / Verification: FAILED and exits 23 (ROLLBACK_VERIFY_FAILED), while an execution failure exits 22 (ROLLBACK_FAILED). History records the execution as SUCCESS with a verification block (passed / failed / cancelled).
  • Ctrl+C during the window cancels the observation (history: cancelled); the completed rollback stays active. Phelix never rolls forward or backward on its own - recovery is your call.
  • Omitting --verify preserves the existing rollback behavior exactly; no extra delay is added.--dry-run --verify 30s shows the planned window and performs nothing.

Automatic rollback

Add --auto-rollback to phelix rebuild and a deploy-phase failure restores the previous known-good version automatically - the same path a manual rollback takes, so locks, health checks, proxy switching, state reconciliation, and history are shared, not duplicated:

Terminal
phelix rebuild myapp --blue-green --auto-rollbackphelix rebuild myapp --replicas 4 --auto-rollback
Output
✗ v13 failed health checks→ Automatic rollback enabled  → automatic rollback: restoring v12  ✓ v12 started, healthy and serving traffic✓ Deployment rolled back automatically
  • Failure boundaries. Build/compile failures never trigger a rollback (no new version was recorded, nothing was displaced). Deployment-phase failures do: instance start failure, failed health checks, proxy switch failure, and a partially-completed rolling rollout.
  • Canary/progressive. A regression during a rollout is handled by the rollout itself: the stable version is restored to 100% of traffic before the command returns (CANARY_REGRESSION), so there is normally nothing left for --auto-rollback to do - it reports "kept serving" instead of faking a rollback. It still covers the corner cases where the rollout's own restore fails.
  • Blue-green. A failed candidate is killed before the traffic switch, so the previous version usually kept serving the whole time - Phelix reports that ("kept serving") instead of faking a rollback. No history entry is written for a rollback that did not happen.
  • Rolling. If the rollout failed after some replicas switched to the new version, recovery redeploys the known-good version over the fleet one replica at a time, preserving availability. If it failed at the first replica, the old fleet is still intact and nothing is redeployed.
  • Classic. The known-good binary is copied back and restarted (brief downtime is inherent to classic - Phelix does not claim zero downtime).
  • Last known good is authoritative. The restore target is the version versions.json currently promotes (a failed deploy never promotes itself), restricted to versions whose binary still exists - never "current minus one". With no known-good version, Phelix says so instead of fabricating a target.
  • History. Automatic recoveries appear in phelix rollback history with the SOURCE column set to automatic and a reason derived from the deployment failure ("Deployment v13 failed: ..."). Manual rollbacks show manual.
  • Recovery can fail too. If the previous version cannot be restored safely, Phelix prints the degraded state explicitly and exits 24 (AUTO_ROLLBACK_FAILED) - it never claims success. A recovered deployment still exits 21 (the deployment itself failed; the output and history say recovery succeeded). The rollback is triggered exactly once per failed deployment and cannot recurse: recovery failures are terminal, never new rollback triggers.

Rollback history

phelix rollback history <AppName> shows the recorded rollback outcomes for one application, newest first. Every rollback attempt that reaches execution is recorded - successes and failures - at the moment the rollback transaction completes, together with the deployment mode actually used for that rollback (classic, blue-green, rolling). The mode is a historical fact: later re-deploys of the app never rewrite old records. FROM/TO are the versions of that transition, not the app's current version.

Terminal
phelix rollback history myappphelix rollback history myapp --limit 50
Output
Rollback History - myapp TIME                  FROM   TO     STATUS   MODE         REASON2026-09-06 14:20:31   v12    v7     SUCCESS  blue-green   Login endpoint returning 5002026-09-06 13:11:02   v13    v12    SUCCESS  rolling      API regression2026-09-02 09:13:12   v9     v8     FAILED   rolling      Database migration issue2026-08-28 18:42:09   v8     v6     SUCCESS  classic      -
FlagDefaultPurpose
--limit20Maximum number of entries to show (must be a positive number)
  • An app with no rollbacks shows No rollback history found. - a normal state, not an error.
  • --dry-run previews never record history; cancelling the interactive picker (Esc) never records history either, and is not a failure.
  • Malformed legacy lines in the history file are skipped with a short warning; they are never silently rewritten.
  • REASON shows the recorded --reason, or - for reason-free and pre-reason records (old history entries load unchanged and are never rewritten to add an empty reason).
  • When --verify was requested, each record also carries a verification block on disk (status passed / failed / cancelled). A failed window renders as VERIFY_FAILED while STATUS stays SUCCESS - execution outcome and verification outcome are kept separate so history always tells the truth about what happened.

History is stored per app as JSON Lines at ~/.phelix/apps/<AppName>/rollback_history.jsonl.

How rollback works

Rollback is split into two phases: resolve + plan, then execute. The CLI first resolves the target version and deployment strategy and validates the target artifact (shared by both the preview and the real path); a real rollback then executes the plan, --dry-run renders it and exits.

Zero-downtime path (blue-green / rolling apps):

  1. Resolve
    ResolveVersionOrTag resolves the --to argument to a concrete version ID.
  2. Locate
    ExistingVersionSource resolves the binary and env paths for the target version.
  3. Start
    A new instance starts on the inactive slot (blue or green).
  4. Verify
    Tiered health checks verify the instance is healthy.
  5. Switch
    The proxy atomically switches traffic to the new instance.
  6. Drain
    The old instance is gracefully drained and stopped.
  7. Promote
    The current symlink and versions.json are updated.

Classic path (apps built without --blue-green / --replicas): resolve the target version, stop the current instance, copy the versioned binary to the app's expected location, start the app, and promote the version (is_current set to true, current symlink updated). If the start fails, the version exists on disk but is_current stays false - you can retry without a broken "current" pointer.

Rollback safety

  • Dry-run preview: --dry-run shows the full plan with zero mutations - verify before you commit.
  • Concurrent protection: a deploy lock prevents rollback from racing with another deploy or rollback on the same app.
  • Versioned env: binary and env are paired per version; rollback always restores both.
  • Audit log: every rollback attempt (success or failure) is recorded in ~/.phelix/apps/<AppName>/rollback.log.

Inspecting versions

Terminal
phelix rollback myapp --list
ColumnMeaning
VersionvN label
TagOptional label (e.g. hotfix-auth-bug), if provided via --tag
CommitGit commit hash (if available at build time)
BuiltBuild timestamp (RFC 3339)
SizeBinary size on disk
CurrentWhether this version is actively serving traffic
Prune soonWhether this version would be removed after the next build (retention)

Retention

Old versions are automatically pruned after each successful build, keeping the last 5 versions by default. The currently active version is never pruned, even if it falls outside the retention window. The retention count is configurable per plan tier.

PlanRetained versions
Free3
Default5
Pro10
EnterpriseUnlimited