Skip to main content

Overview

This guide walks through a complete production deployment of CrewAI Platform on GKE using:
  • Cloud SQL for PostgreSQL (four databases including Wharf) via Cloud SQL Auth Proxy with IAM authentication
  • Google Cloud Storage for object storage via Workload Identity
  • Artifact Registry for crew container images
  • Microsoft Entra ID for SSO authentication
  • Wharf for OTLP trace collection (enabled by default)
  • Studio V2 configured post-install
This is a self-contained guide. All required Helm values are included — no cross-references to other guides are needed for a complete deployment.
This guide assumes:
  • A GKE cluster running Kubernetes 1.28+ with Workload Identity enabled
  • Gateway API enabled on the cluster
  • gcloud CLI, kubectl, and Helm 3.10+ installed
  • An active Microsoft Entra ID (Azure AD) tenant
  • Basic familiarity with GCP services (Cloud SQL, GCS, Artifact Registry)

Prerequisites Checklist

Complete each item before running helm install:
  • GKE cluster with Workload Identity enabled
  • Gateway API enabled (gcloud container clusters update --gateway-api=standard)
  • GCP APIs enabled (see below)
  • GCP Service Account created with required IAM roles
  • Workload Identity binding created for platform and crews namespaces
  • Cloud SQL instance created with IAM authentication enabled
  • All four databases created: crewai_plus_production, crewai_plus_cable_production, crewai_plus_oauth_production, wharf
  • SQL privileges granted to IAM user on all four databases
  • GCS bucket created
  • Artifact Registry repository created (mutable tags, ends in /crewai-enterprise)
  • Entra ID App Registration complete with redirect URI configured
  • Redirect URI https://<YOUR_DOMAIN>/auth/entra_id/callback registered in Azure before helm install
  • factory-admin App Role created in Azure and assigned to admin users
  • values.yaml assembled with all placeholders filled in
The Entra ID redirect URI must be registered in Azure before running helm install. Authentication will fail at login if it is missing. The redirect URI format is exactly: https://<YOUR_DOMAIN>/auth/entra_id/callback

Part 1: GCP Infrastructure Setup

Enable Required APIs

gcloud services enable \
  container.googleapis.com \
  sqladmin.googleapis.com \
  storage-api.googleapis.com \
  artifactregistry.googleapis.com \
  iam.googleapis.com \
  certificatemanager.googleapis.com

Set Shell Variables

export GCP_PROJECT_ID="<YOUR_GCP_PROJECT_ID>"
export GCP_REGION="<YOUR_REGION>"            # e.g. us-central1
export GSA_NAME="crewai-platform"            # GCP Service Account name
export GKE_NAMESPACE="crewai"               # Kubernetes namespace for the Helm release
export KSA_NAME="crewai-sa"                 # Chart default SA name when release name is 'crewai'
export GKE_CLUSTER="<YOUR_CLUSTER_NAME>"
export SQL_INSTANCE="crewai-db"
export AR_REPO="crewai"
export GCS_BUCKET="crewai-prod-storage"
KSA_NAME must match the ServiceAccount the chart creates. The chart default is {release-name}-sa. This guide uses release name crewai, producing crewai-sa. If your release name differs, either update KSA_NAME to match, or pin serviceAccount.name: crewai-sa in your Helm values.

Create GCP Service Account

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

Grant IAM Roles

# 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"

# GCS — 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 container 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 — connect via Auth Proxy
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 GCE SA — pull crew images 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"

Bind Workload Identity

# Platform namespace — web, worker, buildkit daemon pods
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]"

Part 2: Cloud SQL Setup

Create the Cloud SQL Instance

gcloud sql instances create $SQL_INSTANCE \
  --database-version=POSTGRES_16 \
  --tier=db-custom-2-16384 \
  --region=$GCP_REGION \
  --storage-size=100GB \
  --storage-type=SSD \
  --storage-auto-increase \
  --no-assign-ip

Enable IAM Authentication on the Instance

gcloud sql instances patch $SQL_INSTANCE \
  --database-flags=cloudsql.iam_authentication=on
cloudsql.iam_authentication is off by default. The Cloud SQL Auth Proxy cannot authenticate via IAM tokens until this flag is enabled — pods will fail with authentication errors.

