Skip to content

Rollback, Status, Logs & History

Pilum has four commands for working with services that are already deployed:

CommandWhat it doesReads from
pilum rollbackMoves a service back to its previous versionThe cloud provider
pilum statusShows whether each service is healthy and what it runsThe cloud provider
pilum logsPrints a service’s logsThe cloud provider
pilum historyLists 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

Terminal window
pilum rollback <services...> [flags]

Rollback is supported for GCP services only:

Recipe (type)What rollback changesDefault target
gcp-cloud-run, gcp-cloud-run-from-imageSends 100% of traffic to an earlier revisionThe newest healthy revision created before the one serving now
gcp-cloud-run-jobPoints the job at an earlier imageThe image of the most recent execution that ran something other than the current image
gcp-mig-containerStarts a rolling update to an earlier instance template, then waits for the group to be stableThe 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:

Terminal window
gcloud run services update-traffic api --to-revisions=api-00041-xyz=100 --region us-central1 --project my-project

The 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:

Terminal window
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 serviceA revision name, e.g. api-00041-xyz. It must exist, be Ready, and not be serving already.
Cloud Run jobAn image tag, applied to the current image’s repository (--to v1.4.2), or a full image reference (anything containing /).
MIGA 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

Terminal window
# Previous revision of one service
pilum rollback api
# A specific revision
pilum rollback api --to api-00041-xyz
# A job back to an image tag
pilum rollback nightly-job --to v1.4.2
# A MIG back to the template from release v1.8.0
pilum rollback edge-proxy --to v1.8.0
# Several services, plan only
pilum rollback api worker --dry-run
# In CI (no prompt)
pilum rollback api --yes

Service 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

  1. 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.
  2. Show the plan. Each service is listed as current → target, plus the target image where Pilum knows it.
  3. Confirm. Pilum asks Roll back N service(s)? (y/N). With --dry-run it prints the commands and stops here.
  4. Lock. It takes the project’s deployment lock, like a deploy. --force overrides an existing lock.
  5. 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-xyz

Confirmation 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-interactively

The 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 with cloud_run.no_traffic: true skip 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

FlagShortDefaultDescription
--toprevious versionTarget revision, image tag/reference, or template name/tag. One service instance only.
--yes-yfalseSkip the confirmation prompt. Required when not interactive.
--dry-run-DfalsePrint the plan and commands without changing anything
--force-ffalseOverride the deployment lock
--timeout-T60Timeout per command, in seconds
--debug-dfalseDebug output
--env-eApply 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

Terminal window
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 —
RecipeQueryStatus values
gcp-cloud-run, gcp-cloud-run-from-imagegcloud run services describeReady / NotReady from the Ready condition, plus the URL and image
gcp-cloud-run-jobgcloud run jobs describeReady / NotReady
gcp-mig-containergcloud compute instance-groups managed describeReady when the group is stable and on its target version, otherwise Updating. Shows the template and stable/target instance counts
azure-container-appsaz containerapp showThe app’s provisioningState (e.g. Succeeded), plus FQDN and image
aws-lambdaaws lambda get-function --function-name <name>The function’s State (e.g. Active)
cloudflare-pageswrangler pages deployment listAlways 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.

FlagShortDefaultDescription
--timeout-T30Query timeout in seconds
--providerOnly services of this provider
--env-eApply an environments override first (e.g. a different project)
--debug-dfalseDebug output

Logs

Terminal window
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)".

RecipeCommand--lines--follow
gcp-cloud-run, gcp-cloud-run-from-imagegcloud run services logs read <name> --limit=NYesStreams the command’s output, but gcloud ... logs read returns once, so it doesn’t tail
gcp-cloud-run-jobgcloud run jobs executions list --job <name> --limit=N (executions, not log lines)YesSame as above
gcp-mig-containerNot supported. Use Cloud Logging for the group’s instances.
azure-container-appsaz containerapp logs show --type consoleNoYes (--follow)
aws-lambdaaws logs tail /aws/lambda/<name>NoYes (--follow)
cloudflare-pageswrangler pages deployment list (deployments, not logs)NoNo

npm and Homebrew services fail with logs not available for provider '<provider>'.

FlagShortDefaultDescription
--lines-n50Number of lines (GCP only)
--follow-ffalseStream output to the terminal instead of capturing it
--timeout-T30Timeout in seconds (snapshot mode)
--env-eApply an environments override first
--debug-dfalsePrint 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).

Terminal window
pilum history # last 10 runs
pilum history --limit 25 # last 25
pilum history --service api # runs that included api
Deployment 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>.

FlagShortDefaultDescription
--limit-n10Number of entries to show
--service-sOnly 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