> ## Documentation Index
> Fetch the complete documentation index at: https://enterprise-docs.crewai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# GCP + WorkOS + Wharf + Studio V2

> Complete deployment guide for CrewAI Enterprise on GKE with Cloud SQL, GCS, Artifact Registry, WorkOS SSO, Wharf trace collection, and Studio V2.

## Overview

This guide covers a complete, production-ready deployment of CrewAI Enterprise on Google Kubernetes Engine with the following stack:

* **GKE** with Workload Identity (no static credentials)
* **Cloud SQL for PostgreSQL 16** via Cloud SQL Auth Proxy
* **GCS** for object storage with IAM-signed URLs
* **Artifact Registry** for crew container images
* **NGINX Ingress** for TLS termination
* **WorkOS AuthKit** for SSO
* **Wharf** for OTLP trace collection (enabled by default)
* **Studio V2** (post-install configuration — no Helm values exist for this)

Work through each section in order. The Helm install cannot succeed until all infrastructure and auth prerequisites are complete.

***

## Prerequisites Checklist

Complete every item before running `helm install`.

**GCP Infrastructure**

* [ ] GKE cluster running Kubernetes 1.28+ with Workload Identity enabled
* [ ] `gcloud` CLI and `kubectl` configured against the target cluster
* [ ] Helm 3.10+ installed
* [ ] Required GCP APIs enabled (see below)

**Cloud SQL**

* [ ] Cloud SQL for PostgreSQL 16 instance created
* [ ] `cloudsql.iam_authentication=on` flag set on the instance
* [ ] All four databases created: `crewai_plus_production`, `crewai_plus_cable_production`, `crewai_plus_oauth_production`, `wharf`
* [ ] IAM database user created and privileges granted on all four databases

**GCS**

* [ ] GCS bucket created for object storage
* [ ] `roles/storage.objectAdmin` and `roles/iam.serviceAccountTokenCreator` granted to the GSA

**Artifact Registry**

* [ ] Docker repository created with **mutable tags** (CrewAI overwrites tags)
* [ ] `roles/artifactregistry.writer` granted to the GSA
* [ ] Node pool compute SA granted `roles/artifactregistry.reader`

**WorkOS**

* [ ] WorkOS application created with redirect URI `https://<YOUR_DOMAIN>/auth/workos/callback`
* [ ] WorkOS API key, Client ID, and AuthKit domain noted
* [ ] 32-character cookie password generated

**DNS / TLS**

* [ ] DNS record pointing `<YOUR_DOMAIN>` to your NGINX ingress IP or reserved static IP
* [ ] TLS certificate in a Kubernetes Secret named `crewai-tls` in the `crewai` namespace

***

## Step 1: Enable Required GCP APIs

```bash theme={null}
gcloud services enable \
  container.googleapis.com \
  sqladmin.googleapis.com \
  storage-api.googleapis.com \
  artifactregistry.googleapis.com \
  iam.googleapis.com
```

***

## Step 2: Create the GCP Service Account

All CrewAI workloads share a single GSA mapped to the Kubernetes ServiceAccount via Workload Identity.

```bash theme={null}
export GCP_PROJECT_ID="<YOUR_PROJECT_ID>"
export GCP_REGION="<YOUR_REGION>"          # e.g., us-central1
export SQL_INSTANCE="<YOUR_SQL_INSTANCE>"  # e.g., crewai-db
export GSA_NAME="crewai-platform"
export GKE_NAMESPACE="crewai"
export KSA_NAME="crewai-sa"               # chart default when release name is "crewai-platform"

gcloud iam service-accounts create $GSA_NAME \
  --display-name="CrewAI Platform" \
  --project=$GCP_PROJECT_ID
```

<Warning>
  The Workload Identity binding in Step 4 references `KSA_NAME`. The chart creates a Kubernetes ServiceAccount named `{release-name}-sa`. This guide uses `helm install crewai-platform`, which produces `crewai-platform-sa`. If your release name differs, update `KSA_NAME` to match, or pin `serviceAccount.name: crewai-platform-sa` in your Helm values.
</Warning>

***

## Step 3: Grant IAM Roles