Create the Four Required 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
The database names above must be used exactly as shown. In particular, crewai_plus_oauth_production overrides the chart default of oauth_db. You must set POSTGRES_OAUTH_DB: "crewai_plus_oauth_production" in envVars: — if omitted, the OAuth service attempts to connect to a database that does not exist and fails silently.

Create the IAM Database User

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

Grant SQL Privileges

Connect to the instance as the postgres superuser:
gcloud sql connect $SQL_INSTANCE --user=postgres --database=postgres
Then run the following SQL (replace GSA_NAME@GCP_PROJECT_ID.iam with your actual IAM user, e.g., crewai-platform@my-project.iam):
-- Grant connect privileges
GRANT ALL PRIVILEGES ON DATABASE crewai_plus_production TO "GSA_NAME@GCP_PROJECT_ID.iam";
GRANT ALL PRIVILEGES ON DATABASE crewai_plus_cable_production TO "GSA_NAME@GCP_PROJECT_ID.iam";
GRANT ALL PRIVILEGES ON DATABASE crewai_plus_oauth_production TO "GSA_NAME@GCP_PROJECT_ID.iam";
GRANT ALL PRIVILEGES ON DATABASE wharf TO "GSA_NAME@GCP_PROJECT_ID.iam";

-- Schema and table privileges for each database
\c crewai_plus_production
GRANT ALL ON SCHEMA public TO "GSA_NAME@GCP_PROJECT_ID.iam";
GRANT ALL ON ALL TABLES IN SCHEMA public TO "GSA_NAME@GCP_PROJECT_ID.iam";
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO "GSA_NAME@GCP_PROJECT_ID.iam";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "GSA_NAME@GCP_PROJECT_ID.iam";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "GSA_NAME@GCP_PROJECT_ID.iam";

\c crewai_plus_cable_production
GRANT ALL ON SCHEMA public TO "GSA_NAME@GCP_PROJECT_ID.iam";
GRANT ALL ON ALL TABLES IN SCHEMA public TO "GSA_NAME@GCP_PROJECT_ID.iam";
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO "GSA_NAME@GCP_PROJECT_ID.iam";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "GSA_NAME@GCP_PROJECT_ID.iam";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "GSA_NAME@GCP_PROJECT_ID.iam";

\c crewai_plus_oauth_production
GRANT ALL ON SCHEMA public TO "GSA_NAME@GCP_PROJECT_ID.iam";
GRANT ALL ON ALL TABLES IN SCHEMA public TO "GSA_NAME@GCP_PROJECT_ID.iam";
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO "GSA_NAME@GCP_PROJECT_ID.iam";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "GSA_NAME@GCP_PROJECT_ID.iam";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "GSA_NAME@GCP_PROJECT_ID.iam";

\c wharf
GRANT ALL ON SCHEMA public TO "GSA_NAME@GCP_PROJECT_ID.iam";
GRANT ALL ON ALL TABLES IN SCHEMA public TO "GSA_NAME@GCP_PROJECT_ID.iam";
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO "GSA_NAME@GCP_PROJECT_ID.iam";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "GSA_NAME@GCP_PROJECT_ID.iam";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "GSA_NAME@GCP_PROJECT_ID.iam";

Part 3: GCS and Artifact Registry

Create GCS Bucket

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

Create Artifact Registry Repository

gcloud artifacts repositories create $AR_REPO \
  --repository-format=docker \
  --location=$GCP_REGION \
  --description="CrewAI Platform crew images"
The CREW_IMAGE_REGISTRY_OVERRIDE value for this repository is: ${GCP_REGION}-docker.pkg.dev/${GCP_PROJECT_ID}/${AR_REPO} For example: us-central1-docker.pkg.dev/my-project/crewai
Do NOT include /crewai-enterprise in CREW_IMAGE_REGISTRY_OVERRIDE. The platform appends /crewai-enterprise automatically. Including the suffix causes image push failures to a double-suffixed path.Also ensure the Artifact Registry repository has mutable tags. CrewAI overwrites image tags for crew versions — immutable tags will cause build failures.

Enable Gateway API on the Cluster

gcloud container clusters update $GKE_CLUSTER \
  --gateway-api=standard \
  --region=$GCP_REGION \
  --project=$GCP_PROJECT_ID

