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

# Azure Integration Guide

> Azure-specific configuration for CrewAI Platform including Azure Database for PostgreSQL, Azure Blob Storage, Azure Container Registry, and Azure Key Vault

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

<Note>
  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)
</Note>

## 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](/installation/requirements#architecture-support).

### Required Azure Infrastructure

| Component                         | Documentation Link                                                                                          | Notes                          |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------ |
| **AKS Cluster**                   | [AKS Quickstart](https://learn.microsoft.com/en-us/azure/aks/learn/quick-kubernetes-deploy-cli)             | Kubernetes 1.27 or later       |
| **Azure Database for PostgreSQL** | [Flexible Server Overview](https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/overview)     | Flexible Server, PostgreSQL 16 |
| **Azure Blob Storage**            | [Storage Account Overview](https://learn.microsoft.com/en-us/azure/storage/common/storage-account-overview) | Standard LRS or higher         |
| **Azure Container Registry**      | [ACR Overview](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-intro)         | Standard tier or higher        |

<Warning>
  Do not proceed with CrewAI installation until these prerequisites are met. The Helm chart will fail to deploy without them.
</Warning>

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

<Note>
  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.
</Note>

### Create the PostgreSQL Instance

```bash theme={null}
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

```bash theme={null}
# 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

```bash theme={null}
# 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:

```sql theme={null}
-- 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;
```

<Warning>
  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`.
</Warning>

### Helm Configuration

```yaml theme={null}
# 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

```bash theme={null}
# 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
```

<Note>
  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.
</Note>

### 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**:

1. **Enable the AKS OIDC Issuer and Workload Identity**:

```bash theme={null}
# 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"
```

2. **Create a User-Assigned Managed Identity**:

```bash theme={null}
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)
```

3. **Grant Storage Blob Data Contributor role to the Managed Identity**:

```bash theme={null}
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"
```

4. **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`.

```bash theme={null}
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
```

<Warning>
  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.
</Warning>

**Helm Configuration**:

```yaml theme={null}
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
```

<Note>
  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.
</Note>

<Warning>
  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.
</Warning>

#### Option 2: Storage Access Key

**Best for**: Development environments or deployments outside AKS where managed identity is not available.

<Warning>
  Not recommended for production. Use Workload Identity instead to avoid storing long-lived credentials in Kubernetes secrets.
</Warning>

```bash theme={null}
# 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)
```

```yaml theme={null}
envVars:
  STORAGE_SERVICE: "microsoft"
  AZURE_STORAGE_ACCOUNT_NAME: "crewaistorage"
  AZURE_CONTAINER_NAME: "crewai-storage"

secrets:
  AZURE_STORAGE_ACCESS_KEY: "your-storage-access-key"
```

<Note>
  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.
</Note>

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

<Warning>
  **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
</Warning>

### Create ACR and Repository

```bash theme={null}
# 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

<Warning>
  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](/configuration/private-registry).
</Warning>

### 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):

```bash theme={null}
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:

```bash theme={null}
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"
```

<Warning>
  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`.
</Warning>

<Note>
  **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](/configuration/argocd#cloud-provider-workload-identity-crewai-crews-namespace).
</Note>

### ACR Authentication with Static Credentials

For environments where managed identity is not available, you can use an ACR service principal with static credentials:

```bash theme={null}
# 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"
```

<Warning>
  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.
</Warning>

### Helm Configuration for ACR

```yaml theme={null}
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:

```bash theme={null}
# 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:

```bash theme={null}
# 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

```bash theme={null}
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

```bash theme={null}
kubectl get svc ingress-nginx-controller -n ingress-nginx \
  --output jsonpath='{.status.loadBalancer.ingress[0].ip}'
```

### TLS with Kubernetes Secrets

```bash theme={null}
# 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**:

```yaml theme={null}
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:

```yaml theme={null}
# 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](/features/entra-id), but the key Helm values are:

### Helm Configuration

```yaml theme={null}
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>"
```

<Note>
  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`.
</Note>

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

```bash theme={null}
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

```bash theme={null}
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

```bash theme={null}
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**:

```yaml theme={null}
# 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):

```yaml theme={null}
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

<Note>
  If using WorkOS authentication, place `WORKOS_API_KEY` under `envVars:` — NOT under `secrets:`. See the [WorkOS SSO guide](/features/workos-sso) for the full explanation and the correct YAML placement. This is a known chart limitation.
</Note>

Here is a complete production configuration for Azure using Workload Identity for all services:

```yaml theme={null}
# 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**:

```bash theme={null}
# 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:

```yaml theme={null}
# 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**:

1. `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.

```bash theme={null}
# 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**:

```bash theme={null}
# 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**:

```bash theme={null}
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):

```bash theme={null}
kubectl get pod -l app.kubernetes.io/component=web -n crewai -o jsonpath='{.items[0].metadata.labels}'
```

**Test from pod**:

```bash theme={null}
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**:

```bash theme={null}
# 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**:

```bash theme={null}
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):

```bash theme={null}
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**:

```bash theme={null}
az role assignment list \
  --assignee "$IDENTITY_OBJECT_ID" \
  --scope "$ACR_ID" \
  --output table
```

**Verify the crews namespace federated credential**:

```bash theme={null}
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**:

```bash theme={null}
kubectl get serviceaccount default -n crewai-crews -o yaml | grep azure.workload.identity
```

### Key Vault ExternalSecret Sync Errors

**Symptoms**: ExternalSecret shows `SecretSyncedError`

```bash theme={null}
# 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**:

```bash theme={null}
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**:

1. **Wrong ServiceAccount name in federated credential** — The `--subject` in the federated credential must exactly match the namespace and ServiceAccount name used by pods
2. **Missing `azure.workload.identity/use: "true"` label** — Pods without this label do not receive federated tokens

**Diagnosis**:

```bash theme={null}
# 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}'
```
