What "stuck Progressing" means
ArgoCD reports two independent things about every resource: sync status (does
live state match Git?) and health status (is the resource actually healthy?).
Progressing is a health status — it means the resource "is not yet healthy but is
making progress toward becoming healthy"
(ArgoCD: Resource Health).
A Deployment rolling out new pods is Progressing. So is a LoadBalancer Service waiting
for an external IP. Stuck Progressing means that transition never completes. The fix is
almost always in the workload, not in re-syncing.
Be pedantic here, because the two axes get conflated constantly:
- Sync status (
Synced/OutOfSync) compares live manifests to Git. It does not measure health. - Health status (
Healthy/Progressing/Degraded/Suspended/Missing/Unknown) measures whether the applied resource is working.
They are orthogonal. An app can be Synced and Progressing — ArgoCD applied
everything correctly, but the resulting workload hasn't reached Healthy yet. Re-syncing
a Synced app does nothing for a health problem.
Progressing also differs from Degraded: Progressing is "not there yet," Degraded is
"failed." For a Deployment, the boundary between them is a timer — progressDeadlineSeconds
(see below). Before the deadline, a stalled rollout reads Progressing; after it, ArgoCD
maps the resulting condition to Degraded.
Diagnose it
Start at the app and walk down to the specific resource and its pods:
argocd app get <app-name>
# Read TWO columns separately: Sync (Synced/OutOfSync) and Health (Progressing/...).
# Find the child resource whose Health is Progressing.
argocd app resources <app-name>
# Lists managed resources with their health — confirm which one is stuck.
kubectl get pods -n <namespace> -l app=<app>
# Are pods Running and Ready? Or Pending / ImagePullBackOff / CrashLoopBackOff?
For a Deployment, read its conditions — this is the single most useful signal:
kubectl get deploy <deploy> -n <namespace> -o yaml
# Look under .status.conditions:
# type: Progressing status: "True" reason: NewReplicaSetAvailable -> done/Healthy
# type: Progressing status: "True" reason: ReplicaSetUpdated -> still rolling
# type: Progressing status: "False" reason: ProgressDeadlineExceeded -> stalled (Degraded)
# type: Available status: "False" -> not enough ready replicas
kubectl describe deploy <deploy> -n <namespace>
# The Conditions block plus events (scaled up replica set, etc.)
kubectl describe deploy <deploy> -n <namespace> | grep -A5 Conditions
A Deployment stays Progressing until either it satisfies its rollout (becomes Healthy) or
exceeds progressDeadlineSeconds, at which point Kubernetes surfaces a condition of
type: Progressing, status: "False", reason: ProgressDeadlineExceeded in the resource
status; the controller takes no further action itself
(Kubernetes: Failed Deployment).
That field defaults to 600s
(Kubernetes API: DeploymentSpec).
ArgoCD then maps that failed condition to Degraded, not Progressing — so a Deployment
that's still Progressing after several minutes is, by definition, inside that deadline
window. That's your clue that the rollout is stalled and about to flip.
If ArgoCD shows Progressing but Kubernetes shows the resource as fine, force a hard refresh — the controller can serve a stale cached status:
argocd app get <app-name> --hard-refresh
How ArgoCD decides a resource is Progressing
ArgoCD's built-in Deployment health check passes (Healthy) only when observed generation
equals desired generation and updated replicas match desired replicas. Until both
hold, the Deployment is reported Progressing
(ArgoCD: Resource Health).
The application's health is the worst health among its immediate child resources,
ranked Healthy > Suspended > Progressing > Missing > Degraded > Unknown
(ArgoCD: Resource Health).
So one Progressing child holds the whole app at Progressing as long as nothing is worse —
your first job is to find which resource and why.
Causes, each end to end
1. Deployment never reaches desired replicas (pods won't start)
The most common path. The Deployment's updated replicas never reach desired, so the
built-in check never passes and the resource stays Progressing — until
progressDeadlineSeconds (default 600s) is exceeded, after which Kubernetes sets
Progressing=False / ProgressDeadlineExceeded and ArgoCD flips it to Degraded
(Kubernetes: Failed Deployment).
The usual underlying reasons are pods stuck in ImagePullBackOff, CrashLoopBackOff, or
Pending (unschedulable).
Diagnose:
kubectl get pods -n <namespace> -l app=<app>
# ImagePullBackOff / ErrImagePull -> registry/tag/secret problem
# CrashLoopBackOff / Error -> the container starts then dies
# Pending -> unschedulable
kubectl describe pod <pod> -n <namespace>
# Events name the exact reason: Failed to pull image / Back-off restarting / FailedScheduling
kubectl logs <pod> -n <namespace> --previous
# crash output from before the last restart
Fix: depends on which one the events name — correct the image tag/digest or attach a
valid imagePullSecret; fix the bad config/env the crash logs point at; or lower resource
requests / raise quota / fix affinity so Pending pods can be scheduled. Once pods become
Ready and updated replicas reach desired, the Deployment goes Healthy on its own. These
each have a dedicated page:
ImagePullBackOff,
CrashLoopBackOff,
Pod Pending.
2. Readiness probe never passes
The pods are Running, but the readiness probe keeps failing, so the pods never count as Ready, the Deployment's endpoints never fill, updated-ready replicas never reach desired, and the rollout sits Progressing indefinitely (then Degraded once the deadline trips). This is distinct from case 1 — the container is alive; it just never reports ready.
Diagnose:
kubectl get pods -n <namespace> -l app=<app>
# READY column shows 0/1 while STATUS is Running -> readiness, not liveness
kubectl describe pod <pod> -n <namespace>
# Events: Readiness probe failed: HTTP probe failed with statuscode: 503 / connection refused
kubectl get endpoints <service> -n <namespace>
# empty ENDPOINTS confirms no pod is Ready behind the Service
Fix: correct the probe path/port/scheme, raise initialDelaySeconds or
failureThreshold if the app is just slow to warm up, or fix the application endpoint the
probe targets. Related:
Service has no endpoints.
3. Argo Rollouts canary paused awaiting promotion (intentional Progressing)
If you use Argo Rollouts, a canary that has reached a pause step is deliberately
holding for a manual promotion. ArgoCD detects a paused Rollout and marks the Application
as Suspended, not Progressing
(Argo Rollouts FAQ).
But mid-step — while the Rollout is shifting traffic or running analysis between pauses —
it reports Progressing, and that is expected, not a fault. Don't "fix" a healthy canary
that is simply mid-rollout; promote it (or let analysis complete).
Diagnose:
kubectl argo rollouts get rollout <name> -n <namespace>
# Status: ॥ Paused -> awaiting promotion (ArgoCD: Suspended)
# Status: ◌ Progressing -> mid-step, expected
Fix (if promotion is what you actually want):
kubectl argo rollouts promote <name> -n <namespace>
A genuinely failed/aborted Rollout maps to Degraded, not Progressing — see the Degraded page.
4. LoadBalancer Service waiting on an external IP
A Service of type LoadBalancer is Healthy only when its
status.loadBalancer.ingress list is non-empty — at least one hostname or IP
(ArgoCD: Resource Health).
Until the cloud provider provisions the load balancer, that list is empty and ArgoCD
reports the Service (and therefore the app) Progressing. If no cloud LB controller is
installed, or the provider is rejecting the request (quota, subnet, annotations), it stays
Progressing forever.
Diagnose:
kubectl get svc <service> -n <namespace>
# EXTERNAL-IP stuck on <pending> -> no ingress assigned yet
kubectl describe svc <service> -n <namespace>
# Events: EnsuringLoadBalancer / SyncLoadBalancerFailed <provider reason>
Fix: make sure a cloud load-balancer controller is running and the provider can fulfil
the request (quota, subnet tags, required annotations). On bare metal, install something
like MetalLB or switch the Service to NodePort/ClusterIP + an Ingress.
5. Custom resource (CRD) with no health check, or a Lua check that returns Progressing
ArgoCD ships no built-in health check for arbitrary CRDs. When it can't assess a resource, it has nothing to mark it Healthy with — and a custom Lua health check that doesn't explicitly return Healthy or Degraded defaults to Progressing (ArgoCD: Resource Health). So a CRD with no health customization, or one whose Lua logic falls through its conditions, sits Progressing indefinitely even though the underlying object may be perfectly fine.
Diagnose:
kubectl get <crd-kind> <name> -n <namespace> -o yaml
# Read .status / .status.conditions — does the operator report Ready=True?
# If the object is actually fine, the gap is ArgoCD's health assessment, not the workload.
Fix: add a resource customization (a resource.customizations.health entry keyed by the
CRD's group and kind) in argocd-cm, whose Lua reads the CRD's own status and returns Healthy when the operator
reports ready, Degraded on failure conditions — see
ArgoCD: Custom Health Checks.
Many common CRDs already have bundled checks in
ArgoCD's resource_customizations directory;
contribute or copy one rather than writing from scratch.
Recommended approach
Work the two axes separately and in order. First confirm it really is a health problem
and not a sync problem (argocd app get — read the Sync and Health columns independently;
re-syncing a Synced/Progressing app is wasted effort). Then identify the single child
resource that's Progressing and classify it: a Deployment in its deadline window (cases
1–2), an intentional canary pause (case 3), an infrastructure wait like a LoadBalancer
(case 4), or a CRD ArgoCD simply can't assess (case 5). The classification dictates the
fix — chasing the pod when the real gap is a missing Lua health check wastes the whole
investigation.
Tradeoff to weigh: you can mask a slow-but-legitimate rollout by raising
progressDeadlineSeconds, but that also delays the moment a genuinely failed rollout
turns Degraded and gets noticed. Tune it to your real startup time plus headroom, not to
silence an alert.
Validate the fix
kubectl rollout status deploy/<deploy> -n <namespace>
# "successfully rolled out" -> the rollout completed
kubectl get deploy <deploy> -n <namespace> \
-o jsonpath='{range .status.conditions[*]}{.type}={.status} {.reason}{"\n"}{end}'
# Want: Available=True and Progressing=True NewReplicaSetAvailable
argocd app get <app-name>
# Health column should read Healthy
Prevention
- Set realistic readiness probes and
initialDelaySecondsso slow-starting apps aren't read as stalled. - Keep
progressDeadlineSecondstuned to real startup time — long enough to avoid false Degraded, short enough to surface real failures. - Add (or adopt) custom health checks for every CRD you sync through ArgoCD, so a healthy custom resource doesn't sit Progressing forever.
- Alert on apps that stay Progressing beyond an expected window, so a stuck rollout is caught before it silently flips to Degraded.
How Intellira diagnoses this
Intellira reads the per-resource Sync and Health from the ArgoCD MCP server, keeps the
two axes separate, and follows the Progressing resource down into Kubernetes via the
read-only Kubernetes connector — reading the actual Deployment .status.conditions, pod
status, Service loadBalancer.ingress, and CRD status. That lets it tell a Deployment
still inside its progress deadline from a readiness probe that will never pass, a paused
canary, a LoadBalancer waiting on the cloud, or a CRD with no health check — and tie the
stall to the change that introduced it, with evidence. It never re-syncs, scales, or edits
anything.
Sources
- ArgoCD — Resource Health (statuses, Progressing definition, Deployment check, app roll-up, LoadBalancer, custom Lua default Progressing)
- ArgoCD — Custom Health Checks
- ArgoCD — bundled resource_customizations (health checks for common CRDs)
- Kubernetes — Failed Deployment & ProgressDeadlineExceeded
- Kubernetes API — DeploymentSpec progressDeadlineSeconds (defaults to 600s)
- Argo Rollouts — FAQ (paused Rollout maps to Suspended in ArgoCD)
By Intellira Engineering. AI-assisted draft; claims cited inline; last verified 2026-06-02. Pending technical review.