Part 4: Microsoft Entra ID App Registration

Create the App Registration

  1. Go to portal.azure.com and navigate to Microsoft Entra ID → App registrations → New registration
  2. Set the application name (suggested: CrewAI)
  3. Under Supported account types, select Accounts in this organizational directory only
  4. Under Redirect URI, select platform Web and enter: https://<YOUR_DOMAIN>/auth/entra_id/callback
  5. Click Register
The redirect URI must be registered in Azure before running helm install. The exact format required is https://<YOUR_DOMAIN>/auth/entra_id/callback. Authentication will fail silently at login if this is missing or incorrect.

Collect Credentials

From the app’s Overview page, copy:
  • Application (client) ID → this is ENTRA_ID_CLIENT_ID
  • Directory (tenant) ID → this is ENTRA_ID_TENANT_ID

Create a Client Secret

  1. In the left sidebar under Manage, click Certificates & secrets
  2. Click New client secret, set a description and expiration, then click Add
  3. Copy the secret Value immediately — you cannot view it again after leaving the page
  4. This is ENTRA_ID_CLIENT_SECRET
  1. Navigate to Enterprise applications → All applications and select your app
  2. In the left sidebar under Security, click Permissions
  3. Click Grant admin consent for <your organization>
  4. Confirm that Microsoft Graph User.Read is granted

Configure App Roles

In the left sidebar under Manage, click App roles, then Create app role. Create both of the following roles:
Display nameValueAllowed member types
MembermemberUsers/Groups
Factory Adminfactory-adminUsers/Groups
Ensure “Do you want to enable this app role?” is checked for each role before saving.

Assign Users

  1. Go to Enterprise applications → <your app> → Users and groups
  2. Click Add user/group
  3. Assign regular users the Member role
  4. Assign admin users the Factory Admin role
With Entra ID, admin access is granted via the factory-admin App Role in Azure. Do not run the factory:grant_admin Rails task — that command is for WorkOS/Okta/local auth only. For Entra ID, assign the factory-admin App Role to the user in the Azure portal instead.

Part 5: Complete values.yaml

The following is a complete, production-ready values.yaml. Fill in all <PLACEHOLDER> values before deploying. Generate your SECRET_KEY_BASE:
openssl rand -base64 64 | tr -d '\n'
values.yaml
# ============================================================
# CrewAI Platform — GCP GKE + Entra ID + Wharf + Studio V2
# ============================================================

# ServiceAccount annotated for Workload Identity
serviceAccount:
  annotations:
    iam.gke.io/gcp-service-account: crewai-platform@<YOUR_GCP_PROJECT_ID>.iam.gserviceaccount.com

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

# Image pull credentials for images.crewai.com (Replicated proxy)
# Required for direct Helm installs — use your license email and token
image:
  registries:
    - host: "images.crewai.com"
      username: "<YOUR_LICENSE_EMAIL>"
      password: "<YOUR_REPLICATED_LICENSE_TOKEN>"

# Disable built-in PostgreSQL — using Cloud SQL
postgres:
  enabled: false

# Disable built-in MinIO — using GCS
minio:
  enabled: false

# -------------------------------------------------------
# Cloud SQL Auth Proxy (sidecar on web, worker, job pods)
# -------------------------------------------------------
cloudSqlProxy:
  enabled: true
  instanceConnectionName: "<YOUR_GCP_PROJECT_ID>:<YOUR_REGION>:<YOUR_SQL_INSTANCE>"  # e.g. my-project:us-central1:crewai-db
  port: 5432
  privateIp: true        # Use private IP for VPC-peered instances (recommended)
  autoIamAuthn: true     # IAM-based auth — no DB_PASSWORD needed

