Overview
This guide covers Azure-specific integration for CrewAI Platform deployments on Azure Kubernetes Service (AKS). It focuses on how CrewAI uses Azure services and the platform-specific configuration required, rather than general Azure setup.
This guide assumes you have:
- An AKS cluster running Kubernetes 1.27+ with at least 3 nodes (4 vCPU / 8 GB RAM minimum per node)
- Azure CLI installed and authenticated (
az login)
- Helm 3.10+ and kubectl configured for your cluster
- Basic familiarity with Azure services (Azure Database for PostgreSQL, Azure Blob Storage, ACR)
Prerequisites
Before configuring CrewAI Platform, ensure these Azure components are in place:
CrewAI Platform supports AMD64 (x86_64) Kubernetes worker nodes. ARM64 (aarch64) worker nodes are not currently supported. For full platform requirements, see the Requirements Guide.
Required Azure Infrastructure
| Component | Documentation Link | Notes |
|---|
| AKS Cluster | AKS Quickstart | Kubernetes 1.27 or later |
| Azure Database for PostgreSQL | Flexible Server Overview | Flexible Server, PostgreSQL 16 |
| Azure Blob Storage | Storage Account Overview | Standard LRS or higher |
| Azure Container Registry | ACR Overview | Standard tier or higher |
Do not proceed with CrewAI installation until these prerequisites are met. The Helm chart will fail to deploy without them.
Azure Database for PostgreSQL Flexible Server
CrewAI Platform requires PostgreSQL 16 for production deployments. This section covers Azure Database for PostgreSQL Flexible Server requirements.
Instance Sizing
Minimum recommended specifications based on CrewAI workload characteristics:
| Deployment Size | SKU | vCPU | RAM | Storage |
|---|
| Development | Standard_B2s | 2 | 4 GiB | 32 GiB |
| Small Production | Standard_D2s_v3 | 2 | 8 GiB | 128 GiB |
| Medium Production | Standard_D4s_v3 | 4 | 16 GiB | 256 GiB |
| Large Production | Standard_D8s_v3 | 8 | 32 GiB | 512 GiB |
Memory-optimized SKUs (Standard_E family) provide better performance for CrewAI’s connection-heavy Rails architecture. For production workloads, enable high availability with a standby replica.
Create the PostgreSQL Instance
az postgres flexible-server create \
--resource-group your-resource-group \
--name crewai-postgres \
--location eastus \
--sku-name Standard_D2s_v3 \
--storage-size 128 \
--version 16 \
--admin-user crewai \
--admin-password "your-secure-password" \
--high-availability Disabled
Network Connectivity
CrewAI pods must reach your PostgreSQL instance. Two options:
Option 1: Private Access via VNet Integration (Recommended)
- Deploy the Flexible Server into the same VNet as your AKS cluster using a delegated subnet
- No public internet exposure
- PostgreSQL (5432) is reachable only from within the VNet
# Create a delegated subnet for PostgreSQL
az network vnet subnet create \
--resource-group your-resource-group \
--vnet-name your-vnet \
--name postgres-subnet \
--address-prefixes 10.1.2.0/24 \
--delegations Microsoft.DBforPostgreSQL/flexibleServers
# Create server with VNet integration
az postgres flexible-server create \
--resource-group your-resource-group \
--name crewai-postgres \
--location eastus \
--sku-name Standard_D2s_v3 \
--storage-size 128 \
--version 16 \
--admin-user crewai \
--admin-password "your-secure-password" \
--vnet your-vnet \
--subnet postgres-subnet \
--private-dns-zone crewai-postgres.private.postgres.database.azure.com
Option 2: Public Access with Firewall Rules
- Enable public access and restrict by AKS outbound IP ranges
- Requires SSL/TLS enforcement
# Allow connections from AKS node pool outbound IPs
az postgres flexible-server firewall-rule create \
--resource-group your-resource-group \
--name crewai-postgres \
--rule-name allow-aks \
--start-ip-address <AKS_OUTBOUND_IP_START> \
--end-ip-address <AKS_OUTBOUND_IP_END>
Database Setup
CrewAI requires four databases — primary, cable, OAuth, and Wharf for OTLP trace storage:
-- Create required databases
CREATE DATABASE crewai_plus_production;
CREATE DATABASE crewai_plus_cable_production;
CREATE DATABASE crewai_plus_oauth_production;
CREATE DATABASE wharf;
-- Grant privileges to the CrewAI user
GRANT ALL PRIVILEGES ON DATABASE crewai_plus_production TO crewai;
GRANT ALL PRIVILEGES ON DATABASE crewai_plus_cable_production TO crewai;
GRANT ALL PRIVILEGES ON DATABASE crewai_plus_oauth_production TO crewai;
GRANT ALL PRIVILEGES ON DATABASE wharf TO crewai;
-- Schema-level grants required for Rails migrations
\c crewai_plus_production;
GRANT ALL ON SCHEMA public TO crewai;
GRANT ALL ON ALL TABLES IN SCHEMA public TO crewai;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO crewai;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO crewai;
\c wharf;
GRANT ALL ON SCHEMA public TO crewai;
GRANT ALL ON ALL TABLES IN SCHEMA public TO crewai;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO crewai;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO crewai;
Create all four databases before running helm install. The Wharf migration will fail at install time if the wharf database does not exist. The Helm chart does not automatically create databases when postgres.enabled: false.
Helm Configuration
# Disable internal PostgreSQL
postgres:
enabled: false
envVars:
DB_HOST: "crewai-postgres.postgres.database.azure.com"
DB_PORT: "5432"
DB_USER: "crewai"
POSTGRES_DB: "crewai_plus_production"
POSTGRES_CABLE_DB: "crewai_plus_cable_production"
POSTGRES_OAUTH_DB: "crewai_plus_oauth_production"
secrets:
DB_PASSWORD: "your-secure-password"
Azure Blob Storage for Object Storage
CrewAI Platform uses Azure Blob Storage for storing crew artifacts, tool outputs, and user uploads when STORAGE_SERVICE is set to "microsoft".
Create Storage Account and Container
# Create storage account
az storage account create \
--name crewaistorage \
--resource-group your-resource-group \
--location eastus \
--sku Standard_LRS \
--kind StorageV2 \
--allow-blob-public-access false
# Create blob container
az storage container create \
--name crewai-storage \
--account-name crewaistorage
The name you choose for the container (e.g., crewai-storage) becomes the value for AZURE_CONTAINER_NAME in your Helm values. Both AZURE_STORAGE_ACCOUNT_NAME (the storage account name) and AZURE_CONTAINER_NAME (the container name within the account) are required. Omitting AZURE_CONTAINER_NAME causes runtime storage failures with no clear error at install time.
Storage Authentication Options
CrewAI supports two authentication methods for Azure Blob Storage. Choose based on your security requirements:
| Method | Managed Identity Required | Credential Rotation | Setup Complexity | Best For |
|---|
| Workload Identity (Federated) | Yes | Automatic | Medium | Production AKS deployments |
| Storage Access Key | No | Manual | Low | Development or non-AKS clusters |
Option 1: Workload Identity (Recommended)
Best for: Production AKS deployments, highest security. No long-lived credentials stored in Kubernetes secrets.
Azure Workload Identity binds an AKS pod’s service account to an Azure Managed Identity (or App Registration) via federated credential trust — no static keys required.
Setup Steps:
- Enable the AKS OIDC Issuer and Workload Identity:
# Enable OIDC issuer (required for federated credentials)
az aks update \
--resource-group your-resource-group \
--name your-aks-cluster \
--enable-oidc-issuer \
--enable-workload-identity
# Get the OIDC issuer URL
OIDC_ISSUER=$(az aks show \
--resource-group your-resource-group \
--name your-aks-cluster \
--query "oidcIssuerProfile.issuerUrl" -o tsv)
echo "OIDC Issuer: $OIDC_ISSUER"
- Create a User-Assigned Managed Identity:
az identity create \
--name crewai-platform-identity \
--resource-group your-resource-group \
--location eastus
IDENTITY_CLIENT_ID=$(az identity show \
--name crewai-platform-identity \
--resource-group your-resource-group \
--query clientId -o tsv)
IDENTITY_OBJECT_ID=$(az identity show \
--name crewai-platform-identity \
--resource-group your-resource-group \
--query principalId -o tsv)
- Grant Storage Blob Data Contributor role to the Managed Identity:
STORAGE_ID=$(az storage account show \
--name crewaistorage \
--resource-group your-resource-group \
--query id -o tsv)
az role assignment create \
--assignee-object-id "$IDENTITY_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role "Storage Blob Data Contributor" \
--scope "$STORAGE_ID"
- Create Federated Credential for AKS:
The federated credential links the AKS service account to the managed identity. The crewai-sa service account name is the chart default when rbac.create: true.
az identity federated-credential create \
--name crewai-platform-federated \
--identity-name crewai-platform-identity \
--resource-group your-resource-group \
--issuer "$OIDC_ISSUER" \
--subject "system:serviceaccount:crewai:crewai-sa" \
--audience api://AzureADTokenExchange
The --subject value must exactly match the Kubernetes namespace and ServiceAccount name. With rbac.create: true (the default), the chart creates crewai-sa in the Helm release namespace. Adjust both crewai parts if you deploy to a different namespace or use a custom ServiceAccount name.
Helm Configuration:
serviceAccount:
annotations:
azure.workload.identity/client-id: "<IDENTITY_CLIENT_ID>"
envVars:
STORAGE_SERVICE: "microsoft"
AZURE_STORAGE_ACCOUNT_NAME: "crewaistorage"
AZURE_CONTAINER_NAME: "crewai-storage"
rbac:
create: true
When rbac.create: true (the default), the chart automatically creates a ServiceAccount named crewai-sa and annotates it with the label azure.workload.identity/use: "true". The federated credential must reference this exact name and namespace.
AZURE_CONTAINER_NAME is the name of the blob container within the storage account — not the storage account name itself. It must match the container you created with az storage container create --name crewai-storage. If omitted, Azure Blob Storage operations fail at runtime with a missing container error.
Option 2: Storage Access Key
Best for: Development environments or deployments outside AKS where managed identity is not available.
Not recommended for production. Use Workload Identity instead to avoid storing long-lived credentials in Kubernetes secrets.
# Get the storage access key
STORAGE_KEY=$(az storage account keys list \
--resource-group your-resource-group \
--account-name crewaistorage \
--query "[0].value" -o tsv)
envVars:
STORAGE_SERVICE: "microsoft"
AZURE_STORAGE_ACCOUNT_NAME: "crewaistorage"
AZURE_CONTAINER_NAME: "crewai-storage"
secrets:
AZURE_STORAGE_ACCESS_KEY: "your-storage-access-key"
AZURE_CONTAINER_NAME is the name of the blob container within the storage account (e.g., crewai-storage), not the storage account name itself. It must match the container you created in Azure — omitting it causes runtime storage failures.
Azure Container Registry (ACR) for Crew Images
CrewAI Platform requires a writable container registry to build and push crew automation container images. When users create and deploy crews, CrewAI builds container images and pushes them to ACR.
ACR Repository Requirements
Critical Requirements:
- The repository path set in
CREW_IMAGE_REGISTRY_OVERRIDE must be a writable ACR registry prefix
- The platform automatically appends
/crewai-enterprise to the value — ensure the resulting repository exists and allows push/pull
- ACR webhook retention and tag mutability: CrewAI overwrites image tags for crew versions, so immutable tags must not be enforced on the target repository
- Chart installation fails if
CREW_IMAGE_REGISTRY_OVERRIDE is not set
Create ACR and Repository
# Create ACR (Standard tier required for geo-replication and content trust)
az acr create \
--resource-group your-resource-group \
--name crewairegistry \
--sku Standard \
--admin-enabled false
# Get the login server hostname
ACR_LOGIN_SERVER=$(az acr show \
--name crewairegistry \
--query loginServer -o tsv)
echo "ACR Login Server: $ACR_LOGIN_SERVER"
# Example output: crewairegistry.azurecr.io
Valid CREW_IMAGE_REGISTRY_OVERRIDE values:
crewairegistry.azurecr.io — appends /crewai-enterprise automatically
crewairegistry.azurecr.io/production — appends /crewai-enterprise automatically
crewairegistry.azurecr.io/my-org — appends /crewai-enterprise automatically
Do NOT set global.imageRegistry to your ACR host. This redirects ALL platform component image pulls (Redis, Wharf, BuildKit, busybox, the main application) away from images.crewai.com, causing ImagePullBackOff on every platform pod. Only CREW_IMAGE_REGISTRY_OVERRIDE is needed for crew image builds. Only use global.imageRegistry if you have mirrored every platform image to ACR per the private-registry guide.
ACR Authentication with Workload Identity
CrewAI pods require ACR push and pull permissions for building and deploying crew images.
Grant AcrPush to the Managed Identity (uses the same identity created for Blob Storage):
ACR_ID=$(az acr show \
--name crewairegistry \
--resource-group your-resource-group \
--query id -o tsv)
az role assignment create \
--assignee-object-id "$IDENTITY_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role AcrPush \
--scope "$ACR_ID"
For crew pods running in the crewai-crews namespace (build pods that push images), you also need a federated credential for that namespace’s default service account. Create this after the first helm install, which automatically creates the crewai-crews namespace:
az identity federated-credential create \
--name crewai-crews-federated \
--identity-name crewai-platform-identity \
--resource-group your-resource-group \
--issuer "$OIDC_ISSUER" \
--subject "system:serviceaccount:crewai-crews:default" \
--audience api://AzureADTokenExchange
# Annotate the default ServiceAccount in the crews namespace
kubectl annotate serviceaccount default -n crewai-crews --overwrite \
azure.workload.identity/client-id="$IDENTITY_CLIENT_ID"
The kubectl annotate command must run after the first helm install because the Helm chart creates the crewai-crews namespace. If you need to annotate before installing, create the namespace manually first: kubectl create namespace crewai-crews.
ArgoCD users: This step requires a two-Application pattern because the crewai-crews namespace does not exist until after the first sync completes. See the ArgoCD Deployment Guide.
ACR Authentication with Static Credentials
For environments where managed identity is not available, you can use an ACR service principal with static credentials:
# Create service principal with AcrPush access
SP_PASSWORD=$(az ad sp create-for-rbac \
--name crewai-acr-sp \
--scopes "$ACR_ID" \
--role AcrPush \
--query password -o tsv)
SP_APP_ID=$(az ad sp list \
--display-name crewai-acr-sp \
--query "[0].appId" -o tsv)
# Create image pull secret for ACR
kubectl create secret docker-registry acr-secret \
--namespace crewai \
--docker-server=crewairegistry.azurecr.io \
--docker-username="$SP_APP_ID" \
--docker-password="$SP_PASSWORD"
ACR service principal tokens and passwords are long-lived credentials. Rotate them regularly and store them in Azure Key Vault rather than directly in Kubernetes secrets. Use Workload Identity for production deployments.
Helm Configuration for ACR
envVars:
# ACR registry configuration (REQUIRED - deployment will fail if not set)
CREW_IMAGE_REGISTRY_OVERRIDE: "crewairegistry.azurecr.io"
# Note: The /crewai-enterprise suffix is added automatically by CrewAI Platform
# The Helm chart validates this field is set before deployment
# Storage configuration
STORAGE_SERVICE: "microsoft"
AZURE_STORAGE_ACCOUNT_NAME: "crewaistorage"
AZURE_CONTAINER_NAME: "crewai-storage"
# rbac.create: true (default) auto-creates ServiceAccount crewai-sa for Workload Identity
rbac:
create: true
Combined Workload Identity Setup (Storage + ACR)
When using Workload Identity for both Blob Storage and ACR (recommended), a single managed identity covers both:
# Summary of all role assignments for the single managed identity
# Blob Storage
az role assignment create \
--assignee-object-id "$IDENTITY_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role "Storage Blob Data Contributor" \
--scope "$STORAGE_ID"
# ACR
az role assignment create \
--assignee-object-id "$IDENTITY_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role AcrPush \
--scope "$ACR_ID"
Verifying ACR Access
Test ACR authentication from CrewAI pods:
# Check if pod can authenticate to ACR via managed identity
kubectl exec -it deploy/crewai-web -n crewai -- \
az acr login --name crewairegistry
# Verify BuildKit workers are available
kubectl exec -it deploy/crewai-buildkit -n crewai -- \
buildctl debug workers list
NGINX Ingress
CrewAI Platform supports NGINX Ingress Controller for AKS deployments. Azure Application Gateway Ingress Controller (AGIC) is also supported via the custom annotations mechanism.
Install NGINX Ingress Controller
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz
Get the External IP
kubectl get svc ingress-nginx-controller -n ingress-nginx \
--output jsonpath='{.status.loadBalancer.ingress[0].ip}'
TLS with Kubernetes Secrets
# Create TLS secret from your certificate files
kubectl create secret tls crewai-tls \
--cert=path/to/tls.crt \
--key=path/to/tls.key \
--namespace crewai
Helm Configuration:
web:
ingress:
enabled: true
className: nginx
host: "crewai.your-company.com"
nginx:
tls:
enabled: true
secretName: "crewai-tls"
Internal vs External Load Balancer
By default, the NGINX controller creates an Azure public load balancer. For internal-only access:
# In your ingress-nginx Helm values
controller:
service:
annotations:
service.beta.kubernetes.io/azure-load-balancer-internal: "true"
Microsoft Entra ID Authentication
CrewAI Platform uses Microsoft Entra ID (formerly Azure Active Directory) for enterprise SSO. Full setup instructions are in the Microsoft Entra ID Guide, but the key Helm values are:
Helm Configuration
envVars:
AUTH_PROVIDER: "entra_id"
ENTRA_ID_CLIENT_ID: "<Application (client) ID from Azure portal>"
ENTRA_ID_TENANT_ID: "<Directory (tenant) ID from Azure portal>"
# Optional: CLI authentication support
ENTRA_ID_DEVICE_AUTHORIZATION_CLIENT_ID: "<same client ID, or dedicated CLI app client ID>"
ENTRA_ID_CUSTOM_OPENID_SCOPE: "<scope from Expose an API, e.g. api://crewai-app/read>"
secrets:
ENTRA_ID_CLIENT_SECRET: "<Client secret value from Certificates & Secrets>"
The callback URL you register in Entra ID must match your application host and use the path /auth/entra_id/callback. For example: https://crewai.your-company.com/auth/entra_id/callback.
Azure Key Vault Integration
Azure Key Vault provides centralized secret management with audit logging for CrewAI Platform. The chart integrates with Key Vault via External Secrets Operator (ESO).
Which Secrets to Store
Store in Azure Key Vault (sensitive, need rotation):
DB_PASSWORD — Database credentials
SECRET_KEY_BASE — Rails secret key
ENTRA_ID_CLIENT_SECRET — Entra ID OAuth secret
AZURE_STORAGE_ACCESS_KEY — If using access key auth for storage
GITHUB_TOKEN — For private repository access
Keep in values.yaml (configuration, not secrets):
DB_HOST, DB_PORT, DB_USER, POSTGRES_DB, POSTGRES_CABLE_DB
AZURE_STORAGE_ACCOUNT_NAME, AZURE_CONTAINER_NAME
APPLICATION_HOST
AUTH_PROVIDER, ENTRA_ID_CLIENT_ID, ENTRA_ID_TENANT_ID
Create Key Vault
az keyvault create \
--name crewai-keyvault \
--resource-group your-resource-group \
--location eastus \
--enable-rbac-authorization true
# Store secrets
az keyvault secret set --vault-name crewai-keyvault \
--name DB-PASSWORD --value "your-db-password"
az keyvault secret set --vault-name crewai-keyvault \
--name ENTRA-ID-CLIENT-SECRET --value "your-client-secret"
Grant Key Vault Secrets Officer to Managed Identity
KEYVAULT_ID=$(az keyvault show \
--name crewai-keyvault \
--resource-group your-resource-group \
--query id -o tsv)
az role assignment create \
--assignee-object-id "$IDENTITY_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role "Key Vault Secrets User" \
--scope "$KEYVAULT_ID"
Install External Secrets Operator
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets \
external-secrets/external-secrets \
--namespace external-secrets-operator \
--create-namespace
Helm Configuration for Key Vault
The chart’s secretStore block creates a SecretStore resource pointing to Azure Key Vault. Use Workload Identity authentication (recommended) or Service Principal.
Using Workload Identity:
# Enable external secret store with Azure Key Vault
secretStore:
enabled: true
provider: "azure"
azure:
vaultUrl: "https://crewai-keyvault.vault.azure.net/"
tenantId: "<your-tenant-id>"
auth:
workloadIdentity:
enabled: true
serviceAccount: "crewai-sa" # must match the chart ServiceAccount name
externalSecret:
enabled: true
secretStore: "crewai-secret-store"
secretPath: "" # Azure Key Vault uses individual secret names, not paths
includes_azure_credentials: false # Set true if storage key is in Key Vault
Using Service Principal (alternative):
secretStore:
enabled: true
provider: "azure"
azure:
vaultUrl: "https://crewai-keyvault.vault.azure.net/"
tenantId: "<your-tenant-id>"
auth:
servicePrincipal:
enabled: true
secretName: "azure-sp-credentials" # Kubernetes secret with clientId/clientSecret
clientIdKey: "clientId"
clientSecretKey: "clientSecret"
Complete Azure Deployment Example
If using WorkOS authentication, place WORKOS_API_KEY under envVars: — NOT under secrets:. See the WorkOS SSO guide for the full explanation and the correct YAML placement. This is a known chart limitation.
Here is a complete production configuration for Azure using Workload Identity for all services:
# values-azure-production.yaml
# Disable internal services (use Azure managed services)
postgres:
enabled: false
minio:
enabled: false
# ServiceAccount annotation for Workload Identity
serviceAccount:
annotations:
azure.workload.identity/client-id: "<IDENTITY_CLIENT_ID>"
# Database configuration (Azure Database for PostgreSQL Flexible Server)
envVars:
DB_HOST: "crewai-postgres.postgres.database.azure.com"
DB_PORT: "5432"
DB_USER: "crewai"
POSTGRES_DB: "crewai_plus_production"
POSTGRES_CABLE_DB: "crewai_plus_cable_production"
POSTGRES_OAUTH_DB: "crewai_plus_oauth_production"
# Azure Blob Storage (Workload Identity — no key needed)
STORAGE_SERVICE: "microsoft"
AZURE_STORAGE_ACCOUNT_NAME: "crewaistorage"
AZURE_CONTAINER_NAME: "crewai-storage"
# ACR configuration (REQUIRED — deployment will fail if not set)
CREW_IMAGE_REGISTRY_OVERRIDE: "crewairegistry.azurecr.io"
# Note: /crewai-enterprise suffix is added automatically
# Chart validates this field is set before deployment
# Application configuration
APPLICATION_HOST: "crewai.your-company.com"
AUTH_PROVIDER: "entra_id"
RAILS_LOG_LEVEL: "info"
# Entra ID configuration
ENTRA_ID_CLIENT_ID: "<Application (client) ID>"
ENTRA_ID_TENANT_ID: "<Directory (tenant) ID>"
# Secrets (sensitive values — use Key Vault in production)
secrets:
DB_PASSWORD: "your-db-password"
ENTRA_ID_CLIENT_SECRET: "your-entra-client-secret"
# Web application configuration
web:
replicaCount: 3 # HA deployment
resources:
requests:
cpu: "1000m"
memory: "6Gi"
limits:
cpu: "6"
memory: "12Gi"
ingress:
enabled: true
className: nginx
host: "crewai.your-company.com"
nginx:
tls:
enabled: true
secretName: "crewai-tls"
# Worker configuration
worker:
replicaCount: 3
resources:
requests:
cpu: "1000m"
memory: "6Gi"
limits:
cpu: "6"
memory: "12Gi"
# BuildKit for crew image builds
buildkit:
enabled: true
replicaCount: 1
resources:
requests:
cpu: "500m"
memory: "2Gi"
limits:
cpu: "4"
memory: "8Gi"
# RBAC — auto-creates ServiceAccount crewai-sa for Workload Identity
rbac:
create: true
Deploy:
# Deploy CrewAI Platform
helm install crewai-platform \
oci://registry.crewai.com/crewai/stable/crewai-platform \
--values values-azure-production.yaml \
--namespace crewai \
--create-namespace
Troubleshooting Azure-Specific Issues
Pods Fail to Pull Images from images.crewai.com
Symptoms: Platform pods show ImagePullBackOff or ErrImagePull on images from images.crewai.com
First check — verify global.imageRegistry is not set to ACR: Setting global.imageRegistry to your ACR host redirects all platform image pulls away from images.crewai.com. Check your values file for this pattern and remove it:
# REMOVE this if present — causes ImagePullBackOff on all platform pods:
# global:
# imageRegistry: "crewairegistry.azurecr.io"
# CORRECT — only CREW_IMAGE_REGISTRY_OVERRIDE should reference ACR:
envVars:
CREW_IMAGE_REGISTRY_OVERRIDE: "crewairegistry.azurecr.io"
Common causes:
global.imageRegistry was set to the ACR hostname — this redirects all platform image pulls away from images.crewai.com
Resolution: Remove global.imageRegistry from your values. Only CREW_IMAGE_REGISTRY_OVERRIDE should reference ACR.
# Verify images.crewai.com pull secret exists
kubectl get secret docker-registry -n crewai
Workload Identity Not Working — Access Denied to Blob Storage
Symptoms: Logs show AuthorizationPermissionMismatch or 403 errors for Blob Storage operations
Verify the OIDC issuer and federated credential:
# Confirm OIDC issuer is enabled
az aks show \
--resource-group your-resource-group \
--name your-aks-cluster \
--query "oidcIssuerProfile" -o table
# List federated credentials
az identity federated-credential list \
--identity-name crewai-platform-identity \
--resource-group your-resource-group \
--output table
Verify the ServiceAccount annotation:
kubectl get serviceaccount crewai-sa -n crewai -o yaml | grep azure.workload.identity
Check pod labels (Workload Identity requires the azure.workload.identity/use: "true" label on pods):
kubectl get pod -l app.kubernetes.io/component=web -n crewai -o jsonpath='{.items[0].metadata.labels}'
Test from pod:
kubectl exec -it deploy/crewai-web -n crewai -- \
az storage blob list --account-name crewaistorage --container-name crewai-storage --auth-mode login
PostgreSQL Connection Timeout
Symptoms: Pods show could not connect to server: Connection timed out
Check VNet connectivity:
# Test connectivity from a debug pod
kubectl run -it --namespace crewai --rm debug \
--image=postgres:16 --restart=Never -- \
psql -h crewai-postgres.postgres.database.azure.com -U crewai -d crewai_plus_production
Check firewall rules:
az postgres flexible-server firewall-rule list \
--resource-group your-resource-group \
--name crewai-postgres \
--output table
Verify private DNS zone (if using VNet integration):
az network private-dns zone show \
--resource-group your-resource-group \
--name crewai-postgres.private.postgres.database.azure.com
ACR Push Fails — Unauthorized
Symptoms: BuildKit logs show unauthorized: authentication required when pushing crew images
Verify the AcrPush role assignment:
az role assignment list \
--assignee "$IDENTITY_OBJECT_ID" \
--scope "$ACR_ID" \
--output table
Verify the crews namespace federated credential:
az identity federated-credential list \
--identity-name crewai-platform-identity \
--resource-group your-resource-group \
--query "[?subject=='system:serviceaccount:crewai-crews:default']" \
--output table
Check the default ServiceAccount annotation in the crews namespace:
kubectl get serviceaccount default -n crewai-crews -o yaml | grep azure.workload.identity
Key Vault ExternalSecret Sync Errors
Symptoms: ExternalSecret shows SecretSyncedError
# Check ExternalSecret status
kubectl get externalsecret -n crewai
kubectl describe externalsecret crewai-external-secret -n crewai
# Check SecretStore status
kubectl get secretstore -n crewai
kubectl describe secretstore crewai-secret-store -n crewai
# Check ESO operator logs
kubectl logs -n external-secrets-operator deployment/external-secrets
Verify Key Vault access policy:
az role assignment list \
--assignee "$IDENTITY_OBJECT_ID" \
--scope "$KEYVAULT_ID" \
--output table
ServiceAccount Mismatch with Workload Identity
Symptoms: Pods show AuthorizationFailed despite correct Azure role assignments
Common causes:
- Wrong ServiceAccount name in federated credential — The
--subject in the federated credential must exactly match the namespace and ServiceAccount name used by pods
- Missing
azure.workload.identity/use: "true" label — Pods without this label do not receive federated tokens
Diagnosis:
# Check which ServiceAccount the pods are using
kubectl get pods -l app.kubernetes.io/component=web -n crewai \
-o jsonpath='{.items[0].spec.serviceAccountName}'
# Verify the federated credential subject matches
az identity federated-credential list \
--identity-name crewai-platform-identity \
--resource-group your-resource-group \
--query "[].{Name:name,Subject:subject}" \
--output table
# Verify the pod has the workload identity label
kubectl get pod -l app.kubernetes.io/component=web -n crewai \
-o jsonpath='{.items[0].metadata.labels.azure\.workload\.identity/use}'