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

# Deploying Genesis - Kuberenetes on Azure

> Deploy the Genesis container to an existing Azure Kubernetes Service cluster

This guide walks you through deploying the Genesis container to an existing Azure AKS (Azure Kubernetes Service) cluster.

<Note>
  **For everything after the install** — upgrade, rollback, backup and restore, secrets, user offboarding, scaling, teardown, and the failure-mode catalogue — see the [Genesis Operations Runbook](/docs/deployments/Genesis-Operations-Runbook). It is cloud-agnostic, with a per-cloud section for the AKS-specific substrate. Snapshot options stay in [Backup and Snapshot Options](/docs/deployments/Azure/Backup-And-Snapshot-Options).
</Note>

<Note>
  Genesis will provide you with the container image URL. Replace `<container-registry-url>` throughout this document with the URL provided by Genesis. Helm charts are published as private OCI artifacts on Docker Hub (`oci://registry-1.docker.io/genesiscomputingai/genesis`), ECR (`oci://<account>.dkr.ecr.<region>.amazonaws.com/charts/genesis`), and GHCR (`oci://ghcr.io/genesis-computing-ai/charts/genesis`). Docker Hub and ECR credentials are provided during onboarding; GHCR access uses a GitHub PAT with `read:packages`. If you use the bundled `azure-aks/` helper scripts, set `ECR_REGISTRY` in `azure-aks/aks-config.env` and ensure the environment can run `aws ecr get-login-password` for that registry. Pin `image.tag` in your values file, and set `GENESIS_IMAGE_TAG` in `azure-aks/aks-config.env` only when you want a one-off override for a deploy.
</Note>

## Prerequisites

### Azure AKS Cluster Requirements

<CardGroup cols={2}>
  <Card title="AKS Cluster" icon="cube">
    Kubernetes version 1.23 or higher
  </Card>

  <Card title="kubectl" icon="terminal">
    Configured to access your AKS cluster
  </Card>

  <Card title="Helm 3.8+" icon="dharmachakra">
    For deploying the Genesis Helm chart
  </Card>

  <Card title="Azure CLI" icon="cloud">
    Configured with appropriate credentials
  </Card>
</CardGroup>

**Verify cluster access:**

```bash theme={null}
az aks get-credentials --resource-group <resource-group> --name <cluster-name>
kubectl get nodes  # Verify access
```

**Verify Helm installation:**

```bash theme={null}
helm version
```

### Additional Requirements