```bash theme={null}
# GCS object storage read/write
gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \
  --member="serviceAccount:${GSA_NAME}@${GCP_PROJECT_ID}.iam.gserviceaccount.com" \
  --role="roles/storage.objectAdmin"

# Signed URL generation via IAM signBlob (required for Workload Identity)
gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \
  --member="serviceAccount:${GSA_NAME}@${GCP_PROJECT_ID}.iam.gserviceaccount.com" \
  --role="roles/iam.serviceAccountTokenCreator"

# Artifact Registry — push/pull crew images
gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \
  --member="serviceAccount:${GSA_NAME}@${GCP_PROJECT_ID}.iam.gserviceaccount.com" \
  --role="roles/artifactregistry.writer"

# Cloud SQL Auth Proxy connection
gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \
  --member="serviceAccount:${GSA_NAME}@${GCP_PROJECT_ID}.iam.gserviceaccount.com" \
  --role="roles/cloudsql.client"

# Cloud SQL IAM database authentication
gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \
  --member="serviceAccount:${GSA_NAME}@${GCP_PROJECT_ID}.iam.gserviceaccount.com" \
  --role="roles/cloudsql.instanceUser"

# Node pool compute SA — crew pod image pulls from Artifact Registry
GCE_SA="$(gcloud projects describe $GCP_PROJECT_ID --format='value(projectNumber)')-compute@developer.gserviceaccount.com"
gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \
  --member="serviceAccount:${GCE_SA}" \
  --role="roles/artifactregistry.reader"
```

***

## Step 4: Bind Workload Identity

```bash theme={null}
# Platform namespace — web, worker, buildkit daemon
gcloud iam service-accounts add-iam-policy-binding \
  ${GSA_NAME}@${GCP_PROJECT_ID}.iam.gserviceaccount.com \
  --role="roles/iam.workloadIdentityUser" \
  --member="serviceAccount:${GCP_PROJECT_ID}.svc.id.goog[${GKE_NAMESPACE}/${KSA_NAME}]"

# Crews namespace — build pods that push images to Artifact Registry
gcloud iam service-accounts add-iam-policy-binding \
  ${GSA_NAME}@${GCP_PROJECT_ID}.iam.gserviceaccount.com \
  --role="roles/iam.workloadIdentityUser" \
  --member="serviceAccount:${GCP_PROJECT_ID}.svc.id.goog[crewai-crews/default]"
```

<Warning>
  The `[NAMESPACE/KSA_NAME]` strings must exactly match the Kubernetes namespace and ServiceAccount name. A typo here causes a silent Workload Identity failure — pods start normally but all GCP API calls return 403.
</Warning>

***

## Step 5: Create Cloud SQL Databases

Enable IAM authentication on the instance, then create all four required databases.

```bash theme={null}
# Enable IAM authentication (off by default — required for autoIamAuthn)
gcloud sql instances patch $SQL_INSTANCE \
  --database-flags=cloudsql.iam_authentication=on

# Create all four databases
gcloud sql databases create crewai_plus_production --instance=$SQL_INSTANCE
gcloud sql databases create crewai_plus_cable_production --instance=$SQL_INSTANCE
gcloud sql databases create crewai_plus_oauth_production --instance=$SQL_INSTANCE
gcloud sql databases create wharf --instance=$SQL_INSTANCE
```

<Warning>
  `crewai_plus_oauth_production` must be created explicitly. The chart default for `POSTGRES_OAUTH_DB` is `oauth_db` — if you do not override it in `envVars`, the OAuth service attempts to connect to a database that does not exist and fails silently at startup.

  Similarly, `wharf` must be pre-created. The Wharf trace collector expects this database to exist on startup.
</Warning>

### Create the IAM Database User

```bash theme={null}
gcloud sql users create ${GSA_NAME}@${GCP_PROJECT_ID}.iam \
  --instance=$SQL_INSTANCE \
  --type=CLOUD_IAM_SERVICE_ACCOUNT
```

### Grant Privileges

Connect to the Cloud SQL instance and grant the IAM user access to all four databases. Replace `GSA_NAME@GCP_PROJECT_ID.iam` with your actual value (e.g., `crewai-platform@my-project.iam`):

```bash theme={null}
gcloud sql connect $SQL_INSTANCE --user=postgres --database=postgres
```

