Troubleshooting
Known failure modes, grouped by area. Each entry is a symptom you’d actually see, followed by the cause and the fix.
Registry & images
Bundled Redis pod stuck ImagePullBackOff
Cause: Bitnami retired every version-pinned tag from the free
bitnami/* Docker Hub namespace. On chart versions that predate the fix,
the bundled Redis (and Postgres) subcharts’ default image references 404.
Fix: Upgrade to a current chart version, which points at the fixed image references. If you’re stuck on an older chart in the meantime, override the image repository directly via the escape hatch:
values_overrides = [
<<-EOT
redis:
image:
repository: bitnamilegacy/redis
EOT
]bitnamilegacy/* is Bitnami’s own migration path and mirrors the exact
same tags — it’s an unblock, not an upgrade. If you’d rather avoid this
class of problem entirely, set redis_mode = "external" and point at a
Redis instance you already run — Redis holds no durable state in Marshal,
so this only costs you a small managed instance.
Missing EBS CSI driver — bundled Redis PVC never binds (EKS)
Cause: Unlike GKE (whose PD CSI driver ships enabled by default), EKS
does not enable the EBS CSI driver out of the box. Without it, the PVC the
bundled Redis subchart requests never binds, and the pod stays Pending.
Fix: Install the EBS CSI driver as an EKS addon before installing
Marshal, or use the deploy_cluster = true provisioning path, which
installs it for you automatically.
Missing AWS Load Balancer Controller — Ingress never gets an address (EKS)
Cause: terraform apply (or argocd app sync) succeeds even without
the AWS Load Balancer Controller installed — but with nothing to reconcile
the Ingress, no load balancer is ever created, and the Ingress simply never
gets an address. There’s no error pointing at the missing controller.
Fix: Install the AWS Load Balancer Controller before installing
Marshal onto a reused cluster, or use the deploy_cluster = true
provisioning path, which installs it (with its own IAM role) for you.
Trying to set up an ECR pull-through cache for Marshal’s registry fails
Cause: ECR’s pull-through cache feature only accepts a fixed enum of known upstream registries (ECR, Docker Hub, GitHub/GitLab container registries, Quay, and a few others) — it validates the upstream URL against each type’s real hostname, so there’s no way to point it at a private registry like Marshal’s. This is a hard platform limitation, not a configuration mistake.
Fix: On EKS, use direct pull (the default) for a small install, or stand up a self-hosted proxy-cache (e.g. Harbor) in front of the registry if you need a shared cache — some EKS install flavors offer this as an opt-in. This limitation is specific to ECR; a GCP Artifact Registry remote repository does not have this restriction and can mirror the registry directly.
Ingress & TLS
Browser shows a certificate warning on every request
Cause: This is expected with the default TLS mode — a self-signed placeholder certificate (clearly marked in its subject so it’s unmistakable in a cert viewer) is generated automatically so HTTPS works out of the box, before you’ve supplied a real certificate.
Fix: Replace it once you’re ready, by switching to an inline certificate you provide, or to a Kubernetes Secret you manage yourself (e.g. via cert-manager). No other configuration needs to change either way. See Configuration Reference → Ingress & TLS.
Wildcard preview certificate request stuck pending, never issues
Cause: Two common causes, both specific to the wildcard cert a preview
domain needs (*.<preview_domain>):
- A GKE-managed certificate was used — Google’s managed-certificate product
does not support wildcard domains at all, and simply never provisions
for a
*.entry. - A cert-manager
ClusterIssuerwas configured with the default HTTP-01 challenge solver — HTTP-01 can only prove control of one exact hostname per challenge, which is structurally impossible for a wildcard (there’s no single backend that can answer the challenge for every possible subdomain).
Fix: Don’t use a managed-certificate product for a wildcard. With cert-manager, configure a DNS-01 solver (e.g. your cloud DNS provider) for the preview domain’s zone instead of HTTP-01. If you don’t need this today, the default self-signed placeholder certificate already covers the wildcard correctly out of the box — this only matters once you’re replacing it with a real, trusted certificate.
Secrets & sessions
Sessions suddenly all invalidated, or every stored credential stops decrypting.
Cause: secrets.provider was left at (or reset to) generated on a
GitOps-managed install. ArgoCD renders and applies manifests directly —
it never runs helm install, so the chart’s normal mechanism for
reusing an already-generated secret never runs. With generated, that
means a fresh random signing key and encryption key are minted on
every single sync: the signing key rotation invalidates every issued
session and API token, and the encryption key rotation makes every
previously-stored credential permanently undecryptable.
Fix: Set secrets.provider: existing (or externalSecrets) and seed
a stable secret once, outside of ArgoCD’s management — this is the
required posture for any GitOps-managed install, never generated. See
Configuration Reference → Secrets management.
There is no fix that recovers already-rotated credentials; the only way
out is to stop the rotation from recurring and have users re-enter
affected credentials.
Node pools & quotas
A node stays stuck Ready,SchedulingDisabled indefinitely during a
control-plane node pool change.
Cause: A single-node control-plane pool being resized or otherwise changed gets cordoned and drained before Terraform replaces it — but the chart sets a PodDisruptionBudget requiring at least one replica of every control-plane component to stay available. With only one node, draining it means evicting the last remaining replica of every component at once, which the PodDisruptionBudget will never allow. The node sits drained but un-replaced, and the operation makes no further progress.
Fix: Either scale the control-plane pool to at least two nodes before making the change (so pods have somewhere to go during the drain), or temporarily delete the affected PodDisruptionBudgets to unblock the drain — control-plane pods will briefly all reschedule at once, which is fine for a planned maintenance window but not something to do without intending it. A subsequent chart reconcile recreates the PodDisruptionBudgets automatically.
Pods stuck Pending with “Insufficient cpu”
Cause: The control-plane node pool’s machine type is too small. The chart’s own baseline (two replicas each of the core control-plane components) already adds up to several vCPU of requests before any autoscaling kicks in — a machine type sized for a “small” pool often doesn’t actually fit that baseline.
Fix: Use the recommended (larger) default machine type for the control-plane pool rather than downsizing it — the space/session node pool can be sized independently and much smaller per node.
Node pool scaling or a rolling update stalls with no clear error
Cause: A regional disk (or CPU) quota was silently exceeded — the
symptom looks identical to a generic stuck rollout, with nothing in
kubectl describe pointing at “quota.”
Fix: Check your cloud project’s compute and disk quotas (e.g.
regional SSD/EBS capacity, total CPU) against what your node pools’
machine_type × max_node_count × disk_size_gb actually require, before
sizing them. If you’re tight on disk quota specifically, prefer capping the
node count over increasing machine size further — the fix for insufficient
CPU is a bigger machine, the fix for insufficient disk quota is fewer
nodes.