Deployment History & Rollback
Every deploy is logged. Every deploy is reversible. The history layer (lib/history/) records who deployed what to where, when, and what changed; the rollback path puts the previous version back without you needing to remember image tags.
What gets recorded
For every ./portoser deploy ..., Portoser writes a record containing:
- Deployment ID (e.g.
deploy-20260315-143022-a3f2) — sortable timestamp + short hash - Service(s) deployed
- Target machine(s)
- Deployment type (
docker,local,native) - Image tag / commit SHA / version before and after
- Health check outcome (passed, failed-and-self-healed, failed-hard)
- Duration
- User (whoever invoked the CLI or web UI)
Records live in ~/.portoser/history/ as JSON, indexed by timestamp.
Inspecting history
./portoser history list # last 50 deploys
./portoser history list reports-api # filter by service
./portoser history list --json-output # for scripting
./portoser history show deploy-20260315-143022-a3f2 # full record for one deploy
./portoser history compare ID1 ID2 # diff between two deploys
./portoser history stats # success rate, mean duration, recent failures
./portoser history stats reports-api 30 # last 30 days for one service
The web UI's deployment history page renders the same data with timeline scrubbing and click-through to per-deploy detail.
Rolling back
./portoser history preview deploy-20260315-143022-a3f2
# Shows what would change if you rolled back to this deploy:
# - Image tag changes
# - Compose file diff
# - Affected dependents
./portoser history rollback deploy-20260315-143022-a3f2
# Walks the change in reverse — re-deploys the previous version
The rollback path is the same code as deploy; it just targets the old image / commit / config. So all the safety from a normal deploy applies: dependency check, health verification, self-healing if something flaps. If the rollback itself fails health, Portoser stops and surfaces the diagnostic — it doesn't double back further automatically.
--force skips the dependency check, useful when the dependent services are already broken and you just need the rollback to land.
What "rolling back" actually does, by deployment type
docker — image tag swap
The previous image tag is in the history record. Rollback re-pulls that tag, brings up the old container, and waits for health. The current container is stopped first; if rollback health fails, Portoser will log it but won't auto-revert-the-revert.
local — git ref swap + redeploy
Rollback checks out the previous commit (history records the SHA), runs uv sync, restarts the process. Your service repo needs to actually contain the previous version — no commit, no rollback target.
native — config rewind
For systemd or launchd services, rollback regenerates the previous unit / plist content and reloads the service manager. This is the trickiest type — anything done outside the unit file (a manual systemctl edit override, a launchctl-loaded plist that was modified by hand) is invisible to rollback.
Cleanup
History grows. The cleanup subcommand trims it:
./portoser history cleanup # keep defaults: last 200 records or 90 days, whichever is more
./portoser history cleanup 100 30 # keep last 100 records or 30 days
Records older than the policy are deleted from ~/.portoser/history/. The rollback target window shrinks accordingly — you can't rollback to a record that no longer exists.
What history is not
- Not a backup system. It records what was deployed, not the data your services manage. Database backups are a separate concern; see your database's tooling.
- Not version-controlled config. If you change
registry.ymland commit it, that's git. History records what got applied, not what was edited. - Not blue-green. There's no "two versions running side by side" mode. Deploys replace, rollbacks replace. If you want gradual rollouts, you wrap that at the application layer.
Where this earns its keep
- A deploy fails health, the self-healing loop tries its patterns, none stick. You
./portoser history rollback <previous-id>and you're back in 30 seconds. - A change in service A breaks dependent service B.
./portoser history compareshows the diff, yourollback, then think about whether you wanted that change at all. - A team member's deploy is causing trouble overnight. You
./portoser history list --json-output | jq '.[].user'to see who, thenrollback.
Next
- Operations: Troubleshooting — when to rollback vs. when to let self-healing fix it
- Self-Healing Loop — the loop that runs on every deploy and every rollback
- CLI Commands — full
portoser historyreference