```sql theme={null}
-- Database-level access
GRANT ALL PRIVILEGES ON DATABASE crewai_plus_production TO "crewai-platform@<PROJECT>.iam";
GRANT ALL PRIVILEGES ON DATABASE crewai_plus_cable_production TO "crewai-platform@<PROJECT>.iam";
GRANT ALL PRIVILEGES ON DATABASE crewai_plus_oauth_production TO "crewai-platform@<PROJECT>.iam";
GRANT ALL PRIVILEGES ON DATABASE wharf TO "crewai-platform@<PROJECT>.iam";

-- Schema/table/sequence access — required for Rails migrations
\c crewai_plus_production
GRANT ALL ON SCHEMA public TO "crewai-platform@<PROJECT>.iam";
GRANT ALL ON ALL TABLES IN SCHEMA public TO "crewai-platform@<PROJECT>.iam";
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO "crewai-platform@<PROJECT>.iam";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "crewai-platform@<PROJECT>.iam";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "crewai-platform@<PROJECT>.iam";

\c crewai_plus_cable_production
GRANT ALL ON SCHEMA public TO "crewai-platform@<PROJECT>.iam";
GRANT ALL ON ALL TABLES IN SCHEMA public TO "crewai-platform@<PROJECT>.iam";
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO "crewai-platform@<PROJECT>.iam";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "crewai-platform@<PROJECT>.iam";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "crewai-platform@<PROJECT>.iam";

\c crewai_plus_oauth_production
GRANT ALL ON SCHEMA public TO "crewai-platform@<PROJECT>.iam";
GRANT ALL ON ALL TABLES IN SCHEMA public TO "crewai-platform@<PROJECT>.iam";
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO "crewai-platform@<PROJECT>.iam";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "crewai-platform@<PROJECT>.iam";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "crewai-platform@<PROJECT>.iam";

\c wharf
GRANT ALL ON SCHEMA public TO "crewai-platform@<PROJECT>.iam";
GRANT ALL ON ALL TABLES IN SCHEMA public TO "crewai-platform@<PROJECT>.iam";
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO "crewai-platform@<PROJECT>.iam";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "crewai-platform@<PROJECT>.iam";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "crewai-platform@<PROJECT>.iam";
```

***

## Step 6: Create GCS Bucket

```bash theme={null}
export GCS_BUCKET="<YOUR_BUCKET_NAME>"  # e.g., crewai-prod-storage

gsutil mb -p $GCP_PROJECT_ID -l $GCP_REGION -b on gs://$GCS_BUCKET
gsutil versioning set on gs://$GCS_BUCKET
```

***

## Step 7: Create Artifact Registry Repository

<Warning>
  The repository must have **mutable tags**. CrewAI overwrites image tags on each crew deployment. Immutable tag repositories cause push failures.
</Warning>

```bash theme={null}
export AR_REPO="crewai"

gcloud artifacts repositories create $AR_REPO \
  --repository-format=docker \
  --location=$GCP_REGION \
  --description="CrewAI crew images"

# Confirm the registry prefix you will use in CREW_IMAGE_REGISTRY_OVERRIDE:
echo "${GCP_REGION}-docker.pkg.dev/${GCP_PROJECT_ID}/${AR_REPO}"
```

The `CREW_IMAGE_REGISTRY_OVERRIDE` value is this prefix **without** the `/crewai-enterprise` suffix — the platform appends that automatically.

***

## Step 8: Configure WorkOS

<Warning>
  **WORKOS\_API\_KEY and WORKOS\_COOKIE\_PASSWORD must be placed under `envVars:`, not `secrets:`.** This is a known chart limitation. Values placed under `secrets:` are silently absent from pods — WorkOS authentication fails with no clear error message.

  After install, verify both variables are present in the pod:

  ```bash theme={null}
  kubectl exec -it deploy/crewai-web -- env | grep -E 'WORKOS_API_KEY|WORKOS_COOKIE_PASSWORD'
  ```
</Warning>

1. In the WorkOS Dashboard, navigate to **Applications → Create Application** (name it e.g., `CrewAI Platform`)
2. Add redirect URI: `https://<YOUR_DOMAIN>/auth/workos/callback`
3. Note your **Client ID** from the application main page
4. Navigate to **Developer → Domains** and note your **AuthKit domain**
5. Navigate to the **main API Keys page** and copy your API key (starts with `sk_live_`)
6. Generate a cookie password (must be 32 characters or fewer):

```bash theme={null}
openssl rand -base64 32 | cut -c -32
```

The `| cut -c -32` truncation is mandatory. WorkOS silently rejects cookie passwords longer than 32 characters — the install succeeds but all authentication attempts fail.

***

## Complete values.yaml

Replace all `<PLACEHOLDER>` values before running `helm install`.