* **Azure Disk CSI Driver**: Required for persistent storage (see [Azure Disk CSI Driver Setup](#azure-disk-csi-driver-setup))
* **Ingress Controller**: For external access (see [Ingress Controller Setup](#ingress-controller-setup))

### Network Access

| Requirement | Purpose |
| - | - |
| Outbound Internet | Cluster nodes must pull container images from the registry URL provided by Genesis |
| DNS Resolution | For accessing external APIs (OpenAI, Azure OpenAI, etc.) if using those LLM providers |

### Azure Permissions

The AKS cluster's managed identity or service principal needs appropriate permissions for:

* Azure Disk operations (for persistent volumes)
* Azure Container Registry access (if using ACR)
* Azure Key Vault access (if storing secrets in Key Vault)

## Azure Disk CSI Driver Setup

Genesis requires persistent storage for its database, git repositories, and uploaded files. The Azure Disk CSI driver must be installed in your cluster to provision Azure managed disks.

<Info>
  The Azure Disk CSI driver is **automatically installed by default** on AKS clusters. However, verify it's running and properly configured.
</Info>

**Check if installed:**

```bash theme={null}
kubectl get pods -n kube-system | grep csi-azuredisk
```

<Card title="Installation Instructions" icon="book" href="https://learn.microsoft.com/en-us/azure/aks/azure-disk-csi">
  If not installed, refer to the Azure Kubernetes Service documentation for detailed Azure Disk CSI driver installation steps.
</Card>

## Ingress Controller Setup

Genesis requires an ingress controller for external access. Choose one based on your requirements:

<Tabs>
  <Tab title="Application Gateway (Recommended)">
    The Application Gateway Ingress Controller (AGIC) integrates Azure Application Gateway with AKS for ingress management.

    **Install Application Gateway Ingress Controller:**

    ```bash theme={null}
    # Add the AGIC Helm repository
    helm repo add application-gateway-kubernetes-ingress https://appgwingress.blob.core.windows.net/ingress-azure-helm-package/
    helm repo update

    # Install AGIC
    helm install ingress-azure application-gateway-kubernetes-ingress/ingress-azure \
      --set appgw.name=<application-gateway-name> \
      --set appgw.resourceGroup=<resource-group> \
      --set appgw.subscriptionId=<subscription-id> \
      --set appgw.shared=false \
      --set armAuth.type=servicePrincipal \
      --set armAuth.secretJSON=$(az ad sp create-for-rbac --role Contributor --scopes /subscriptions/<subscription-id>/resourceGroups/<resource-group> --sdk-auth | base64 -w 0) \
      -n kube-system
    ```

    **Verify installation:**

    ```bash theme={null}
    kubectl get deployment -n kube-system ingress-azure
    ```
  </Tab>

  <Tab title="NGINX Ingress Controller">
    **Install NGINX Ingress:**

    ```bash theme={null}
    kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml

    # Wait for it to be ready
    kubectl wait --namespace ingress-nginx \
      --for=condition=ready pod \
      --selector=app.kubernetes.io/component=controller \
      --timeout=90s
    ```
  </Tab>
</Tabs>

## Genesis Container Configuration

### Container Image

| Setting | Description |
| - | - |
| Repository | Genesis will provide the container image URL (e.g., `<container-registry-url>/genesis`) |
| Tags | Genesis will specify the appropriate tag to use (for example `v1.18.2`, `edge`, or another specific version) |
| Registry Access | Genesis will provide details about authentication requirements, if any |

<Info>
  Current Genesis container images include `ripgrep` (`rg`) out of the box for in-container file-content searches. After `kubectl exec`, you can use it to inspect mounted files under `/app/bot_storage` or `/app/bot_git` without installing extra packages. The same images also bundle the in-repo `genesis_sdk` package under `/app/genesis_sdk` (for `execute_python_code` local execution — not installable from PyPI) and the `llm_wiki` package under `/app/llm_wiki`, so the SDK, wiki APIs, and tools shipped with Genesis are available without extra image customization.
</Info>

### Image Pull Secrets (If Required)

If the container registry requires authentication, Genesis will provide the necessary credentials. Create a Kubernetes secret:

```bash theme={null}
# Create namespace first if it doesn't exist
kubectl create namespace genesis

# Create Docker registry secret (if credentials provided by Genesis)
kubectl create secret docker-registry genesis-registry-credentials \
  --docker-server=<container-registry-url> \
  --docker-username=<username> \
  --docker-password=<password> \
  --docker-email=<email> \
  -n genesis
```

Then reference it in your Helm values file:

```yaml theme={null}
imagePullSecrets:
  - name: genesis-registry-credentials
```

### Exposed Ports

| Port | Service | Description |
| - | - | - |
| 8080 | FastAPI | Primary application with React GUI and modern APIs |
| 8082 | Flask | UDF proxy, OAuth, external integrations |
| 8501 | Streamlit | Legacy Streamlit interface |

### Persistent Storage Requirements

Genesis requires persistent storage for:

| Path | Purpose |
| - | - |
| `/app/.genesis/db/genesis.db` | SQLite database |
| `/app/bot_git` | Cloned git repositories |
| `/app/bot_storage` | Uploaded files and bot storage |
| `/app/tmp` | Runtime temp files |

<Info>
  **Recommended Storage Size:** 50Gi minimum (adjust based on expected usage)

  **Storage Class:** Uses cluster default (typically `managed-csi` or `managed-premium` on AKS)

  For CSI volume snapshots, Azure Backup for AKS, Velero, and logical export options, see [Backup and Snapshot Options](/docs/deployments/Azure/Backup-And-Snapshot-Options).
</Info>

### Environment Variables

<AccordionGroup>
  <Accordion title="Required Configuration">
    | Variable | Default | Description |
    | - | - | - |
    | `SQLITE_DB_PATH` | `/app/.genesis/db/genesis.db` | Path to SQLite database |
    | `DATABASE_URL` | `sqlite:///app/.genesis/db/genesis.db` | SQLite connection string |
  </Accordion>

  <Accordion title="LLM Provider Configuration">
    There is no LLM provider environment variable to set on the container. The
    provider, its credential and its model assignments are configured after the
    pod is running, from **Config -> AI Providers** or through the config API —
    see the worked example in [Deployment Steps](#deployment-steps) below.
    The Azure Terraform deployment also seeds `AZURE_OPENAI_ENABLED=true`;
    see the legacy-image compatibility note under [AWS Bedrock Configuration](#aws-bedrock-configuration).
  </Accordion>

  <Accordion title="Authentication (Optional)">
    | Variable | Value | Description |
    | - | - | - |
    | `AUTH_ENABLED` | `"true"` | Enable authentication |
    | `AUTH_PROVIDER` | `"proxy"` | For ingress-based auth |
  </Accordion>

  <Accordion title="Git repository runtime">
    Genesis prepares `GIT_PATH` on startup and ensures `repos/`, `worktrees/`,
    and `.genesis_runtime/` exist before shared Git-backed workflows use the
    mount. The shared group-write repair starts in the background so slow
    recursive permission fixes do not block container startup.

    | Variable | Default | Description |
    | - | - | - |
    | `GIT_PATH` | `/app/bot_git` | Mounted Git workspace Genesis prepares at startup. |
    | `GENESIS_BOT_GIT_SHARED_GROUP` | `cortex_sandbox` | Shared Linux group Genesis applies when repairing the mounted Git tree so repositories remain group writable. The repair starts in the background during container startup. |
    | `GENESIS_TRUST_ALL_GIT_DIRS` | `true` | When `true`, Genesis adds `*` to system Git `safe.directory` during startup. Set to `false` only when you manage Git trust outside Genesis. |
  </Accordion>

  <Accordion title="Logging">
    | Variable | Options | Description |
    | - | - | - |
    | `LOG_LEVEL` | `DEBUG`, `INFO`, `WARNING`, `ERROR` | Log verbosity |
    | `LOGS_FORMAT` | `json`, `text` | Log output format |
  </Accordion>
</AccordionGroup>

## Deployment Steps

<Steps>
  <Step title="Prepare Helm Chart">
    Genesis publishes the Helm chart to private OCI registries. After authenticating with the registry Genesis assigned to your deployment, pull and unpack the chart locally. If Genesis provided a `.tgz` archive instead, extract it and continue with the same chart directory layout:

    ```bash theme={null}
    # Option A: Pull and unpack the OCI chart locally
    helm pull oci://registry-1.docker.io/genesiscomputingai/genesis --version <chart-version> --untar

    # Option B: Extract the Helm chart archive provided by Genesis
    tar -xzf genesis-<version>.tgz

    # Navigate to the chart directory
    cd genesis
    ```

    <Info>
      Chart releases are published from `main`. On each GA release, Genesis updates `appVersion` in `Chart.yaml` to the promoted container version and patch-bumps the chart version automatically. To find the chart version for a specific GA release, query the `GA_HELM_MAP` repo variable: `gh variable get GA_HELM_MAP -R genesis-computing-ai/genesis-agents | jq -r '.["vX.Y.Z"]'`.
    </Info>
  </Step>

  <Step title="Create Values File">
    Create a `genesis-values.yaml` file with your configuration:

    ```yaml theme={null}
    # Genesis AKS Deployment Values

    # Container image (Genesis will provide the repository URL)
    image:
      repository: <container-registry-url>/genesis  # Replace with URL provided by Genesis
      tag: v1.18.2  # Example GA pin; edge bundles use edge
      pullPolicy: IfNotPresent

    # Genesis AKS helper scripts enable ECR and inject the registry/token at deploy time
    ecr:
      enabled: true
      registry: ""

    # Image pull secrets (if the container registry requires authentication)
    # Genesis will provide instructions if credentials are needed
    # imagePullSecrets:
    #   - name: genesis-registry-credentials

    # Secrets
    secrets:
      # Master encryption key (REQUIRED on first install unless
      # existingSecretGenesisMasterKey or config.extraEnv.GENESIS_MASTER_KEY_REF)
      genesisMasterKey: ""  # Generate: python -c "import secrets; print(secrets.token_hex(32))"

    # Non-sensitive configuration
    config:
      sqliteDbPath: "/app/.genesis/db/genesis.db"
      logLevel: "INFO"
      logsFormat: "json"
      authEnabled: "true"
      authProvider: "proxy"  # For ingress-based authentication
      
      # The LLM provider is not configured here. Configure it after install
      # through the config API or Config -> AI Providers.

    # Recommended for IaC-managed Genesis settings
    genesisConfig:
      content: ""  # Set with --set-file genesisConfig.content=genesis-config.yaml
      existingConfigMap: ""  # Or reference a pre-created ConfigMap, not both

    # Resource requests
    resources:
      requests:
        cpu: "2000m"
        memory: "6Gi"
      limits: {}  # No limits by default

    # Persistent storage
    persistence:
      storageClassName: ""  # Uses cluster default (managed-csi or managed-premium)
      size: 50Gi  # Kubernetes uses binary units (Gi = Gibibytes)
      accessMode: ReadWriteOnce

    # Service configuration
    service:
      type: ClusterIP
      ports:
        fastapi: 8080
        flask: 8082
        streamlit: 8501

    # Ingress configuration
    ingress:
      enabled: true
      className: "azure/application-gateway"  # For Application Gateway, or "nginx" for NGINX
      
      # For Application Gateway Ingress Controller
      annotations:
        appgw.ingress.kubernetes.io/ssl-redirect: "true"
        appgw.ingress.kubernetes.io/health-probe-path: "/api/health"
        appgw.ingress.kubernetes.io/health-probe-interval: "30"
        appgw.ingress.kubernetes.io/health-probe-timeout: "10"
        appgw.ingress.kubernetes.io/health-probe-unhealthy-threshold: "3"
        # For HTTPS, configure certificate in Application Gateway
        # appgw.ingress.kubernetes.io/appgw-ssl-certificate: "<certificate-name>"
      
      hosts:
        - host: genesis.yourdomain.com  # Replace with your domain
          paths:
            - path: /
              pathType: Prefix
              port: 8080
      
      # TLS configuration (if using HTTPS)
      tls: []

    # Optional extra Kubernetes resources rendered as separate YAML documents
    extraManifests: []
    ```

    <Note>
      For IaC-managed deployments, keep AKS and ingress settings in
      `genesis-values.yaml` and copy the chart's `genesis-config-example.yaml`
      to a separate `genesis-config.yaml` for `providers`, `llm`, `embedding`,
      `auth`, `oauth_providers`, and related app settings (including LiteLLM
      Proxy OAuth). Deploy with
      `--set-file genesisConfig.content=genesis-config.yaml`, or set
      `genesisConfig.existingConfigMap` when the file already exists as a
      ConfigMap. Genesis mounts the file at
      `/app/.genesis/config/genesis-config.yaml`, and explicit environment
      variables still take precedence over file-backed values.

      For Helm-driven RBAC on OIDC deployments, you can also pass
      `--set-file groupRoleMapping.content=group-role-mapping.yaml` and
      `--set-file rbacSeedPolicy.content=rbac-policies.csv`. See
      [Deploying Genesis - Azure AKS with OIDC Authentication](/docs/deployments/Azure/Genesis-On-Azure-AKS-OIDC#helm-driven-rbac).
    </Note>

    <Tip>
      `extraManifests` defaults to `[]`. Add complete Kubernetes resources here when you want Helm to deploy objects such as `PodDisruptionBudget`, `NetworkPolicy`, or `PodMonitor` alongside Genesis without forking the chart. Each list item is rendered as its own YAML document.
    </Tip>

    <Tip>
      For NGINX Ingress Controller, use these annotations instead:

      ```yaml theme={null}
      annotations:
        nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
        nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
        nginx.ingress.kubernetes.io/proxy-body-size: "100m"
      ```
    </Tip>

    <Info>
      If you deploy with the bundled `azure-aks/deploy-aks-*.sh` helpers, pin `image.tag` in the corresponding `values-aks-*.yaml` file. The script passes `ecr.enabled`, `ecr.registry`, a fresh `ecr.token`, and `image.pullPolicy=Always`, runs `kubectl rollout restart statefulset`, and only passes `--set image.tag` when `GENESIS_IMAGE_TAG` is set in `azure-aks/aks-config.env`.
    </Info>
  </Step>

  <Step title="Deploy Genesis">
    ```bash theme={null}
    # Install using Helm
    helm install genesis ./genesis -f genesis-values.yaml

    # Or if deploying from chart directory
    helm install genesis . -f genesis-values.yaml
    ```
  </Step>

  <Step title="Verify Deployment">
    ```bash theme={null}
    # Check namespace
    kubectl get namespace genesis

    # Check StatefulSet
    kubectl get statefulset -n genesis

    # Check pod status
    kubectl get pods -n genesis -w

    # Check persistent volume claim
    kubectl get pvc -n genesis

    # Check service
    kubectl get svc -n genesis

    # Check ingress
    kubectl get ingress -n genesis

    # View pod logs
    kubectl logs -n genesis genesis-0 --follow
    ```
  </Step>

  <Step title="Access Genesis">
    <Tabs>
      <Tab title="Via Ingress (Recommended)">
        **For Application Gateway Ingress Controller:** Get the Application Gateway public IP:

        ```bash theme={null}
        az network application-gateway show \
          --resource-group <resource-group> \
          --name <application-gateway-name> \
          --query frontendIPConfigurations[0].publicIPAddress.id \
          --output tsv | xargs az network public-ip show --ids --query ipAddress --output tsv
        ```

        Access via: `http://<public-ip>` or `https://genesis.yourdomain.com`

        **For NGINX Ingress:** Get the external IP:

        ```bash theme={null}
        kubectl get svc -n ingress-nginx ingress-nginx-controller
        ```

        Access via: `http://<external-ip>` or `http://genesis.yourdomain.com` (if DNS configured)
      </Tab>

      <Tab title="Port Forward (For Testing)">
        ```bash theme={null}
        # Forward port 8080
        kubectl port-forward -n genesis svc/genesis 8080:8080

        # Access at http://localhost:8080
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Configure the LLM Provider">
    Genesis starts without a provider and reports **No suitable LLM found** until
    one is configured. Configure it against the **running** pod, either from
    **Config -> AI Providers** in the UI or through the config API. Keep
    credentials in a request-body file — process arguments are visible to every
    process on the host and land in shell history.

    ```bash theme={null}
    umask 077
    set -o pipefail   # json.tool must not mask a failed curl
    GENESIS_URL=https://genesis.yourdomain.com
    BODY=$(mktemp "${TMPDIR:-/tmp}/genesis-llm-body.XXXXXX.json")
    CURL_CONFIG=$(mktemp "${TMPDIR:-/tmp}/genesis-curl.XXXXXX")
    trap 'rm -f "$BODY" "$CURL_CONFIG"' EXIT
    chmod 600 "$BODY" "$CURL_CONFIG"

    # With authEnabled: "true", every call needs a bearer token for a user holding
    # access_config_panel. read -rs keeps it out of shell history. Drop these lines
    # and --config only on an auth-disabled pod.
    read -rsp 'Genesis access token: ' GENESIS_TOKEN && echo
    printf 'header = "Authorization: Bearer %s"\n' "$GENESIS_TOKEN" > "$CURL_CONFIG"
    unset GENESIS_TOKEN

    cat > "$BODY" <<'JSON'
    {"endpoint_name": "genesis-azureopenai-1",
     "api_key": "<azure-openai-api-key>",
     "full_endpoint_url": "https://<resource-name>.openai.azure.com/",
     "api_version": "2025-04-01-preview",
     "model_deployment_name": "<chat-deployment-name>",
     "embedding_deployment_name": "<embedding-deployment-name>",
     "test_connection": true}
    JSON
    curl -sS --fail-with-body --config "$CURL_CONFIG" \
      -X POST -H 'Content-Type: application/json' --data @"$BODY" \
      "$GENESIS_URL/api/config/llm-providers/azure" | python3 -m json.tool

    # Note the two naming spaces: the card is "azure", the llm type is "azureopenai".
    curl -sS --fail-with-body --config "$CURL_CONFIG" \
      -X POST -H 'Content-Type: application/json' -d '{"force": true}' \
      "$GENESIS_URL/api/config/llm-providers/azureopenai/activate" | python3 -m json.tool

    curl -sS --fail-with-body --config "$CURL_CONFIG" \
      -X PUT -H 'Content-Type: application/json' \
      -d '{"provider": "azureopenai", "use_defaults": true, "activate_provider": true}' \
      "$GENESIS_URL/api/config/models/main/assignments" | python3 -m json.tool
    ```

    Read each response before running the next call: these routes also answer `200`
    with `success: false` (for example when the provider already exists — repeat
    that call as a `PUT`). For the full endpoint reference and the other providers, see
    [Configuring a provider through the config API](/docs/Setup/Setup-AI-Model#configuring-a-provider-through-the-config-api).

    <Note>
      Some schema fields carry a `default_env_var` binding that seeds them from the
      environment while nothing is stored, so an image can come up pre-seeded. That
      is bootstrap only — it is not the way to configure a provider, and anything
      saved through the UI or the config API outranks it.
    </Note>
  </Step>
</Steps>

## Kubernetes-Specific Configuration

### StatefulSet

Genesis is deployed as a **StatefulSet** (not Deployment) to ensure:

* Stable network identity (pod name: `genesis-0`)
* Ordered, graceful deployment and scaling
* Stable persistent storage (PVC name: `genesis-data-genesis-0`)

### Persistent Volume Claim

The StatefulSet creates a PersistentVolumeClaim with:

| Property | Value |
| - | - |
| Name | `genesis-data-genesis-0` |
| Storage Class | Uses cluster default (typically `managed-csi` or `managed-premium`) |
| Access Mode | `ReadWriteOnce` (single pod access) |
| Retention | PVC is retained when StatefulSet is deleted (data preservation) |

### Volume Mounts

The Genesis container mounts the persistent volume at multiple paths:

| Mount Path | Purpose |
| - | - |
| `/app/.genesis/db` | Database files |
| `/app/bot_git` | Git repositories |
| `/app/bot_storage` | File storage and uploads |
| `/app/tmp` | Temporary files |

### Health Checks

Genesis uses three Kubernetes probes so slow startups do not trigger liveness restarts before the app is ready:

<CardGroup cols={3}>
  <Card title="Startup Probe" icon="hourglass">
    * **Endpoint:** `GET /api/health` on port 8080
    * **Period:** 10 seconds
    * **Timeout:** 5 seconds
    * **Failure threshold:** 60 (up to \~10 minutes boot grace)
    * Gates liveness and readiness until startup completes (`503` with progress, then `200`)
  </Card>

  <Card title="Liveness Probe" icon="heart-pulse">
    * **Endpoint:** `GET /api/health/live` on port 8080
    * **Period:** 30 seconds
    * **Timeout:** 5 seconds
    * **Failure threshold:** 3
    * Always `200` while the process is alive — does not require Genesis to be fully ready
  </Card>

  <Card title="Readiness Probe" icon="check">
    * **Endpoint:** `GET /api/health` on port 8080
    * **Period:** 10 seconds
    * **Timeout:** 5 seconds
    * **Failure threshold:** 3
    * Returns `503` while booting; `200` when ready to serve traffic
  </Card>
</CardGroup>

When Genesis sits behind **OAuth2 Proxy** (`authProvider: "proxy"`), configure the proxy so GET health probes and the dashboard `healthcheck` endpoint bypass authentication. The bundled `azure-aks/oauth2-proxy-values.yaml` example (also generated by `azure-aks/setup-azure-ad-auth.sh`) includes:

```yaml theme={null}
cookie_secure = true
cookie_samesite = "lax"
skip_auth_routes = ["GET=^/api/health$", "GET=^/api/health/startup$", "GET=^/api/health/live$", "GET=^/api/healthcheck$"]
cookie_csrf_per_request = true
cookie_csrf_expire = "5m"
```

`cookie_secure = true` is correct when HTTPS terminates at the ingress controller or external load balancer, even if the OAuth2 Proxy chart ingress has `tls: []`. Routes are GET-scoped and anchored so other `/api/health/*` paths (errors, threads, slots, and so on) remain authenticated. `cookie_csrf_per_request` isolates concurrent login flows and prevents invalid-CSRF `403` responses.

For Azure AD refresh-token, CSRF-cookie, and nginx `auth_request` caveats, see [Deploying Genesis - Azure AKS with OIDC Authentication](/docs/deployments/Azure/Genesis-On-Azure-AKS-OIDC#comparison-oidc-vs-oauth2-proxy).

For native OIDC without OAuth2 Proxy, see [Deploying Genesis - Azure AKS with OIDC Authentication](/docs/deployments/Azure/Genesis-On-Azure-AKS-OIDC).

### Graceful shutdown (`podLifecycle`)

The chart sets a `preStop` sleep and a pod-level termination grace period so ingress load balancers can drain in-flight requests before Genesis receives `SIGTERM`.

| Value | Type | Default | Description |
| - | - | - | - |
| `podLifecycle.preStopSleepSeconds` | `int` | `30` | Seconds the main container sleeps in `preStop` before `SIGTERM`, giving the load balancer time to stop sending new traffic. |
| `podLifecycle.terminationGracePeriodSeconds` | `int` | `90` | Total grace period before `SIGKILL`. Must be **greater than** `preStopSleepSeconds` so Genesis can shut down after the sleep. Helm fails template rendering if this constraint is violated. |

```yaml theme={null}
podLifecycle:
  preStopSleepSeconds: 30
  terminationGracePeriodSeconds: 90
```

### Extension containers (`extraContainers`, `extraInitContainers`)

Pass additional Kubernetes container specs through the chart without forking templates.

| Value | Type | Default | Description |
| - | - | - | - |
| `extraContainers` | `list` | `[]` | Sidecar containers in the Genesis pod. |
| `extraInitContainers` | `list` | `[]` | Init containers that run before the `genesis` container starts. |

See [Genesis on AWS](/docs/deployments/AWS/Genesis-On-AWS) for YAML examples (log shipper, wait-for-dependency init container).

### Resource Requirements

**Default resource requests:**

| Resource | Request |
| - | - |
| CPU | 2000m (2 cores) |
| Memory | 6Gi |

<Note>
  No resource limits are set by default to avoid OOM kills. Adjust based on your workload.
</Note>

## Azure-Specific Considerations

### Managed Identity for Azure OpenAI

If using Azure OpenAI, you can configure a managed identity to access Azure OpenAI without storing API keys in Kubernetes secrets:

<Steps>
  <Step title="Create Managed Identity">
    If not using cluster's managed identity:

    ```bash theme={null}
    az identity create --resource-group <resource-group> --name genesis-identity
    ```
  </Step>

  <Step title="Grant Permissions">
    ```bash theme={null}
    # Get the identity's principal ID
    PRINCIPAL_ID=$(az identity show --resource-group <resource-group> --name genesis-identity --query principalId -o tsv)

    # Grant "Cognitive Services User" role on Azure OpenAI resource
    az role assignment create \
      --assignee $PRINCIPAL_ID \
      --role "Cognitive Services User" \
      --scope /subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.CognitiveServices/accounts/<azure-openai-resource-name>
    ```
  </Step>

  <Step title="Configure Pod Identity">
    For Workload Identity:

    * Create a service account with the managed identity annotation
    * Configure the StatefulSet to use the service account
  </Step>

  <Step title="Retrieve API Key (Optional)">
    Using managed identity in an init container or startup script:

    ```bash theme={null}
    az login --identity
    az cognitiveservices account keys list \
      --name <azure-openai-resource-name> \
      --resource-group <resource-group> \
      --query key1 -o tsv
    ```
  </Step>
</Steps>

### Azure Disk Encryption

Azure managed disks created by the Azure Disk CSI driver on AKS are encrypted by default. Verify:

```bash theme={null}
kubectl get storageclass -o yaml
```

### Virtual Network Configuration

Ensure your AKS cluster nodes have:

| Requirement | Purpose |
| - | - |
| Outbound Internet Access | For pulling images from the container registry provided by Genesis |
| DNS Resolution | For external API calls (OpenAI, Azure OpenAI, etc.) |
| Network Security Group Rules | Allow ingress on ports 80/443 if using Application Gateway, or node port if using NodePort service |

## Troubleshooting

<AccordionGroup>
  <Accordion title="Pod Not Starting">
    ```bash theme={null}
    # Check pod status
    kubectl describe pod -n genesis genesis-0

    # Check events
    kubectl get events -n genesis --sort-by='.lastTimestamp'
    ```

    **Common issues:**

    * **Image pull errors:** Check network connectivity to the container registry provided by Genesis
    * **PVC issues:** Verify Azure Disk CSI driver is installed and storage class exists
    * **Resource constraints:** Check node resources
  </Accordion>

  <Accordion title="Persistent Volume Issues">
    ```bash theme={null}
    # Check PVC status
    kubectl get pvc -n genesis
    kubectl describe pvc genesis-data-genesis-0 -n genesis

    # Check PV
    kubectl get pv
    kubectl describe pv <pv-name>

    # Check Azure Disk CSI driver logs
    kubectl logs -n kube-system -l app=csi-azuredisk-controller
    ```
  </Accordion>

  <Accordion title="Ingress Not Working">
    **For Application Gateway Ingress Controller:**

    ```bash theme={null}
    # Check controller logs
    kubectl logs -n kube-system -l app=ingress-azure

    # Check ingress status
    kubectl describe ingress -n genesis genesis

    # Verify Application Gateway configuration
    az network application-gateway show \
      --resource-group <resource-group> \
      --name <application-gateway-name>
    ```

    **For NGINX Ingress:**

    ```bash theme={null}
    # Check controller pods
    kubectl get pods -n ingress-nginx

    # Check controller logs
    kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller
    ```
  </Accordion>

  <Accordion title="Database Issues">
    ```bash theme={null}
    # Access pod shell
    kubectl exec -it -n genesis genesis-0 -- /bin/bash

    # Check database file
    ls -lh /app/.genesis/db/

    # Check database integrity (if SQLite tools available)
    sqlite3 /app/.genesis/db/genesis.db "PRAGMA integrity_check;"
    ```
  </Accordion>

  <Accordion title="Viewing Logs">
    ```bash theme={null}
    # View current logs
    kubectl logs -n genesis genesis-0

    # Follow logs
    kubectl logs -n genesis genesis-0 --follow

    # View previous container logs (if pod restarted)
    kubectl logs -n genesis genesis-0 --previous
    ```
  </Accordion>
</AccordionGroup>

## Upgrading Genesis

When a new Genesis version is released:

```bash theme={null}
# Update image tag in values file or override
helm upgrade genesis ./genesis -f genesis-values.yaml --set image.tag=v1.3.2

# Or update values.yaml and upgrade
helm upgrade genesis ./genesis -f genesis-values.yaml

# Verify upgrade
kubectl get pods -n genesis -o jsonpath='{.items[0].spec.containers[0].image}'
kubectl get pods -n genesis
```

<Info>
  The StatefulSet will perform a rolling update, and the persistent volume will be reattached to the new pod.
</Info>

<Note>
  For values-driven upgrades with the AKS helper scripts, update `image.tag` in the relevant `values-aks-*.yaml` file and rerun the matching `azure-aks/deploy-aks-*.sh` helper. If you use `azure-aks/update-aks-images.sh`, GA stacks default to `v1.18.2` and edge stacks default to `edge` when you do not pass an explicit override.
</Note>

<Tip>
  Purely numeric `image.tag` values (for example ECR build IDs) are coerced to strings automatically in chart **0.3.55** and later. On older charts, quote the tag in YAML or use `--set-string image.tag=...` instead of `--set`.
</Tip>

## Uninstalling

```bash theme={null}
# Uninstall Helm release
helm uninstall genesis

# Note: PVC is retained by default to preserve data
# To delete PVC and all data:
kubectl delete pvc genesis-data-genesis-0 -n genesis

# Delete namespace
kubectl delete namespace genesis
```

<Warning>
  Deleting the PVC will permanently remove all Genesis data including the database, git repositories, and uploaded files.
</Warning>

## Additional Resources

<CardGroup cols={3}>
  <Card title="Azure AKS Documentation" icon="microsoft" href="https://learn.microsoft.com/en-us/azure/aks/">
    Official Azure Kubernetes Service documentation
  </Card>

  <Card title="Azure Disk CSI Driver" icon="hard-drive" href="https://learn.microsoft.com/en-us/azure/aks/azure-disk-csi">
    Azure Disk CSI Driver setup guide
  </Card>

  <Card title="Application Gateway Ingress" icon="network-wired" href="https://azure.github.io/application-gateway-kubernetes-ingress/">
    Application Gateway Ingress Controller documentation
  </Card>
</CardGroup>