# -------------------------------------------------------
# Environment Variables (non-sensitive configuration)
# -------------------------------------------------------
envVars:
  # --- Database (Cloud SQL Auth Proxy) ---
  DB_HOST: "127.0.0.1"                        # Proxy runs as localhost sidecar
  DB_PORT: "5432"
  DB_USER: "crewai-platform@<YOUR_GCP_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_PROJECT_ID: "<YOUR_GCP_PROJECT_ID>"
  GCS_BUCKET: "<YOUR_GCS_BUCKET>"                       # e.g. crewai-prod-storage
  GCS_IAM_SIGNING: "true"                               # REQUIRED with Workload Identity — omitting causes SignedUrlUnavailable errors

  # --- Artifact Registry (crew images) ---
  CREW_IMAGE_REGISTRY_OVERRIDE: "<YOUR_REGION>-docker.pkg.dev/<YOUR_GCP_PROJECT_ID>/<YOUR_AR_REPO>"
  # Example: us-central1-docker.pkg.dev/my-project/crewai
  # Do NOT include /crewai-enterprise — the platform appends it automatically

  # --- Entra ID SSO ---
  AUTH_PROVIDER: "entra_id"                             # Exact value required — lowercase, underscore
  ENTRA_ID_CLIENT_ID: "<APPLICATION_CLIENT_ID>"         # From Azure App Registration Overview
  ENTRA_ID_TENANT_ID: "<DIRECTORY_TENANT_ID>"           # From Azure App Registration Overview
  # Note: CLIENT_ID and TENANT_ID are non-sensitive identifiers — they belong here in envVars, NOT in secrets

  # --- Application ---
  APPLICATION_HOST: "<YOUR_DOMAIN>"                     # e.g. crewai.your-company.com (no https://)
  RAILS_LOG_LEVEL: "info"

# -------------------------------------------------------
# Secrets (sensitive credentials)
# -------------------------------------------------------
secrets:
  SECRET_KEY_BASE: "<OUTPUT_OF: openssl rand -base64 64 | tr -d '\n'>"

  # Entra ID client secret — belongs here in secrets (not envVars)
  ENTRA_ID_CLIENT_SECRET: "<CLIENT_SECRET_VALUE>"       # From Azure Certificates & Secrets

  # DB_PASSWORD is NOT needed when autoIamAuthn: true — the proxy handles auth via Workload Identity

# -------------------------------------------------------
# Wharf (OTLP trace collection) — enabled by default
# -------------------------------------------------------
wharf:
  enabled: true          # Explicit for clarity — this is the chart default

# -------------------------------------------------------
# Gateway API
# -------------------------------------------------------
gateway:
  enabled: true
  create: true
  gatewayClassName: gke-l7-regional-external-managed  # Use gke-l7-global-external-managed for multi-region or CDN
  annotations:
    networking.gke.io/certmap: "<YOUR_CERT_MAP_NAME>"   # GCP-managed certificate map
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
    - name: http
      protocol: HTTP
      port: 80

# -------------------------------------------------------
# Web
# Note: Merge all web: settings under this single top-level web: key.
# Helm silently drops all but the last occurrence of a duplicate top-level key.
# -------------------------------------------------------
web:
  replicaCount: 3
  resources:
    requests:
      cpu: "1000m"
      memory: "6Gi"
    limits:
      cpu: "6"
      memory: "12Gi"
  gateway:
    enabled: true
    hostnames:
      - "<YOUR_DOMAIN>"   # Must match APPLICATION_HOST above

# -------------------------------------------------------
# Worker
# -------------------------------------------------------
worker:
  replicaCount: 3
  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 microservice (Built-In Integrations — optional)
# Remove this block if you do not need tool integrations
# (Microsoft 365, Google Workspace, HubSpot, etc.)
# -------------------------------------------------------
oauth:
  enabled: true
  gateway:
    enabled: true
    pathPrefix: "/oauthsvc"

# -------------------------------------------------------
# RBAC
# -------------------------------------------------------
rbac:
  create: true
The image.registries block is required for direct Helm installs. It provides credentials for pulling platform images (busybox, redis, buildkit, wharf, etc.) from the Replicated proxy at images.crewai.com. Use the same email and token used for helm registry login registry.crewai.com.When installing via Replicated KOTS, these credentials are injected automatically — image.registries is not needed.
DB_USER format for Cloud SQL IAM authentication: Set this to the GSA email without the .gserviceaccount.com suffix. The Cloud SQL Auth Proxy strips this suffix during IAM token exchange. Including the full suffix causes authentication to fail silently.
  • Correct: crewai-platform@my-project.iam
  • Wrong: crewai-platform@my-project.iam.gserviceaccount.com

Part 6: Install

