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

# Kubernetes and OpenShift

> Install, adapt and verify 2501 with a chart and your site's values

Install 2501 from a verified release chart and your own `values.yaml`. Use `oc` in place of `kubectl` on OpenShift.

```mermaid theme={null}
flowchart LR
  A[Verify release files] --> B[Set site values]
  B --> C[Create secrets]
  C --> D[Review and apply]
  D --> E[Check readiness and sign in]
```

## Install

Use one namespace per installation. For an existing installation, follow [Upgrade](https://docs.2501.ai/0.19/deployment/upgrade.md) or [Move from the CLI](https://docs.2501.ai/0.19/deployment/migrate-from-cli.md).

**1. Download the build**

Get `2501-<build>.tgz` and `values.example.yaml` from [installation releases](https://github.com/2501-ai/install/releases). [Verify the files](https://docs.2501.ai/0.19/deployment/verify-downloads.md#verify-the-installation-files).

**2. Prepare image access**

Configure [access to 2501's registry](https://docs.2501.ai/0.19/deployment/pull-from-2501.md), or copy the build's images into your registry.

**3. Prepare the namespace**

Set `NS=ai-2501` in your shell, then run `kubectl create namespace "$NS"`.

**4. Configure the installation**

Write your values and create the required secrets below.

**5. Apply and verify**

Choose the kubectl, Helm or ArgoCD path below, then complete **Check**.

### Write your values.yaml

Copy `values.example.yaml` to `values.yaml`. This minimal example uses Kubernetes, a supplied PostgreSQL database and a certificate secret:

```yaml theme={null}
target: kubernetes
global:
  imageRegistry: 605134458542.dkr.ecr.eu-west-3.amazonaws.com/2501.ai/release
commandCenter:
  host: 2501.example.com
  tls:
    mode: secret
database:
  managed: true
elasticsearch:
  mode: none
```

| Decision | Value |
| - | - |
| OpenShift | `target: openshift` and `database.managed: false` |
| TLS terminates at your load balancer | `commandCenter.tls.mode: upstream` |
| Your registry | `global.imageRegistry` and `global.imagePullSecrets` |
| First administrator and organization | Optional `identity` settings |
| First LLM | Set `llm.provider`, `llm.baseUrl` and `llm.model` together, with its key secret |

Without LLM settings, add the model in Command Center after installation. Identity and LLM settings initialize the first installation; change them in Command Center afterwards. See the [configuration reference](https://docs.2501.ai/0.19/deployment/configuration.md) for all values.

### Create the secrets

Print the commands for your chosen values without installing anything:

```bash theme={null}
helm install ai2501 ./2501-<build>.tgz -n "$NS" -f values.yaml --dry-run=client
```

Replace the placeholders and run the printed secret commands once. Include the certificate for `commandCenter.host` when using `tls.mode: secret`. Your secret manager can create the same names and keys instead. Pods wait for missing secrets.

> [!WARNING]
>
> Keep secret values out of Git. Preserve the installation's encryption keys and database credentials across upgrades; replacing them can make existing data inaccessible.

### Apply

Render, review and apply the complete file. The same pruning flags make removed resources visible in the diff:

```bash theme={null}
helm template ai2501 ./2501-<build>.tgz -n "$NS" -f values.yaml > manifests.yaml
prune=(--prune
  -l 'app.kubernetes.io/part-of=2501,app.kubernetes.io/instance=ai2501'
  --prune-allowlist=core/v1/ConfigMap
  --prune-allowlist=core/v1/Service
  --prune-allowlist=core/v1/ServiceAccount
  --prune-allowlist=apps/v1/Deployment
  --prune-allowlist=batch/v1/Job
  --prune-allowlist=networking.k8s.io/v1/Ingress
  --prune-allowlist=networking.k8s.io/v1/NetworkPolicy
  --prune-allowlist=rbac.authorization.k8s.io/v1/Role
  --prune-allowlist=rbac.authorization.k8s.io/v1/RoleBinding
)
kubectl -n "$NS" diff -f manifests.yaml "${prune[@]}"
kubectl -n "$NS" apply -f manifests.yaml "${prune[@]}"
```

Review deletions before applying. This allowlist excludes volume claims and secrets. Repeat the render, review and apply after changing values.

## With Helm or ArgoCD

The chart is also available at `oci://ghcr.io/2501-ai/charts/2501`. [Verify the chart](https://docs.2501.ai/0.19/deployment/verify-downloads.md#verify-the-chart-in-the-registry) before installing it.

> [!WARNING]
>
> Select an exact build, such as `0.19.0-r1`. Helm and ArgoCD skip pre-release builds in ordinary version ranges such as `0.19.x`. They also skip them when no version is specified.

> [tab] Helm
>
> Use the same namespace, values and pre-created secrets:
>
> ```bash theme={null}
> helm upgrade --install ai2501 ./2501-<build>.tgz -n "$NS" -f values.yaml --wait
> ```
>
> To install the verified build from the registry instead:
>
> ```bash theme={null}
> helm upgrade --install ai2501 oci://ghcr.io/2501-ai/charts/2501 --version <build> -n "$NS" -f values.yaml --wait
> ```

> [tab] ArgoCD
>
> Unpack the verified chart into your Git repository beside `values.yaml`:
>
> ```yaml theme={null}
> spec:
>   source:
>     repoURL: <your-git-repository>
>     targetRevision: <branch>
>     path: <path>/2501
>     helm:
>       releaseName: ai2501
>       valueFiles: [../values.yaml]
>   destination: { namespace: <namespace> }
>   syncPolicy:
>     automated: { prune: true }
> ```
>
> The chart protects its data volumes from pruning. Keep secret values outside this repository.

## Your site's values

Apply changes through `values.yaml` so they survive upgrades. Expand the relevant recipe; the [configuration reference](https://docs.2501.ai/0.19/deployment/configuration.md) lists the remaining options.

> [accordion] HTTP proxy
>
> ```yaml theme={null}
> proxy:
>   url: http://proxy.bank.internal:3128
>   noProxy: [.bank.internal]
> ```
>
> The installation's own services bypass the proxy. Executors inherit these settings.

> [accordion] Corporate CA
>
> Create a ConfigMap with your PEM certificate, then set `caBundle: corporate-ca`:
>
> ```bash theme={null}
> kubectl -n "$NS" create configmap corporate-ca --from-file=ca.crt=corporate-ca.pem
> ```

> [accordion] HTTPS to the engine
>
> Issue a certificate covering `ai-2501-engine`, `ai-2501-engine.<namespace>.svc` and `ai-2501-engine.<namespace>.svc.cluster.local`. Adapt the prefix if you change `stackName`.
>
> ```bash theme={null}
> kubectl -n "$NS" create secret tls engine-tls --cert=engine-tls.crt --key=engine-tls.key
> ```
>
> ```yaml theme={null}
> engine:
>   tls:
>     existingSecret: engine-tls
> ```
>
> Command Center trusts this certificate. Restart both services after renewal.

> [accordion] Your PostgreSQL
>
> Create `pg-connection-v1` with `DATABASE_URL` and `DIRECT_URL`; the latter bypasses any connection pooler. Use your database's connection strings with TLS verification.
>
> ```yaml theme={null}
> database:
>   managed: false
>   existingSecret: pg-connection-v1
>   sslmode: verify-full
>   caBundle: database-ca
> ```
>
> Supply `database-ca` as a ConfigMap with `ca.crt` when your database needs a corporate CA. Otherwise omit `caBundle`.

> [accordion] Your Elasticsearch
>
> Create `es-credentials` with `ELASTICSEARCH_USERNAME` and `ELASTICSEARCH_PASSWORD`:
>
> ```yaml theme={null}
> elasticsearch:
>   mode: external
>   url: https://es.bank.internal:9200
>   existingSecret: es-credentials
>   environment: production
> ```
>
> Trust a corporate certificate with `caBundle`. For an existing installation, also enable Elasticsearch in Command Center as described in [Operations](https://docs.2501.ai/0.19/deployment/operations.md).

> [accordion] Storage and sizing
>
> Set `storageClass`, `database.size` and `elasticsearch.size` before creating volumes. Bound claims cannot change storage class or shrink. Choose `sizing.preset` for the installation's capacity; reserve additional namespace capacity for task and plugin containers.

### Executors in another namespace

Create the namespace and its image pull secrets, then set `executors.namespace`. The chart places executor permissions and the hosted MCP policy there. With this setting, rendered objects name their namespaces: omit `-n "$NS"` from the file's `diff` and `apply` commands.

### Additional installation settings

Put non-secret settings in `engine.extraEnv`. For secret values, create an `engine-extra-env` Secret and restart the engine after changing it. For example, `MCP_OAUTH_ALLOW_PRIVATE_PROVIDERS: "true"` allows OAuth identity providers on private addresses, still over HTTPS.

## Check

```bash theme={null}
kubectl -n "$NS" rollout status deploy/engine
kubectl -n "$NS" rollout status deploy/command-center
kubectl -n "$NS" get jobs -l app.kubernetes.io/component=bootstrap
kubectl -n "$NS" logs job/<newest-bootstrap-job>
```

The bootstrap log should show successful database migration and initialization. Investigate repeated failures before signing in at `https://<your host>`.

The bootstrap Job has `backoffLimit: 10`. After it reaches that limit, fix the cause shown in the logs and delete the failed Job:

```bash theme={null}
kubectl -n "$NS" delete job <failed-bootstrap-job>
```

Then repeat your deployment method: render and apply the manifests, run Helm upgrade, or sync ArgoCD. Check the new bootstrap Job's logs before signing in.

| Signal | Meaning |
| - | - |
| `/health` returns 200 | Command Center is running |
| `/ready` returns 503 on a new pod | Required migrations are missing, unfinished, failed, or cannot yet be checked |
| `/ready` returns 200 | This pod has confirmed the migrations its version requires |

Command Center keeps previous pods serving during the rollout until new pods become ready. Readiness stays successful after the first successful check; it is not an ongoing database health test. The engine is replaced during upgrades, so allow running work to finish first.

> [!WARNING]
>
> Windows targets using gMSA require [Docker Swarm](https://docs.2501.ai/0.19/deployment/docker-swarm.md#gmsa-hosts). They are not supported by this Kubernetes deployment.

## Hosted MCP servers

[Hosted plugins](https://docs.2501.ai/0.19/configure/plugins.md) run as non-root pods without a service account token. The chart supplies namespace-scoped permissions and a policy allowing DNS and public destinations, while blocking private and link-local destinations.

Your network plugin must enforce NetworkPolicy. Add an explicit policy exception for required internal services or NodeLocal DNSCache. Review the network plugin's startup enforcement before using untrusted images. Ensure image pull secrets and namespace quotas cover these pods.

## Centrals

For local [Central](https://docs.2501.ai/0.19/configure/centrals.md) execution, prepare a dedicated namespace with restricted pod security, image access, scoped pod/attach permissions and network policies. Set `engine.extraEnv.CENTRAL_LOCAL_K8S_NAMESPACE` to that namespace. The chart does not prepare this separate Central environment for you.

Leave **Cluster (optional)** at **No cluster (default)** to use this local environment, or select a registered [cluster](https://docs.2501.ai/0.19/configure/clusters.md). Run the Central's dry run before assigning work; a successful check does not validate task credentials.

> [accordion] Prepare local Central execution
>
> This example uses `ai-2501` for the installation and `central` for task containers. Replace `ai-2501` in the binding if your installation uses another namespace. Save the following as `central.yaml`:
>
> ```yaml theme={null}
> apiVersion: v1
> kind: Namespace
> metadata:
>   name: central
>   labels:
>     pod-security.kubernetes.io/enforce: restricted
> ---
> apiVersion: rbac.authorization.k8s.io/v1
> kind: Role
> metadata:
>   name: engine-central
>   namespace: central
> rules:
>   - apiGroups: [""]
>     resources: ["pods"]
>     verbs: ["create", "get", "list", "watch", "delete"]
>   - apiGroups: [""]
>     resources: ["pods/attach"]
>     verbs: ["create", "get"]
>   - apiGroups: [""]
>     resources: ["pods/status", "pods/log"]
>     verbs: ["get"]
>   - apiGroups: [""]
>     resources: ["events"]
>     verbs: ["list"]
> ---
> apiVersion: rbac.authorization.k8s.io/v1
> kind: RoleBinding
> metadata:
>   name: engine-central
>   namespace: central
> subjects:
>   - kind: ServiceAccount
>     name: engine-executor
>     namespace: ai-2501
> roleRef:
>   apiGroup: rbac.authorization.k8s.io
>   kind: Role
>   name: engine-central
> ---
> apiVersion: networking.k8s.io/v1
> kind: NetworkPolicy
> metadata:
>   name: central-isolation
>   namespace: central
> spec:
>   podSelector: {}
>   policyTypes: [Ingress, Egress]
>   egress:
>     - to:
>         - namespaceSelector: {}
>       ports:
>         - { protocol: UDP, port: 53 }
>         - { protocol: TCP, port: 53 }
>         - { protocol: UDP, port: 5353 }
>         - { protocol: TCP, port: 5353 }
>     - to:
>         - ipBlock:
>             cidr: 0.0.0.0/0
>             except:
>               - 10.0.0.0/8
>               - 172.16.0.0/12
>               - 192.168.0.0/16
>               - 100.64.0.0/10
>               - 169.254.0.0/16
>               - 127.0.0.0/8
>         - ipBlock:
>             cidr: ::/0
>             except: ["fc00::/7", "fe80::/10", "::1/128"]
> ```
>
> **1. Apply isolation and permissions**
>
> Run `kubectl apply -f central.yaml`. Confirm your network plugin enforces the policy; other policies must not grant broader access to these pods.
>
> **2. Prepare registry access**
>
> Create the configured executor image pull secret in `central` too. It must allow both the release image and your Central images.
>
> **3. Select the namespace**
>
> Add the values below and reapply the chart. Keep the Central manifest's namespace unset to use this default.
>
> **4. Verify execution**
>
> Run the Central's **Test** in **Dry run** and confirm command success and container removal.
>
> ```yaml theme={null}
> engine:
>   extraEnv:
>     CENTRAL_LOCAL_K8S_NAMESPACE: central
> ```
>
> The service account also needs permission to read its own pod in the installation namespace. Check this especially if executors use a separate namespace. Allow only the internal destinations your Central tools require; the example permits cluster DNS and public destinations.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.