```yaml values.yaml theme={null}
# ============================================================
# CrewAI Enterprise — GCP + WorkOS + Wharf + Studio V2
# ============================================================

# Workload Identity: maps the Kubernetes ServiceAccount to the GCP Service Account
serviceAccount:
  annotations:
    iam.gke.io/gcp-service-account: crewai-platform@<YOUR_PROJECT_ID>.iam.gserviceaccount.com

rbac:
  create: true  # creates the crewai-platform-sa Kubernetes ServiceAccount

# Namespace for crew workloads
crewNamespace: "crewai-crews"

# Image pull credentials for platform images from images.crewai.com (Replicated proxy)
# Required for direct Helm installs; not needed for KOTS installs
image:
  registries:
    - host: "images.crewai.com"
      username: "<YOUR_LICENSE_EMAIL>"
      password: "<YOUR_REPLICATED_LICENSE_TOKEN>"

# ─────────────────────────────────────────
# Disable bundled services — use GCP managed
# ─────────────────────────────────────────
postgres:
  enabled: false

minio:
  enabled: false

# ─────────────────────────────────────────
# Cloud SQL Auth Proxy
# ─────────────────────────────────────────
cloudSqlProxy:
  enabled: true
  instanceConnectionName: "<YOUR_PROJECT_ID>:<YOUR_REGION>:<YOUR_SQL_INSTANCE>"  # e.g., my-project:us-central1:crewai-db
  port: 5432
  privateIp: true        # use private IP (recommended for VPC-peered instances)
  autoIamAuthn: true     # IAM-based DB auth via Workload Identity — no DB_PASSWORD needed

# ─────────────────────────────────────────
# Environment variables
# ─────────────────────────────────────────
envVars:
  # Application
  APPLICATION_HOST: "<YOUR_DOMAIN>"             # e.g., crewai.company.com
  RAILS_LOG_LEVEL: "info"

  # Database (via Cloud SQL Auth Proxy listening on 127.0.0.1)
  DB_HOST: "127.0.0.1"
  DB_PORT: "5432"
  DB_USER: "crewai-platform@<YOUR_PROJECT_ID>.iam"  # GSA email WITHOUT .gserviceaccount.com suffix
  POSTGRES_DB: "crewai_plus_production"
  POSTGRES_CABLE_DB: "crewai_plus_cable_production"
  POSTGRES_OAUTH_DB: "crewai_plus_oauth_production"  # MUST override — chart default is "oauth_db"

  # GCS object storage
  STORAGE_SERVICE: "google"
  GCS_BUCKET: "<YOUR_BUCKET_NAME>"
  GCS_REGION: "<YOUR_REGION>"
  GCS_IAM_SIGNING: "true"  # REQUIRED for Workload Identity — omitting causes Google::Cloud::Storage::SignedUrlUnavailable at runtime

  # Artifact Registry for crew images
  # Platform appends /crewai-enterprise automatically — do NOT include it here
  CREW_IMAGE_REGISTRY_OVERRIDE: "<YOUR_REGION>-docker.pkg.dev/<YOUR_PROJECT_ID>/crewai"

  # WorkOS SSO
  # CRITICAL: these MUST be under envVars — NOT under secrets
  # Values under secrets: are silently absent from pods (chart bug)
  AUTH_PROVIDER: "workos"
  WORKOS_CLIENT_ID: "<YOUR_WORKOS_CLIENT_ID>"         # From WorkOS Application main page
  WORKOS_AUTHKIT_DOMAIN: "<YOUR_ORG>.authkit.app"     # From WorkOS Developer → Domains
  WORKOS_API_KEY: "<YOUR_WORKOS_API_KEY>"             # Starts with sk_live_ — must be under envVars
  WORKOS_COOKIE_PASSWORD: "<YOUR_32_CHAR_PASSWORD>"   # Max 32 chars — must be under envVars

# ─────────────────────────────────────────
# Secrets (sensitive values rendered as Kubernetes Secrets)
# ─────────────────────────────────────────
secrets:
  SECRET_KEY_BASE: "<YOUR_SECRET_KEY_BASE>"  # generate: openssl rand -hex 64

# DB_PASSWORD intentionally omitted — not needed with autoIamAuthn: true

# ─────────────────────────────────────────
# Wharf (OTLP trace collection)
# ─────────────────────────────────────────
wharf:
  enabled: true  # true is the chart default; included here for explicitness
  # Wharf uses the same DB_HOST/DB_PORT/DB_USER as the main app (via Cloud SQL Auth Proxy)
  # postgres.wharfDatabase defaults to "wharf" — matches the database you created above

# ─────────────────────────────────────────
# Ingress (NGINX)
# ─────────────────────────────────────────
web:
  enableSslFromPuma: false  # MUST be false when NGINX handles TLS termination
  replicaCount: 2
  resources:
    requests:
      cpu: "1000m"
      memory: "6Gi"
    limits:
      cpu: "6"
      memory: "12Gi"
  ingress:
    enabled: true
    className: "nginx"
    host: "<YOUR_DOMAIN>"
    nginx:
      tls:
        enabled: true
        secretName: "crewai-tls"  # Kubernetes TLS secret in the crewai namespace

# ─────────────────────────────────────────
# Worker
# ─────────────────────────────────────────
worker:
  replicaCount: 2
  resources:
    requests:
      cpu: "1000m"
      memory: "6Gi"
    limits:
      cpu: "6"
      memory: "12Gi"

# ─────────────────────────────────────────
# BuildKit (crew image builds)
# ─────────────────────────────────────────
buildkit:
  enabled: true
  replicaCount: 1
  resources:
    requests:
      cpu: "500m"
      memory: "2Gi"
    limits:
      cpu: "4"
      memory: "8Gi"

# ─────────────────────────────────────────
# OAuth service (Built-in Integrations)
# Required for Google Workspace, Microsoft 365, HubSpot, etc.
# Not required for WorkOS authentication itself
# ─────────────────────────────────────────
oauth:
  enabled: true
  ingress:
    enabled: true
    className: "nginx"
    host: "<YOUR_DOMAIN>"
    pathPrefix: "/oauthsvc"
```

