Deployment status

Drive a deployment from your own code and read back whether it worked.

Deploying from your own code is two APIs and one field. You write a Distribution to say what should run where, the platform creates a Pod per matching device, and you read status.phase on those pods to find out what happened.

This page is about the reading half. Creating resources follows the same patterns as everything else in this section; knowing when a rollout has actually succeeded is the part that catches people out.

Which resource to watch

Both live in the Applications service.

Resource What it is What you do with it
Distribution An application plus a rule for which devices should run it Create and update it to drive a rollout
Pod One running instance on one device Read it to find out what actually happened

A distribution records your intent. It does not tell you whether ninety devices took the update and ten failed. Status lives on the pods, so a rollout is judged by listing or watching the pods the distribution produced, not by re-reading the distribution.

The cuttle applications command family maps onto the same API, which makes it the fastest way to see the shape of a response before you write code against it.

Reading status.phase

Pod.Status.Phase is the single field that answers “did this work?”.

Phase Meaning
PENDING Accepted, not yet launched
RUNNING Every container that should be up is up
SUCCEEDED Terminated, with non-error exit codes
FAILED Ran, then some or all containers stopped running
UNKNOWN The device stopped responding
IMAGE_DOWNLOAD_FAILED The image pull failed
INIT_FAILED Validation or initialization failed — often compose syntax, or a full disk
POD_CREATE_FAILED The compose file is valid, but bringing the pod up failed
PHASE_UNSPECIFIED Unknown state; not normally used

Two things about this enum are worth internalizing before you write a polling loop against it.

There is no TERMINATED phase. A pod that has stopped is SUCCEEDED or FAILED, depending on exit codes. TERMINATED exists, but it is a container state, in status.containerStatuses[].state. Code that waits for a terminated pod by matching on the phase will wait forever.

Failure is not one value. Treating FAILED as “the deployment broke” misses three of the four ways it can break. IMAGE_DOWNLOAD_FAILED, INIT_FAILED, and POD_CREATE_FAILED all mean the deployment did not work, and none of them is FAILED — that phase is reserved for a pod that started successfully and then stopped. Match the whole failure set, or you will report success for a pod that never pulled its image.

When a pod is in any failure phase, status.error carries the message, and status.failureCount counts retries for the errors that are retried.

Health is a separate question

status.healthStatus is not a finer-grained status.phase. Phase answers whether the containers are running; health answers whether the containers that are running consider themselves well, aggregated across the pod. A pod is UNHEALTHY as soon as any one container is, and HEALTHY while at least one reports healthy.

So a pod can be RUNNING and UNHEALTHY at the same time, and for most alerting that combination is the interesting one — the platform believes it did its job, and your application disagrees.

Watch, do not poll

A rollout is exactly the case watch exists for: you want to know the moment a pod changes phase, across a fleet, without asking repeatedly.

Watch the pods your distribution produced and apply changes when isCurrent arrives, as described in Watching for changes. For a controller tracking a whole collection across reconnects, use a STATELESS watch so you can resume from a token rather than restarting with a fresh snapshot.

If you do poll — in a CI job that deploys and exits, say — request only the fields you need with a field mask. Pod status is much smaller than a full pod, and the difference is multiplied by every device in the fleet. See Reading resources.