Create GCP-Managed TLS Certificate (if using certmap)

gcloud certificate-manager certificates create crewai-cert \
  --domains="<YOUR_DOMAIN>"

gcloud certificate-manager maps create crewai-cert-map

gcloud certificate-manager maps entries create crewai-cert-entry \
  --map=crewai-cert-map \
  --certificates=crewai-cert \
  --hostname="<YOUR_DOMAIN>"

Log In to the Helm Registry

helm registry login registry.crewai.com \
  --username "<YOUR_LICENSE_EMAIL>" \
  --password "<YOUR_REPLICATED_LICENSE_TOKEN>"

Run Helm Install

helm install crewai-platform \
  oci://registry.crewai.com/crewai/stable/crewai-platform \
  --values values.yaml \
  --namespace crewai \
  --create-namespace

Annotate the Crews Namespace (Post-Install, Required)

After the first install creates the crewai-crews namespace, annotate the default ServiceAccount so build pods can push images to Artifact Registry via Workload Identity:
kubectl annotate serviceaccount default -n crewai-crews --overwrite \
  iam.gke.io/gcp-service-account=${GSA_NAME}@${GCP_PROJECT_ID}.iam.gserviceaccount.com
Until this annotation is applied, all crew image builds will fail with a permission denied error when pushing to Artifact Registry.

Part 7: Post-Install (Required for All Deployments)

Wait for all pods to reach Running status, then run the following commands:
# Initialize the internal organization
kubectl exec -it deploy/crewai-web -- bin/rails studio:install_internal_organization

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

# Add the initial platform owner (replace with your admin's email)
kubectl exec -it deploy/crewai-web -- bin/rails 'factory:add_owner[2,admin@your-company.com]'
For Entra ID deployments, do not run factory:grant_admin. Admin access is controlled entirely by the factory-admin App Role assigned in the Azure portal. Running factory:grant_admin is only needed for WorkOS, Okta, and local auth deployments.

Part 8: Studio V2 Setup (Post-Install)

Studio V2 has no Helm values and cannot be configured in values.yaml. Adding studioV2.enabled, STUDIO_V2_ENABLED, or any similar key to your values has no effect — Helm silently ignores unrecognized keys. Setup is always performed post-install in this order:

Step 1: Create the LLM Connection (UI)

  1. Log in to the platform at https://<YOUR_DOMAIN>
  2. Navigate to Settings → LLM Connections
  3. Click New Connection
  4. Set the name to exactly studio-v2 (lowercase, no spaces — this exact string is required)
  5. Select your LLM provider, enter the model name and API key
  6. Click Save
The connection name must be exactly studio-v2. The install commands in Step 3 look up this name specifically — any variation in spelling or capitalization will cause them to fail.

Step 2: Set as Default Connection (UI)

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

Step 3: Run Install Commands (kubectl)

Run these commands in order. Each must complete successfully before running the next.
# 1. Install the Studio agent (requires studio-v2 LLM Connection to exist)
kubectl exec -it deploy/crewai-web -- bin/rails studio:agent:install

# 2. Sync tools
kubectl exec -it deploy/crewai-web -- \
  bin/rails studio:tools:sync_crewai_tools studio:tools:sync_enterprise_tools

# 3. Install the Studio runner
kubectl exec -it deploy/crewai-web -- bin/rails studio:runner:install
studio:agent:install will fail if the studio-v2 LLM Connection does not already exist. Complete Steps 1 and 2 before running any kubectl commands.

Part 9: Verify

Platform Health

# Check all pods are running
kubectl get pods -n crewai

# Check web application health
kubectl exec -it deploy/crewai-web -- bin/rails runner "puts 'OK'"

# Verify Wharf is running
kubectl get pods -n crewai -l app.kubernetes.io/component=wharf

Gateway and DNS

# Get the Gateway external IP — update your DNS A record to point to it
kubectl get gateway -n crewai

# Verify the application is reachable
curl -I https://<YOUR_DOMAIN>/health

Entra ID Authentication

  1. Open https://<YOUR_DOMAIN> in a browser
  2. Click Sign in — you should be redirected to the Microsoft login page
  3. Log in with an Entra ID account that has been assigned either the Member or Factory Admin App Role
  4. After login, verify you are redirected back to the platform and successfully authenticated