***

## Step 9: Install

```bash theme={null}
helm install crewai-platform \
  oci://registry.crewai.com/crewai/stable/crewai-platform \
  --values values.yaml \
  --namespace crewai \
  --create-namespace
```

Watch pods come up:

```bash theme={null}
kubectl get pods -n crewai -w
```

All pods should reach `Running` within a few minutes. If any pod shows `CrashLoopBackOff` or `Error`, check logs before proceeding:

```bash theme={null}
kubectl logs -n crewai deploy/crewai-web --tail=50
```

***

## Step 10: Post-Install Annotations

After the first install, the `crewai-crews` namespace is created automatically. Annotate its default ServiceAccount so build pods can authenticate to Artifact Registry via Workload Identity:

```bash theme={null}
kubectl annotate serviceaccount default -n crewai-crews --overwrite \
  iam.gke.io/gcp-service-account=crewai-platform@<YOUR_PROJECT_ID>.iam.gserviceaccount.com
```

Without this annotation, all crew image builds fail with a permission denied error when pushing to Artifact Registry.

***

## Step 11: Required Post-Install Commands

Run these commands in order. Each must complete before proceeding to the next.

```bash theme={null}
# Initialize the internal organization (required before any other setup)
kubectl exec -it deploy/crewai-web -n crewai -- bin/rails studio:install_internal_organization

# Set up default roles and permissions
kubectl exec -it deploy/crewai-web -n crewai -- bin/rails factory:setup_permissions_defaults

# Add the first owner (replace with your actual admin email)
kubectl exec -it deploy/crewai-web -n crewai -- bin/rails 'factory:add_owner[2,admin@company.com]'

# Grant admin access for WorkOS — run AFTER the user has logged in at least once via WorkOS SSO
kubectl exec -it deploy/crewai-web -n crewai -- bin/rails 'factory:grant_admin[admin@company.com]'
```

<Note>
  `factory:grant_admin` is the correct command for WorkOS SSO. This is distinct from Entra ID deployments, which use Azure App Roles instead.

  `factory:grant_admin` will fail if the user has not yet authenticated through WorkOS. Have the admin log in via the web UI first, then run the command.
</Note>

***

## Step 12: Enable Studio V2

<Warning>
  Studio V2 cannot be configured in values.yaml. Adding `studioV2.enabled`, `STUDIO_V2_ENABLED`, or any similar key has no effect — Helm silently ignores unrecognized keys. Setup is always post-install via the UI and kubectl.
</Warning>

### 12a: Create the LLM Connection (UI)

1. Navigate to **Settings → LLM Connections**
2. Click **New Connection**
3. Set the name to exactly `studio-v2` (lowercase, no spaces — the install commands in 12c look up this name specifically)
4. Select your LLM provider, enter the model name and API key
5. Click **Save**

### 12b: Set as Default Connection (UI)

1. Navigate to **Settings → Crew Studio**
2. Under **Default Connection**, select `studio-v2`
3. Click **Save**

### 12c: Run Install Commands

Run these in order. Each must complete successfully before running the next.

