> For the complete documentation index, see [llms.txt](https://doc.ozforensics.com/oz-knowledge/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://doc.ozforensics.com/oz-knowledge/guides/administrator-guide/installation/installation-in-kubernetes/how-to-install-instant-api-in-kubernetes.md).

# How to Install Instant API in Kubernetes

Oz Instant API is a stateless deployment of Oz API: it runs liveness and face matching analyses and returns results immediately, without storing any media or results. This guide covers installing it in Kubernetes via the official [Helm chart](/oz-knowledge/guides/administrator-guide/installation/installation-in-kubernetes/helm-charts.md#instant-api).

The example below is based on the 1.0.2 chart version. However, this is an illustrative example, and we recommend using the latest chart version.

## Prerequisites

Before you start: request these from your Oz representative.

* **DockerHub credentials** – to pull the Instant API image (used in **step 2** of **Preparation**).
* **License key** – required to run Instant API (**step 3** of **Preparation**).
* **Image tag** – the application image version to deploy (used in your `values` file).

If you plan to use OzCapsula, you will also need to generate a JWT key pair (**step 4** of **Preparation**).

**Kubernetes 1.23+** – the chart uses `autoscaling/v2` (HPA with behavior policies, GA in 1.23), `networking.k8s.io/v1` (Ingress, GA in 1.19), and `policy/v1` (PDB, GA in 1.21). Clusters running 1.21–1.22 are partially supported via automatic fallback to `autoscaling/v2beta2` and `policy/v1beta1`.

Before installing, make sure the following components are running in your cluster:

* **Ingress controller** (one of, when using Ingress API):
  * [F5 NGINX Ingress Controller](https://github.com/nginx/kubernetes-ingress) >= 3.0 (recommended, IngressClass: `f5-nginx`).
  * [ingress-nginx](https://github.com/kubernetes/ingress-nginx) >= 1.0 (legacy, [retiring](https://github.com/kubernetes/ingress-nginx?tab=readme-ov-file#retiring), IngressClass: `nginx`).
* **Gateway API CRDs** >= 1.0 and a compatible controller (e.g. **Istio** >= 1.20, Envoy Gateway, Cilium) – when using Gateway API instead of Ingress. The chart creates `gateway.networking.k8s.io/v1` resources (Gateway, HTTPRoute).
* **cert-manager** >= 1.0 with a configured `ClusterIssuer` (for TLS certificates). For Gateway API TLS, cert-manager >= 1.5 is required with `--feature-gates=ExperimentalGatewayAPISupport=true`.
* **metrics-server** >= 0.5 (required for HPA autoscaling).
* **cluster-autoscaler** (optional, for automatic node scaling).
* **KEDA** >= 2.0 (optional, alternative to vanilla HPA; the chart uses `keda.sh/v1alpha1` ScaledObject API).
* **Prometheus Operator** (kube-prometheus-stack) >= 0.44 (optional, for `monitoring.coreos.com/v1` ServiceMonitor support).

## Preparation

### 1. Create a namespace

`kubectl create namespace api-instant`

### 2. Create image pull secret

{% code title="" expandable="true" %}

```bash
kubectl -n api-instant create secret docker-registry oz-dockerhub-creds \
  --docker-server=docker.io \
  --docker-username=<USERNAME> \
  --docker-password='<PASSWORD>' \
  --docker-email=<EMAIL>
```

{% endcode %}

Alternatively, if you already have a pre-encoded `.dockerconfigjson` base64 string (e.g., provided by Oz Support), use one of the options:

{% code title="Option A: decode inline (Linux/macOS)" expandable="true" %}

```bash
kubectl -n api-instant create secret generic oz-dockerhub-creds \
  --type=kubernetes.io/dockerconfigjson \
  --from-literal=.dockerconfigjson="$(echo '<BASE64_ENCODED_DOCKERCONFIGJSON>' | base64 -d)"
```

{% endcode %}

{% code title="Option B: save to file first, then create from file" expandable="true" %}

```bash
echo '<BASE64_ENCODED_DOCKERCONFIGJSON>' | base64 -d > dockerconfig.json
kubectl -n api-instant create secret generic oz-dockerhub-creds \
  --type=kubernetes.io/dockerconfigjson \
  --from-file=.dockerconfigjson=dockerconfig.json
rm dockerconfig.json
```

{% endcode %}

{% code title="Option C: apply a Secret manifest with the base64 string in the data field" expandable="true" %}

```bash
apiVersion: v1
kind: Secret
metadata:
  name: oz-dockerhub-creds
  namespace: api-instant
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: <BASE64_ENCODED_DOCKERCONFIGJSON>
```

{% endcode %}

Then:

{% code title="" expandable="true" %}

```
kubectl apply -f secret.yaml
```

{% endcode %}

{% hint style="info" %}
The value under `.dockerconfigjson` is a base64-encoded JSON string in the format `{"auths":{"<https://index.docker.io/v1/>":{"username":"...","password":"...","auth":"..."}}}`. Contact Oz Support to get the credentials. The secret name must match `imagePull.secret` in your values (default: `oz-dockerhub-creds`).
{% endhint %}

### 3. Get a license key

Contact [Oz Support](mailto:info@ozforensics.com) to get your license key.

### 4. Create JWT secret (optional)

Instant API in case of using OzCapsula requires a JWT key pair for session tokens. Generate the keys and create the secret:

{% code title="" expandable="true" %}

```bash
openssl ecparam -name secp384r1 -genkey -noout | openssl pkcs8 -topk8 -nocrypt -out jwt.key
openssl ec -in jwt.key -pubout -out jwt.pub

kubectl -n api-instant create secret generic api-instant-jwt \
  --from-file=jwt.pub=./jwt.pub \
  --from-file=jwt.key=./jwt.key
```

{% endcode %}

{% hint style="info" %}
The secret name must match `api.jwt.secretName` in your values (default: `""`).
{% endhint %}

## Installation

### 1. Add Helm repository

{% code title="" expandable="true" %}

```
helm repo add ozchartmuseum https://chartmuseum.infra.ozforensics.ai helm repo update
```

{% endcode %}

### 2. Apply basic configuration

Create a `my-values.yaml` file:

{% code title="my-values.yaml" expandable="true" %}

```yaml
api:
  hostname: api-instant.example.com
  licenseKey: "YOUR_LICENSE_KEY"
  image:
    repository: ozforensics/oz-api-instant
    tag: "6.6-ask-oz-support-team"
```

{% endcode %}

Install the chart:

{% code title="" expandable="true" %}

```bash
helm install oz-api-instant ozchartmuseum/oz-api-instant \
  -n api-instant \
  -f my-values.yaml
```

{% endcode %}

### 3. Verify the installation

To check that the API is up and responding, call `GET {{host}}/api/healthcheck`.

To confirm it processes verification requests, run an analysis against the deployed Instant API.

### Upgrade

{% code title="" expandable="true" %}

```bash
helm repo update
helm upgrade oz-api-instant ozchartmuseum/oz-api-instant \
  -n api-instant \
  -f my-values.yaml
```

{% endcode %}

## Key Configuration

Only the parameters marked **Mandatory** must be set (they appear in the [Basic configuration](#id-2.-apply-basic-configuration) above). All other parameters are optional – you only need to be aware that they exist and can override them if needed.

<table data-header-hidden="false" data-header-sticky><thead><tr><th>Parameter</th><th>Description</th><th>Default</th></tr></thead><tbody><tr><td><code>imagePull.secret</code></td><td>Image pull secret name</td><td><code>oz-dockerhub-creds</code></td></tr><tr><td><code>imagePull.policy</code></td><td>Image pull policy</td><td><code>IfNotPresent</code></td></tr><tr><td><code>affinity</code></td><td>Global pod affinity rules (nodeAffinity, podAffinity, podAntiAffinity)</td><td><code>{}</code></td></tr><tr><td><code>nodeSelector</code></td><td>Global node selector constraints</td><td><code>{}</code></td></tr><tr><td><code>tolerations</code></td><td>Global tolerations</td><td><code>[]</code></td></tr><tr><td><code>api.hostname</code></td><td><strong>Mandatory.</strong> Public hostname for the Instant API</td><td><code>somehost.example.local</code></td></tr><tr><td><code>api.additionalHostnames</code></td><td>Additional hostnames (e.g. CDN aliases); added to Ingress rules and Gateway listeners</td><td><code>[]</code></td></tr><tr><td><code>api.licenseKey</code></td><td><strong>Mandatory.</strong> License key for Instant API (from Oz Support)</td><td><code>""</code></td></tr><tr><td><code>api.extraEnvs</code></td><td>Additional environment variables (key-value pairs). Stored in a ConfigMap – do not put secrets here.</td><td><code>{NUM_WORKERS: "16", ...}</code></td></tr><tr><td><code>api.extraEnvVarsSecret</code></td><td>Name of an existing Secret whose keys are injected as env vars (via <code>envFrom.secretRef</code>). Use for sensitive values such as S3 credentials.</td><td><code>""</code></td></tr><tr><td><code>api.image.tag</code></td><td><strong>Mandatory.</strong> Application image tag (contact Oz Support)</td><td><code>""</code></td></tr><tr><td><code>api.resources</code></td><td>CPU and memory requests/limits</td><td>requests/limits: 15 CPU, 28Gi RAM</td></tr><tr><td><code>api.autoscaling.hpa.enabled</code></td><td>Enable HPA</td><td><code>true</code></td></tr><tr><td><code>api.autoscaling.keda.enabled</code></td><td>Enable Keda ScaledObject</td><td><code>false</code></td></tr><tr><td><code>api.ingress.enabled</code></td><td>Create Ingress resource</td><td><code>true</code></td></tr><tr><td><code>api.ingress.className</code></td><td>Ingress class</td><td><code>nginx</code></td></tr><tr><td><code>api.ingress.annotations</code></td><td>Ingress annotations (proxy, CORS, etc.)</td><td><code>{}</code></td></tr><tr><td><code>api.ingress.tls.enabled</code></td><td>Enable TLS</td><td><code>true</code></td></tr><tr><td><code>api.ingress.tls.clusterIssuer</code></td><td>cert-manager ClusterIssuer</td><td><code>letsencrypt-production</code></td></tr><tr><td><code>api.gateway.enabled</code></td><td>Create Gateway API resources (mutually exclusive with <code>api.ingress</code>)</td><td><code>false</code></td></tr><tr><td><code>api.gateway.className</code></td><td>Gateway class name</td><td><code>istio</code></td></tr><tr><td><code>api.gateway.annotations</code></td><td>Gateway annotations</td><td><code>{}</code></td></tr><tr><td><code>api.gateway.tls.enabled</code></td><td>Enable TLS on Gateway</td><td><code>true</code></td></tr><tr><td><code>api.gateway.tls.clusterIssuer</code></td><td>cert-manager ClusterIssuer for Gateway</td><td><code>letsencrypt-production-dns</code></td></tr><tr><td><code>api.proxy.enabled</code></td><td>Enable HTTP proxy</td><td><code>false</code></td></tr><tr><td><code>api.proxy.url</code></td><td>Proxy URL</td><td><code>&#x3C;http://proxy.local:3128</code>></td></tr><tr><td><code>api.proxy.auth.existingSecret</code></td><td>Name of existing Secret with <code>all_proxy</code> key containing the full proxy URL. If set, <code>login</code>/<code>password</code> are ignored.</td><td><code>""</code></td></tr><tr><td><code>api.proxy.auth.login</code></td><td>Proxy username (used when <code>existingSecret</code> is not set)</td><td><code>""</code></td></tr><tr><td><code>api.proxy.auth.password</code></td><td>Proxy password – special characters are percent-encoded automatically (used when <code>existingSecret</code> is not set)</td><td><code>""</code></td></tr><tr><td><code>api.serviceMonitor.enabled</code></td><td>Create Prometheus ServiceMonitor</td><td><code>true</code></td></tr></tbody></table>

For the full list of parameters, see `values.yaml`.

## Traffic management

This chart supports two ways to route external traffic to Instant API: **K8s Ingress API** and **K8s Gateway API**. They are mutually exclusive – enable only one at a time.

{% hint style="warning" %}
**Important:** Any traffic management configuration (load balancing, retries, connection pooling, CORS, etc.) should be validated with load testing that reflects the chosen setup and the client's actual traffic profile.
{% endhint %}

You can use any controller that implements the chosen K8s API. The examples below show the most common options.

### Option 1: K8s Ingress API (default)

Set `api.ingress.enabled: true` (default) and `api.gateway.enabled: false`.

The chart creates an `Ingress` resource. You need an Ingress controller installed in your cluster. Set `api.ingress.className` to match your controller's IngressClass.

`api.ingress.annotations` is empty by default. Add annotations that your controller needs.

#### **Ingress-NGINX controller – example configuration**

{% hint style="info" %}
**Note:** The [Ingress-NGINX](https://github.com/kubernetes/ingress-nginx) project is [retiring](https://github.com/kubernetes/ingress-nginx?tab=readme-ov-file#retiring). Consider migrating to the F5 NGINX Ingress Controller.
{% endhint %}

{% code title="Ingress-NGINX" expandable="true" %}

```yaml
api:
  ingress:
    className: nginx
    annotations:
      nginx.ingress.kubernetes.io/proxy-body-size: "150m"
      nginx.ingress.kubernetes.io/load-balance: "least_conn"
      nginx.ingress.kubernetes.io/upstream-keepalive-connections: "0"
      nginx.ingress.kubernetes.io/proxy-next-upstream: "error timeout http_500"
      nginx.ingress.kubernetes.io/proxy-next-upstream-timeout: "10"
      nginx.ingress.kubernetes.io/proxy-next-upstream-tries: "2"
      nginx.ingress.kubernetes.io/enable-cors: "true"
      nginx.ingress.kubernetes.io/cors-allow-origin: "*"
      nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, OPTIONS, PATCH, DELETE"
      nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,x-forensic-access-token"
```

{% endcode %}

#### **F5 NGINX Ingress Controller – example configuration**

{% code title="F5 NGINX" expandable="true" %}

```yaml
api:
  ingress:
    className: f5-nginx
    annotations:
      nginx.org/client-max-body-size: "150m"
      nginx.org/lb-method: "least_conn"
      nginx.org/keepalive: "0"
      nginx.org/proxy-next-upstream: "error timeout http_500"
      nginx.org/proxy-next-upstream-timeout: "10"
      nginx.org/proxy-next-upstream-tries: "2"
      nginx.org/location-snippets: |
        add_header Access-Control-Allow-Origin "*" always;
        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PATCH, DELETE" always;
        add_header Access-Control-Allow-Headers "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,x-forensic-access-token" always;
```

{% endcode %}

{% hint style="info" %}
F5 NGINX Ingress Controller does not have dedicated CORS annotations. Use `nginx.org/location-snippets` with `add_header` directives instead.

See the [F5 NGINX IC annotations docs](https://docs.nginx.com/nginx-ingress-controller/configuration/ingress-resources/advanced-configuration-with-annotations/) and the [migration guide from ingress-nginx](https://docs.nginx.com/nginx-ingress-controller/install/migrate-ingress-nginx/).
{% endhint %}

#### **Other Ingress controllers**

Any controller that implements the Kubernetes Ingress API will work. Set `api.ingress.className` to your controller's IngressClass and provide the appropriate annotations.

### Option 2: Gateway API

Set `api.ingress.enabled: false` and `api.gateway.enabled: true`.

The chart creates a `Gateway` and `HTTPRoute` resource. You need a controller that implements the [Kubernetes Gateway API](https://gateway-api.sigs.k8s.io/) (e.g., Istio, Envoy Gateway, Cilium). Set `api.gateway.className` to match your GatewayClass.

#### **Istio – example configuration**

**Prerequisites:**

* Istio >= 1.20 with [Gateway API CRDs](https://gateway-api.sigs.k8s.io/) installed
* cert-manager >= 1.5 with `--feature-gates=ExperimentalGatewayAPISupport=true`

{% code title="Istio" expandable="true" %}

```yaml
api:
  ingress:
    enabled: false
  gateway:
    enabled: true
    className: istio
    tls:
      clusterIssuer: letsencrypt-production-dns
      secretName: api-gateway-tls-certs
    annotations:
      # Cloud load balancer annotations (AWS NLB example)
      service.beta.kubernetes.io/aws-load-balancer-attributes: load_balancing.cross_zone.enabled=true
      service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
      service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
```

{% endcode %}

The chart creates `Gateway` and `HTTPRoute` resources. Traffic policies such as load balancing, retries, and CORS **are not managed by this chart** when using Gateway API. Configure them with Istio CRDs applied alongside the Helm release.

<table data-header-hidden="false" data-header-sticky><thead><tr><th>Traffic policy</th><th>Istio resource</th><th>Configuration</th></tr></thead><tbody><tr><td>Load balancing</td><td><code>DestinationRule</code></td><td><code>loadBalancer.simple: LEAST_REQUEST</code></td></tr><tr><td>Connection pooling</td><td><code>DestinationRule</code></td><td><code>connectionPool.http.maxRequestsPerConnection</code></td></tr><tr><td>Outlier detection</td><td><code>DestinationRule</code></td><td><code>outlierDetection</code> (eject unhealthy endpoints on 5xx)</td></tr><tr><td>Retries</td><td><code>EnvoyFilter</code></td><td>Route-level <code>retryPolicy</code></td></tr><tr><td>CORS</td><td><code>EnvoyFilter</code></td><td>Envoy CORS filter (<code>typedPerFilterConfig</code>)</td></tr></tbody></table>

**Istio – load balancing, connection pooling, and outlier detection**

Create a `DestinationRule` targeting the Instant API service (replace `<release-name>` with your Helm release name, e.g. `oz-api-instant`):

{% code title="DestinationRule" expandable="true" %}

```yaml
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: api-instant-traffic-policy
  namespace: api-instant
spec:
  host: <release-name>-api.api-instant.svc.cluster.local
  trafficPolicy:
    loadBalancer:
      simple: LEAST_REQUEST
    connectionPool:
      http:
        maxRequestsPerConnection: 1            # disable keepalive between gateway and upstream pods
    outlierDetection:
      consecutive5xxErrors: 1                  # eject endpoint on first 5xx
      interval: 10s
      baseEjectionTime: 30s
```

{% endcode %}

By default, `LEAST_REQUEST` picks the least-loaded endpoint out of 2 random candidates. For more even distribution across pods (especially useful with heavy liveness verification workloads), create an `EnvoyFilter` to increase the candidate pool size:

{% code title="EnvoyFilter for candidate pool size" expandable="true" %}

```yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: api-instant-least-request-lb-config
  namespace: api-instant
spec:
  workloadSelector:
    labels:
      gateway.networking.k8s.io/gateway-name: <release-name>-gateway-api
  configPatches:
  - applyTo: CLUSTER
    match:
      context: GATEWAY
      cluster:
        name: outbound|8080||<release-name>-api.api-instant.svc.cluster.local
    patch:
      operation: MERGE
      value:
        lb_policy: LEAST_REQUEST
        least_request_lb_config:
          choice_count: 5
```

{% endcode %}

{% hint style="info" %}
Higher `choice_count` values improve load distribution but add a small overhead per request. A value of 5 is a good starting point for clusters with 5+ pods.
{% endhint %}

Alternatively, in **Istio >= 1.22** (Envoy >= 1.30) you can use the `FULL_SCAN` selection method, which scans **all** endpoints and picks the one with the fewest active requests (ties are broken via reservoir sampling). This guarantees the optimal choice but costs O(n) per request, so it is best suited for deployments with a small number of pods or low RPS:

{% code title="EnvoyFilter with FULL\_SCAN" expandable="true" %}

```yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: api-instant-least-request-lb-full-scan
  namespace: api-instant
spec:
  workloadSelector:
    labels:
      gateway.networking.k8s.io/gateway-name: <release-name>-gateway-api
  configPatches:
  - applyTo: CLUSTER
    match:
      context: GATEWAY
      cluster:
        name: outbound|8080||<release-name>-api.api-instant.svc.cluster.local
    patch:
      operation: MERGE
      value:
        load_balancing_policy:
          policies:
          - typed_extension_config:
              name: envoy.load_balancing_policies.least_request
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest
                selection_method: FULL_SCAN
```

{% endcode %}

{% hint style="info" %}
When `load_balancing_policy` is set it takes precedence over the legacy `lb_policy` / `least_request_lb_config` fields. Use one approach or the other, not both.
{% endhint %}

**Istio – retries**

Create an `EnvoyFilter` to add a route-level retry policy:

{% code title="EnvoyFilter for retry policy" expandable="true" %}

```yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: api-instant-retries
  namespace: api-instant
spec:
  workloadSelector:
    labels:
      gateway.networking.k8s.io/gateway-name: <release-name>-gateway-api
  configPatches:
  - applyTo: HTTP_ROUTE
    match:
      context: GATEWAY
    patch:
      operation: MERGE
      value:
        route:
          retryPolicy:
            retryOn: "connect-failure,reset,5xx"
            numRetries: 2
```

{% endcode %}

**Istio – CORS**

Create an `EnvoyFilter` to configure CORS headers:

{% code title="EnvoyFilter for CORS" expandable="true" %}

```yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: api-instant-cors
  namespace: api-instant
spec:
  workloadSelector:
    labels:
      gateway.networking.k8s.io/gateway-name: <release-name>-gateway-api
  configPatches:
  - applyTo: HTTP_ROUTE
    match:
      context: GATEWAY
    patch:
      operation: MERGE
      value:
        typedPerFilterConfig:
          envoy.filters.http.cors:
            "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.CorsPolicy
            filterEnabled:
              defaultValue:
                numerator: 100
                denominator: HUNDRED
            allowOriginStringMatch:
            - safeRegex:
                regex: ".*"
            allowMethods: "GET, POST, OPTIONS, PATCH, DELETE"
            allowHeaders: "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,x-forensic-access-token"
```

{% endcode %}

{% hint style="info" %}
Replace `<release-name>` with your Helm release name (e.g. `oz-api-instant`). The `DestinationRule` and `EnvoyFilter` resources above are **not part of this chart** – apply them with `kubectl apply` after installing the Helm release.

The exact EnvoyFilter configuration may vary depending on your Istio version. Test with `istioctl proxy-config routes` and `istioctl proxy-config listeners` to verify the filters are applied correctly.
{% endhint %}

#### **Other Gateway API controllers**

Any controller that implements the Kubernetes Gateway API will work. Set `api.gateway.className` to your GatewayClass and add controller-specific settings via `api.gateway.annotations`.

### OpenShift Route

This chart does not create OpenShift Route resources. If you need to use Routes, set `api.ingress.enabled: false` and `api.gateway.enabled: false`, then create a Route manually for the `http-api` port of the service (`<release-name>-api`).

### TLS certificates

Both Ingress and Gateway options support automatic TLS certificate provisioning via **cert-manager**. Set `clusterIssuer` or `issuer` under `api.ingress.tls` or `api.gateway.tls` to reference your cert-manager issuer.

## Proxy configuration

If the Instant API pod requires internet access through an HTTP proxy, set `api.proxy.enabled: true`.

Both options below are equally secure – choose whichever fits your setup.

### Option 1: inline credentials

Special characters in the password are percent-encoded automatically by the chart.

{% code title="" expandable="true" %}

```yaml
api:
  proxy:
    enabled: true
    url: "http://proxy.local:3128"
    auth:
      login: "your_login"
      password: "your_p@ss:w0rd!"
```

{% endcode %}

### Option 2: existing Secret

Use this when credentials contain characters that are hard to escape in YAML, or when you want to keep credentials out of values files entirely. Create a Secret with the full proxy URL pre-encoded:

{% code title="" expandable="true" %}

```bash
kubectl -n api-instant create secret generic oz-proxy-creds \
  --from-literal=all_proxy='http://your_login:your_p%40ss%3Aw0rd%21@proxy.local:3128'
```

{% endcode %}

Then, reference it in values:

{% code title="" expandable="true" %}

```yaml
api:
  proxy:
    enabled: true
    url: "http://proxy.local:3128"
    auth:
      existingSecret: oz-proxy-creds
```

{% endcode %}

{% hint style="info" %}
When `auth.existingSecret` is set, `auth.login` and `auth.password` are ignored. The Secret must contain a key named `all_proxy` with the complete proxy URL including credentials.
{% endhint %}

## S3 storage

Instant API can store liveness verification data in an S3 or S3-compatible bucket instead of local storage. Two env vars are always required to enable S3:&#x20;

* `OZ_INSTANT_SAVING_ARTIFACTS_ENABLED: "true"` (for API versions below 6.5: `OZ_INSTANT_SAVING_ARTIFACTS_IN_S3_ENABLED`)
* `OZ_FILE_STORAGE_TYPE: "S3"`.&#x20;

Non-sensitive S3 parameters go in `api.extraEnvs`; credentials must be placed in a pre-created Secret referenced by `api.extraEnvVarsSecret` – its keys are injected as env vars alongside the ConfigMap.

### Option 1: AWS S3 with access keys

Create a Secret with the credentials:

{% code title="" expandable="true" %}

```bash
kubectl -n api-instant create secret generic oz-api-s3-creds \
  --from-literal=OZ_STATIC_S3_ACCESS_KEY='<YOUR_S3_ACCESS_KEY>' \
  --from-literal=OZ_STATIC_S3_SECRET_KEY='<YOUR_S3_SECRET_KEY>'
```

{% endcode %}

Reference it in values alongside the non-sensitive S3 params:

{% code title="" expandable="true" %}

```yaml
api:
  extraEnvVarsSecret: oz-api-s3-creds
  extraEnvs:
    OZ_INSTANT_SAVING_ARTIFACTS_IN_S3_ENABLED: "true"
    OZ_FILE_STORAGE_TYPE: "S3"
    OZ_STATIC_S3_BUCKET: "oz-bucket"
    OZ_STATIC_S3_ENDPOINT_URL: "https://s3.us-east-1.amazonaws.com/"
    OZ_STATIC_S3_REGION_NAME: "us-east-1"
    OZ_STATIC_S3_BUCKET_URL: "None"
    OZ_STATIC_S3_BASE_URL: "http://localhost/static"
    OZ_STATIC_S3_SUFFIX: ""           # root folder in bucket; empty = bucket root
```

{% endcode %}

### Option 2: AWS S3 with IAM role (EKS IRSA)

Use this when running on EKS with [IAM Roles for Service Accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). No access keys are needed – the pod assumes an IAM role via the service account.

**1. Create the S3 bucket.**

**2. Create an OIDC Identity Provider in IAM:**

* Type: OpenID Connect
* Audience: `sts.amazonaws.com`

**3. Create an IAM policy:**

{% code title="" expandable="true" %}

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListBucket",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::YOUR_BUCKET_NAME"
    },
    {
      "Sid": "ObjectOperations",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*"
    }
  ]
}
```

{% endcode %}

**4. Create an IAM role with the following trust policy, then attach the policy from step 3:**

{% code title="" expandable="true" %}

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "<Identity provider ARN>"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "<Identity provider name>:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}
```

{% endcode %}

Example:

{% code title="" expandable="true" %}

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789000:oidc-provider/oidc.eks.us-east-2.amazonaws.com/id/1234567890ABCDEFGHIGHKLMOPQRSTUV"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.us-somewhere-1.amazonaws.com/id/1234567890ABCDEFGHIGHKLMOPQRSTUV:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}
```

{% endcode %}

**5. Annotate the chart's service account with the role ARN:**

{% code title="" expandable="true" %}

```yaml
serviceAccount:
  enabled: true
  annotations:
    eks.amazonaws.com/role-arn: "arn:aws:iam::123456789000:role/<YOUR_ROLE_NAME>"
    eks.amazonaws.com/sts-regional-endpoints: "true"
```

{% endcode %}

**6. Set S3 env vars (no access keys needed):**

{% code title="" expandable="true" %}

```yaml
api:
  extraEnvs:
    OZ_INSTANT_SAVING_ARTIFACTS_IN_S3_ENABLED: "true"
    OZ_FILE_STORAGE_TYPE: "S3"
    OZ_STATIC_S3_BUCKET: "oz-bucket"
    OZ_STATIC_S3_ENDPOINT_URL: "https://s3.us-east-1.amazonaws.com/"
    OZ_STATIC_S3_REGION_NAME: "us-east-1"
    OZ_STATIC_S3_BUCKET_URL: "None"
    OZ_STATIC_S3_BASE_URL: "http://localhost/static"
    OZ_STATIC_S3_SUFFIX: ""
```

{% endcode %}

### Option 3: S3-compatible storage (MinIO, NetApp StorageGRID, etc.)

Tested compatible: MinIO 5.4.0, NetApp StorageGRID 11.8.

{% code title="" expandable="true" %}

```bash
kubectl -n api-instant create secret generic oz-api-s3-creds \
  --from-literal=OZ_STATIC_S3_ACCESS_KEY='<YOUR_S3_ACCESS_KEY>' \
  --from-literal=OZ_STATIC_S3_SECRET_KEY='<YOUR_S3_SECRET_KEY>'
```

{% endcode %}

{% code title="" expandable="true" %}

```yaml
api:
  extraEnvVarsSecret: oz-api-s3-creds
  extraEnvs:
    OZ_INSTANT_SAVING_ARTIFACTS_IN_S3_ENABLED: "true"
    OZ_FILE_STORAGE_TYPE: "S3"
    OZ_STATIC_S3_BUCKET: "oz-bucket"
    OZ_STATIC_S3_ENDPOINT_URL: "http://your-s3-host:9000"   # protocol + host + port
    OZ_STATIC_S3_REGION_NAME: "us-east-1"                   # any value accepted by your storage
    OZ_STATIC_S3_BUCKET_URL: "None"
    OZ_STATIC_S3_BASE_URL: "http://localhost/static"
    OZ_STATIC_S3_SUFFIX: ""
```

{% endcode %}

## Uninstall

{% code title="" expandable="true" %}

```bash
helm uninstall oz-api-instant -n api-instant
```

{% endcode %}

{% hint style="info" %}
This does not delete the JWT and image pull secrets. Remove them manually if needed.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://doc.ozforensics.com/oz-knowledge/guides/administrator-guide/installation/installation-in-kubernetes/how-to-install-instant-api-in-kubernetes.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
