Skip to main content

Overview

ArgoCD renders Helm templates client-side before applying them to the cluster. Helm’s lookup function — which the chart uses to persist auto-generated secrets across upgrades — always returns an empty result in client-side rendering. This means every ArgoCD sync generates new random values for auto-generated secrets, breaking Rails database encryption and session continuity.

Affected Secrets

OIDC_PRIVATE_KEY is the only secret requiring block scalar YAML syntax because it is a multi-line RSA PEM string. All other secrets are single-line alphanumeric values. See the Configure in Values example — the | character on the OIDC_PRIVATE_KEY line is required.OIDC_KEY_ID and OIDC_PRIVATE_KEY must be rotated together. Changing one without the other breaks Workload Identity trust relationships (AWS IAM OIDC, GCP Workload Identity, Azure Federated Credentials) that pin to the key ID.
If any Active Record encryption key changes, all previously encrypted data becomes unreadable. This includes encrypted database columns that Rails cannot decrypt with new keys. Always pre-set these values before your first deployment.
UV_DEFAULT_INDEX is not auto-generated — it is auto-populated from your Replicated license in KOTS deployments only. When deploying via ArgoCD (direct Helm/OCI install), UV_DEFAULT_INDEX is never set by the chart. Where you provide it depends on which path below you choose:
  • Pre-Set All Secrets path (no ESO): add it under secrets: in your values file.
  • External Secrets Operator path (manageAutoGenerated: true): put the URL in your secret store and reference it via externalSecret.autoGeneratedPropertyMap — the secrets: block is ignored when ESO is enabled, so a value placed there will silently have no effect.
See the Secrets Reference for how to obtain the value.
Generate stable values and set them explicitly in your Helm values file. This bypasses lookup and randAlphaNum entirely.

Generate Secret Values

The easiest approach is to let the chart generate the values for you. Run helm template with your values file — since it renders client-side (just like ArgoCD), the chart’s auto-generation logic produces random secrets that you can extract and pin:
Replace <chart> with the path to the chart (e.g., ./helm or an OCI reference). This requires yq — install with brew install yq, snap install yq, or see the yq docs. This prints the auto-generated values in plain text. Copy them into your values file:

Configure in Values

Store these values in a sealed secret, SOPS-encrypted file, or your CI/CD platform’s secret management — never commit them to version control in plaintext.
If you prefer to generate values independently of the chart. The | tr -dc 'A-Za-z0-9' | head -c N filter mirrors the chart’s randAlphaNum output exactly — pure alphanumeric, no +/= from base64 padding that would otherwise drift from the values a non-ESO chart install produces.

Using ESO with ArgoCD

For deployments that want the external store (AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, Vault) as the canonical source of truth, see the dedicated External Secrets Operator guide. It covers fresh installs, migration paths from inline secrets or partial ESO setups, the backend key reference, and the pre-upgrade wait Job. When using ESO with ArgoCD specifically, the pre-upgrade hook sequencing the chart relies on works the same way under ArgoCD’s sync — ArgoCD respects Helm hook annotations and applies resources in the correct phase order.

Alternative: ArgoCD ignoreDifferences

You can configure ArgoCD to ignore changes to the Secret resource so that auto-generated values from the initial install are preserved:
This only works if the Secret already exists from a prior install. On a fresh deployment through ArgoCD, the first sync will still generate random values — and those values will persist. However, any intentional secret changes in your values file will also be ignored. Pre-setting secrets is the more reliable approach.

Self-Signed TLS Certificates

The chart can auto-generate self-signed TLS certificates (web.tls.autoGenerate: true). These also use lookup for persistence and will regenerate on every ArgoCD sync. If you use application-level TLS with ArgoCD, provide your own certificates:
Or use cert-manager or your cloud provider’s certificate management instead of web.tls.autoGenerate.

Cloud Provider Workload Identity: crewai-crews Namespace

GCP, Azure, and AWS (IRSA) all require the default ServiceAccount in the crewai-crews namespace to be annotated for Workload Identity (GKE), Azure Workload Identity (AKS), or IRSA (EKS). The chart creates this namespace via a Helm Job, so the namespace does not exist until after the first sync completes. Two-Application pattern (recommended): Create a separate ArgoCD Application that runs after the main CrewAI Application and applies only the SA annotation:
Create manifests/crewai-crews-sa/sa.yaml: GCP (GKE Workload Identity):
Azure (Workload Identity):
AWS (IRSA):
For AWS IRSA, the IAM role trust policy must also include system:serviceaccount:crewai-crews:default in addition to the annotation. See Post-Install: IRSA Trust Policy for Crew Build Pods.
The first sync will fail to apply the SA annotation (namespace does not yet exist). After the main CrewAI Application syncs successfully and the namespace exists, re-sync the annotation Application.

Dependency Ordering with Sync Waves

Do NOT add argocd.argoproj.io/sync-wave annotations to your Helm values.yaml. Helm has no podAnnotations top-level key — this is a silently-ignored unknown value. Sync wave annotations belong on ArgoCD Application manifests in your Git repository, not in chart values.
For deployments that require infrastructure dependencies (NGINX Ingress Controller, External Secrets Operator) to be ready before CrewAI starts, use multiple ArgoCD Applications with sync waves:
The chart’s built-in Helm hooks (pre-install, post-install) already sequence the DB migration job before the main web/worker deployments — no additional sync wave configuration within the chart is needed for internal ordering.
OIDC_PRIVATE_KEY in the secrets table requires block scalar YAML syntax because it is a multi-line RSA PEM string, unlike all other secrets in the table which are single-line alphanumeric strings. See the Configure in Values example above for the correct | block scalar syntax.