```bash theme={null}
# Install the Studio agent
# Fails if the studio-v2 LLM Connection does not exist yet — complete 12a first
kubectl exec -it deploy/crewai-web -n crewai -- bin/rails studio:agent:install

# Sync CrewAI and enterprise tools
kubectl exec -it deploy/crewai-web -n crewai -- \
  bin/rails studio:tools:sync_crewai_tools studio:tools:sync_enterprise_tools

# Install the Studio runner
kubectl exec -it deploy/crewai-web -n crewai -- bin/rails studio:runner:install
```

***

## Step 13: Verify

### Platform health

```bash theme={null}
# All pods running
kubectl get pods -n crewai

# Web pod responding
kubectl exec -it deploy/crewai-web -n crewai -- bin/rails runner "puts 'ok'"
```

### WorkOS authentication

Navigate to `https://<YOUR_DOMAIN>`. You should be redirected to the WorkOS AuthKit login page.

If authentication fails, check that both WorkOS keys are present in the pod:

```bash theme={null}
kubectl exec -it deploy/crewai-web -n crewai -- env | grep -E 'WORKOS_API_KEY|WORKOS_COOKIE_PASSWORD'
```

If either variable is missing, confirm they are under `envVars:` in values.yaml — not `secrets:` — and run `helm upgrade`.

### WorkOS redirect URI

If you see a "redirect\_uri\_mismatch" error in the WorkOS dashboard, confirm the redirect URI in WorkOS exactly matches:

```
https://<YOUR_DOMAIN>/auth/workos/callback
```

### GCS signed URLs

If file uploads or downloads fail with `SignedUrlUnavailable`, confirm:

```yaml theme={null}
envVars:
  GCS_IAM_SIGNING: "true"
```

And that the GSA has `roles/iam.serviceAccountTokenCreator`.

### Wharf trace collection

```bash theme={null}
kubectl get pods -n crewai | grep wharf
kubectl logs -n crewai deploy/crewai-wharf --tail=20
```

### Studio V2

```bash theme={null}
kubectl get deploy -n crewai | grep studio
kubectl logs -n crewai -l app=studio-assistant --tail=20
```

Both `studio-assistant` and `studio-runner` deployments should show `Running`.

***

## Troubleshooting

### WorkOS auth fails with no clear error

Both `WORKOS_API_KEY` and `WORKOS_COOKIE_PASSWORD` are absent from the pod. Move them from `secrets:` to `envVars:` and run `helm upgrade`.

### WORKOS\_COOKIE\_PASSWORD rejected silently

The password exceeds 32 characters. Regenerate: `openssl rand -base64 32 | cut -c -32`.

### GCS signed URLs fail at runtime (install succeeds)

`GCS_IAM_SIGNING` is missing from `envVars`. This error only appears when users try to access uploaded files — the install itself succeeds, making it easy to miss. Add `GCS_IAM_SIGNING: "true"` and run `helm upgrade`.

### OAuth service connects to wrong database

`POSTGRES_OAUTH_DB` is not set in `envVars`. The chart default `oauth_db` does not match the `crewai_plus_oauth_production` database you created. Add the override explicitly.

### Cloud SQL connection refused on 127.0.0.1

The Cloud SQL Auth Proxy sidecar is not running. Check:

```bash theme={null}
kubectl logs deploy/crewai-web -n crewai -c cloud-sql-proxy
```

Verify `cloudSqlProxy.instanceConnectionName` matches the output of:

```bash theme={null}
gcloud sql instances describe $SQL_INSTANCE --format='value(connectionName)'
```

### IAM database auth fails

Check that:

1. `cloudsql.iam_authentication=on` flag is set on the instance
2. `DB_USER` is the GSA email **without** `.gserviceaccount.com` (e.g., `crewai-platform@my-project.iam`)
3. The IAM database user exists: `gcloud sql users list --instance=$SQL_INSTANCE`

### 502 errors from NGINX

`web.enableSslFromPuma` is not set to `false`. When NGINX handles TLS termination, Puma must not also attempt SSL. Set `enableSslFromPuma: false` in your `web:` block and run `helm upgrade`.

### Crew image pushes fail (unauthorized)

The `default` ServiceAccount in `crewai-crews` is missing the Workload Identity annotation. Run:

```bash theme={null}
kubectl annotate serviceaccount default -n crewai-crews --overwrite \
  iam.gke.io/gcp-service-account=crewai-platform@<YOUR_PROJECT_ID>.iam.gserviceaccount.com
```

### studio:agent:install fails

The `studio-v2` LLM Connection does not exist yet. Complete Steps 12a and 12b in the UI, then re-run the command.