Wharf Trace Collection

# Verify Wharf database tables were created
kubectl exec -it deploy/crewai-web -- \
  bin/rails runner "puts ActiveRecord::Base.connection.tables.grep(/wharf/).inspect"

# Check Wharf pod logs
kubectl logs -l app.kubernetes.io/component=wharf --tail=30 -n crewai

Studio V2

# Verify Studio deployments are running
kubectl get deploy -n crewai | grep studio

# Check Studio assistant logs
kubectl logs -l app=studio-assistant --tail=20 -n crewai
Both the studio-assistant and studio-runner deployments should show Running.

Troubleshooting

Entra ID: Silent Auth Failure (No Error, Login Loops)

Cause: ENTRA_ID_CLIENT_ID or ENTRA_ID_TENANT_ID were placed under secrets: instead of envVars:. The chart template does not inject Entra ID values from the secrets: section into pod environment variables. The pod starts normally but cannot authenticate because the identifiers are absent from the environment. Fix: Move ENTRA_ID_CLIENT_ID and ENTRA_ID_TENANT_ID to envVars:. Only ENTRA_ID_CLIENT_SECRET belongs under secrets:.

Entra ID: “Redirect URI mismatch” Error

Cause: The redirect URI registered in Azure does not exactly match https://<YOUR_DOMAIN>/auth/entra_id/callback. Fix: In the Azure portal, go to App registrations → <your app> → Authentication and verify the redirect URI matches exactly, including the https:// prefix, exact domain, and the path /auth/entra_id/callback.

Cloud SQL: Auth Proxy Authentication Failure

Symptoms: Pods show issue connecting with your username/password or fe_sendauth: no password supplied.
  1. Verify autoIamAuthn: true is set in cloudSqlProxy:
  2. Verify cloudsql.iam_authentication=on is set on the Cloud SQL instance:
    gcloud sql instances describe $SQL_INSTANCE \
      --format='value(settings.databaseFlags)'
    
  3. Verify the IAM database user exists:
    gcloud sql users list --instance=$SQL_INSTANCE --format="table(name,type)"
    
  4. Verify DB_USER uses the truncated format (without .gserviceaccount.com)

GCS: SignedUrlUnavailable Error

Symptoms: Logs show Google::Cloud::Storage::SignedUrlUnavailable. Fix: Ensure GCS_IAM_SIGNING: "true" is set in envVars: and the GSA has roles/iam.serviceAccountTokenCreator. This setting is required whenever Workload Identity is used.

Cloud SQL: Missing OAuth Database

Symptoms: OAuth service fails to start; logs show database does not exist. Fix: Verify POSTGRES_OAUTH_DB: "crewai_plus_oauth_production" is set in envVars:. The chart default is oauth_db. If you created the database as crewai_plus_oauth_production but did not override this value, the service connects to the wrong database name.

Studio V2: studio:agent:install Fails

Cause: The studio-v2 LLM Connection does not exist in the database yet. Fix: Complete the UI steps (Settings → LLM Connections → New Connection named exactly studio-v2, then Settings → Crew Studio → set as Default Connection) before running any Rails tasks.

Artifact Registry: Build Pod Push Failures

Symptoms: Crew image builds fail with unauthorized or denied during push. Fix: Verify the Workload Identity annotation on the crews namespace ServiceAccount:
kubectl get serviceaccount default -n crewai-crews -o jsonpath='{.metadata.annotations}'
If missing, re-run the annotation command from Part 6.

Workload Identity Not Working

Verify the cluster has Workload Identity enabled:
gcloud container clusters describe $GKE_CLUSTER \
  --region=$GCP_REGION \
  --format='value(workloadIdentityConfig.workloadPool)'
# Expected: <GCP_PROJECT_ID>.svc.id.goog
Verify the ServiceAccount annotation:
kubectl get serviceaccount $KSA_NAME -n $GKE_NAMESPACE -o yaml | grep gcp-service-account
Test from a pod:
kubectl run -it --rm wi-test \
  --namespace=$GKE_NAMESPACE \
  --image=google/cloud-sdk:slim \
  --serviceaccount=$KSA_NAME \
  --restart=Never -- \
  gcloud auth print-access-token