Rollback, Status, Logs & History
Pilum has four commands for working with services that are already deployed:
| Command | What it does | Reads from |
|---|---|---|
pilum rollback | Moves a service back to its previous version | The cloud provider |
pilum status | Shows whether each service is healthy and what it runs | The cloud provider |
pilum logs | Prints a service’s logs | The cloud provider |
pilum history | Lists past Pilum runs in this project | .pilum/history.jsonl |
The first three call the provider CLI (gcloud, az, aws, wrangler) with your current credentials, so they work from any machine, including CI.
Rollback
pilum rollback <services...> [flags]Rollback is supported for GCP services only:
Recipe (type) | What rollback changes | Default target |
|---|---|---|
gcp-cloud-run, gcp-cloud-run-from-image | Sends 100% of traffic to an earlier revision | The newest healthy revision created before the one serving now |
gcp-cloud-run-job | Points the job at an earlier image | The image of the most recent execution that ran something other than the current image |
gcp-mig-container | Starts a rolling update to an earlier instance template, then waits for the group to be stable | The newest template in the mig.template_base family created before the current one |
Other recipes (Lambda, Azure, Cloudflare, npm, Homebrew, gcp-artifact-registry-image) fail with rollback is not supported for <service> (recipe <type>).
How each kind rolls back
Cloud Run services. No rebuild. Pilum reads which revision is serving, lists the service’s revisions, and runs:
gcloud run services update-traffic api --to-revisions=api-00041-xyz=100 --region us-central1 --project my-projectThe old revision comes back with its full configuration: env vars, secrets, CPU and memory. Revisions that never became Ready are never chosen.
Cloud Run jobs. Jobs have no revisions, so Pilum reads the image of earlier executions and runs gcloud run jobs update <job> --image <previous image>. The job’s other settings stay as they are now. If no earlier execution used a different image, Pilum asks you to pass --to.
Managed instance groups. Pilum lists the templates named <template_base> or <template_base>-* in the MIG’s template region, picks the previous one, and runs:
gcloud compute instance-groups managed rolling-action start-update <mig> \ --version template=<previous template> --max-surge <mig.max_surge> --max-unavailable <mig.max_unavailable> ...gcloud compute instance-groups managed wait-until <mig> --stable --timeout <mig.wait_timeout> ...This is the same zero-downtime rolling update a deploy uses: no more than mig.max_surge extra VMs, and nothing taken down early with the default max_unavailable: 0. The wait is allowed mig.wait_timeout plus 60 seconds.
Choosing a target with --to
Without --to, rollback steps back one version. Run it again to step back further.
| Kind | --to accepts |
|---|---|
| Cloud Run service | A revision name, e.g. api-00041-xyz. It must exist, be Ready, and not be serving already. |
| Cloud Run job | An image tag, applied to the current image’s repository (--to v1.4.2), or a full image reference (anything containing /). |
| MIG | A template name (<base> or <base>-...), or a release tag, which becomes <base>-<tag>. |
--to needs exactly one service instance. A multi-region service has one instance per region, so name one: pilum rollback "api (us-central1)" --to api-00041-xyz.
Examples
# Previous revision of one servicepilum rollback api
# A specific revisionpilum rollback api --to api-00041-xyz
# A job back to an image tagpilum rollback nightly-job --to v1.4.2
# A MIG back to the template from release v1.8.0pilum rollback edge-proxy --to v1.8.0
# Several services, plan onlypilum rollback api worker --dry-run
# In CI (no prompt)pilum rollback api --yesService arguments work as they do for deploy: space- or comma-separated names, globs such as 'api-*', and "name (region)" for one region.
What happens when you run it
- Plan. Pilum queries every selected service in parallel. If planning fails for any service (no earlier revision, unknown
--to), the whole rollback stops before anything changes. A multi-service rollback is never left half applied because one service couldn’t be planned. - Show the plan. Each service is listed as
current → target, plus the target image where Pilum knows it. - Confirm. Pilum asks
Roll back N service(s)? (y/N). With--dry-runit prints the commands and stops here. - Lock. It takes the project’s deployment lock, like a deploy.
--forceoverrides an existing lock. - Apply in parallel, then record the result in history.
Rollback plan api api-00042-abc → api-00041-xyz us-central1-docker.pkg.dev/my-project/services/api:v1.3.0
Roll back 1 service(s)? (y/N) y✓ api → api-00041-xyzConfirmation in CI
Rollback refuses to run unattended without --yes. It treats input as non-interactive when any of CI, GITHUB_ACTIONS, GITLAB_CI, CIRCLECI, JENKINS_URL or BUILDKITE is set, when stdin is not a terminal, or when stdin is /dev/null. It also won’t prompt in --json mode. In all those cases it fails with:
refusing to roll back without confirmation; pass --yes to proceed non-interactivelyThe next deploy undoes a rollback
A rollback is a temporary pin, not a change to your config:
- Cloud Run: every deploy ends with the route traffic to latest step (
gcloud run services update-traffic --to-latest), which removes the pin and sends 100% of traffic to the new revision. It also resets any traffic split you set by hand. Services withcloud_run.no_traffic: trueskip that step. - Jobs: the next deploy sets the job’s image to the new build.
- MIGs: the next deploy creates a new
<base>-<tag>template and rolls the group onto it.
If the bad code is still on your main branch, fix or revert it before the next deploy runs.
Rollback flags
| Flag | Short | Default | Description |
|---|---|---|---|
--to | previous version | Target revision, image tag/reference, or template name/tag. One service instance only. | |
--yes | -y | false | Skip the confirmation prompt. Required when not interactive. |
--dry-run | -D | false | Print the plan and commands without changing anything |
--force | -f | false | Override the deployment lock |
--timeout | -T | 60 | Timeout per command, in seconds |
--debug | -d | false | Debug output |
--env | -e | Apply an environments override before planning |
Dry-run doesn’t take the lock and doesn’t need --yes. With --json, rollback prints the plan (dry-run) or the per-service results.
Status
pilum status [services...] [--provider gcp] [--env production] [--timeout 30]pilum status (alias st) queries every selected service in parallel. npm and Homebrew services are skipped because nothing is running for them.
Service Status ✓ api Ready us-central1 us-central1-docker.pkg.dev/my-project/services/api:v1.3.0 https://api-abc123-uc.a.run.app ● edge-proxy Updating us-central1-a template edge-proxy-v1-9-0, 2/3 instances stable ✗ billing Error us-central1 —| Recipe | Query | Status values |
|---|---|---|
gcp-cloud-run, gcp-cloud-run-from-image | gcloud run services describe | Ready / NotReady from the Ready condition, plus the URL and image |
gcp-cloud-run-job | gcloud run jobs describe | Ready / NotReady |
gcp-mig-container | gcloud compute instance-groups managed describe | Ready when the group is stable and on its target version, otherwise Updating. Shows the template and stable/target instance counts |
azure-container-apps | az containerapp show | The app’s provisioningState (e.g. Succeeded), plus FQDN and image |
aws-lambda | aws lambda get-function --function-name <name> | The function’s State (e.g. Active) |
cloudflare-pages | wrangler pages deployment list | Always Unknown; the output isn’t parsed yet |
Symbols: ✓ for Ready, Running, Succeeded and Active; ✗ for Error, NotReady and Failed; ⚠ for Unknown; ● for anything else, such as Updating. Error means the query command itself failed, for example because the service doesn’t exist or you lack permission.
A gcp-artifact-registry-image service only publishes an image, so there is nothing running to query. Status falls back to a Cloud Run lookup by name and usually shows Error.
| Flag | Short | Default | Description |
|---|---|---|---|
--timeout | -T | 30 | Query timeout in seconds |
--provider | Only services of this provider | ||
--env | -e | Apply an environments override first (e.g. a different project) | |
--debug | -d | false | Debug output |
Logs
pilum logs <service> [--lines 100] [--follow]pilum logs (alias log) takes exactly one service and prints its logs. If a multi-region service matches more than once, the first instance is used, so name one with "api (europe-west1)".
| Recipe | Command | --lines | --follow |
|---|---|---|---|
gcp-cloud-run, gcp-cloud-run-from-image | gcloud run services logs read <name> --limit=N | Yes | Streams the command’s output, but gcloud ... logs read returns once, so it doesn’t tail |
gcp-cloud-run-job | gcloud run jobs executions list --job <name> --limit=N (executions, not log lines) | Yes | Same as above |
gcp-mig-container | Not supported. Use Cloud Logging for the group’s instances. | ||
azure-container-apps | az containerapp logs show --type console | No | Yes (--follow) |
aws-lambda | aws logs tail /aws/lambda/<name> | No | Yes (--follow) |
cloudflare-pages | wrangler pages deployment list (deployments, not logs) | No | No |
npm and Homebrew services fail with logs not available for provider '<provider>'.
| Flag | Short | Default | Description |
|---|---|---|---|
--lines | -n | 50 | Number of lines (GCP only) |
--follow | -f | false | Stream output to the terminal instead of capturing it |
--timeout | -T | 30 | Timeout in seconds (snapshot mode) |
--env | -e | Apply an environments override first | |
--debug | -d | false | Print the provider command |
History
Every deploy, build, publish and push run, and every rollback, appends one JSON line to .pilum/history.jsonl in the project root. Dry runs are not recorded. Nothing is written when Pilum can’t find a project root (a directory with go.mod, package.json, Cargo.toml, tsconfig.json, cdk.json or .gitignore).
pilum history # last 10 runspilum history --limit 25 # last 25pilum history --service api # runs that included apiDeployment History 3f9a1c2e 2026-10-07 14:02 deploy v1.3.0 ✓ 10/10 (2m14s) b71e09d4 2026-10-07 15:40 rollback ✓ 1/1 (8.2s)Each line shows the run ID, local time, command, tag, succeeded/total results and duration. Results are counted per service per step, so a five-step deploy of two services counts 10. Rollback entries record each service’s step as rollback <from> → <to>.
| Flag | Short | Default | Description |
|---|---|---|---|
--limit | -n | 10 | Number of entries to show |
--service | -s | Only entries that include this exact service name. Multi-region instances are recorded as name (region). |
--json returns the raw entries. History is local to the checkout: CI runners write their own .pilum/history.jsonl, so it doesn’t replace your CI’s run history. Add .pilum/ to .gitignore.
Next Steps
- Environments & CI — locks, commit statuses and webhooks
- GCP Cloud Run — revisions, traffic and
no_traffic - GCP Managed Instance Group — template naming and rolling updates
- CLI Commands — every command and flag