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

# Advanced Setup

This guide provides an in-depth overview of considerations when installing
Thoras. For a quicker, higher-level guide to install and try out Thoras, check
out the [QuickStart Guide](../getting-started/quickstart).

## Installing the Thoras license

Thoras will provide you a license key which should be made available to your
installation.

You have two options for managing this value:

1. Pass the value into the Helm release via
   `$.Values.imageCredentials.password`, or
2. Reference an existing secret

Let's review both options, choose whichever works best for you!

### Option 1: Pass license in as Helm chart value

Pass the content of your license key file to
`$.Values.imageCredentials.password` in your helm release

### Option 2: Reference existing secret

If you'd prefer to reference a pre-existing secret object instead of passing
values into your helm release, this option is for you!

Ensure a secret of type `docker-registry` exists in the `thoras` namespace and
that it's structured correctly:

1. An example of how to create the secret manually:
   ```sh theme={null}
   kubectl create secret docker-registry thoras-license \
       --docker-server=us-east4-docker.pkg.dev/thoras-registry/platform \
       --docker-username=_json_key_base64 \
       --docker-password="{{ YOUR_LICENSE_KEY_CONTENT_HERE }}" \
       -n thoras
   ```
2. In the helm values file, reference this secret by setting
   `.Values.imageCredentials.secretRef: "thoras-license"`.

### Confirm license installed successfully

You can confirm the license has been installed successfully if the pods in the
`thoras` namespace come up into `Running` status after one or two minutes. Pod
error statuses such as `ImagePullBackOff` might indicate an issue.

## Configure Worker Capacity

Thoras is very lightweight but you'll still want to allocate enough forecast
workers to accommodate the number of workloads you're managing:

1. The number of workers is managed by the
   `$.Values.thorasForecast.worker.replicas` helm value
2. The default number of workers is `1`
3. For maximum performance, Thoras recommends a `1:100` ratio of
   `worker:AIScaleTarget`
4. For example, if you have 300 `AIScaleTargets`, you'll want to set
   `$.Values.thorasForecast.worker.replicas` to `3`

## Persistent Volumes

To enable production-grade predictions, Thoras requires a Kubernetes
[persistent volume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/)
(PV) for persisting historical usage metrics. It's critical to ensure disk
persistence is configured for any clusters that host workloads that people or
things rely on. Read on for an overview!

### Understanding Storage Requirements

You'll want to ensure Thoras has the disk space it needs now and in the future.
This may or may not require configuration based on your backend storage
provider.

### Understanding Usage

The storage requirements for a managed workload is `1GB`. So for a cluster with
twenty managed workloads, Thoras requires `20GB` of disk space.

### Understanding Kubernetes Storage Backend

In general there are two patterns for Kubernetes storage backends with regard to
disk space configuration:

#### Growable Storage Backends

These are PV storage backends that don't enforce a fixed size, they dynamically
grow as-needed and require no Thoras configuration to specify volume size.
Examples include:

* `AWS Elastic File System (EFS)`
* `Google Cloud Filestore (enterprise tier)`
* `Azure Files`
* `CephFS`

**Action:** If you use one of these backends, you don't need to allocate disk
space in the Thoras configuration, the disk will automatically grow to meet
your needs

#### Fixed-Size Storage Backends

These are PV storage backends that require explicit provisioning of a fixed
capacity and enforce the size specified in the `PersistentVolumeClaim`. These
backends require you to specify the desired size of the disk, which you should
configure as a Helm chart value (read on for details on how to do that).
Examples include:

* `AWS Elastic Block Store (EBS)`
* `Google Persistent Disk (GCP PD)`
* `Azure Disk Storage`
* `VMware vSphere Volumes`

**Action:** If you use a fixed-size storage backend, you'll need to specify the
volume size as a Helm value:

1. Determine how much total size will be needed using
   [guidance above](#1-understanding-usage)
2. Configure the storage size by setting the following helm value:

```yaml theme={null}
metricsCollector:
  persistence:
    # Or whatever size needed based on your math from step #1
    pvcStorageRequestSize: "100Gi"
```

## Managing Persistent Volumes

You may have an established workflow for provisioning Kubernetes Persistent
Volumes (PV). That's great! Here's how to get up and running depending on your
preferred approach:

### Referencing an existing `StorageClass` provisioner

If you have a Kubernetes `StorageClass` that you'd like to use to dynamically
provision your PV (such as an
[EFS StorageClass](https://github.com/kubernetes-sigs/aws-efs-csi-driver/blob/master/examples/kubernetes/dynamic_provisioning/specs/storageclass.yaml)).
In that case, simply reference the storage class name in your Thoras helm chart
`values.yaml`:

```yaml theme={null}
metricsCollector:
  persistence:
    enabled: true
    storageClassName: "{{ YOUR_STORAGE_CLASS_NAME }}"
```

### Referencing an existing PV

If you have a workflow for provisioning a PV and would like to simply reference
the PV directly by name, add the following config block to your Helm
`values.yaml`:

```yaml theme={null}
metricsCollector:
  persistence:
    enabled: true
    volumeName: "{{ YOUR_VOLUME_NAME_HERE }}"
```

### Questions?

For help, email [support@thoras.ai](mailto:support@thoras.ai).

## Configuring Webhook Namespace Exclusions

By default, Thoras webhooks exclude the `kube-system` and `kube-node-lease`
namespaces. You can customize which namespaces are excluded, or disable
exclusions entirely, using the `thorasOperator.webhooks.namespaceSelector`
Helm value.

### Default Behavior

Out of the box, Thoras excludes system namespaces from webhook enforcement:

```yaml theme={null}
thorasOperator:
  webhooks:
    namespaceSelector:
      matchExpressions:
        - key: kubernetes.io/metadata.name
          operator: NotIn
          values:
            - kube-system
            - kube-node-lease
```

### Customizing Excluded Namespaces

To exclude additional namespaces (e.g. a monitoring namespace), extend the
`values` list:

```yaml theme={null}
thorasOperator:
  webhooks:
    namespaceSelector:
      matchExpressions:
        - key: kubernetes.io/metadata.name
          operator: NotIn
          values:
            - kube-system
            - kube-node-lease
            - monitoring
```

### Disabling Namespace Exclusions

If you use an admission enforcer such as OPA Gatekeeper that manages namespace
selection independently, you can disable the default exclusions by setting the
selector to an empty object:

```yaml theme={null}
thorasOperator:
  webhooks:
    namespaceSelector: {}
```

## Using a Private Container Registry

If you need to pull Thoras container images from your own private registry
instead of the public Thoras registry, you can override the default registry
location using the `$.Values.imageCredentials.registry` value.

### Prerequisites

1. Access to download Thoras container images (email
   [support@thoras.ai](mailto:support@thoras.ai) for assistance)
2. A private container registry accessible from your Kubernetes cluster
3. Container images pushed to your private registry

### Configuration

Set the private registry hostname value in your Helm `values.yaml`:

```yaml theme={null}
imageCredentials:
  registry: "your-private-registry.example.com/thoras"
```

### Copying Multi-Platform Images

Thoras container images are built for multiple platforms (`linux/amd64` and
`linux/arm64`) to support multiple CPU architectures. When copying images from
the Thoras registry to your private registry, it's a good idea to preserve the
multi-platform manifest to ensure compatibility across different CPU
architectures.

### Generate a list of images to copy

```sh theme={null}
$ helm repo add thoras https://thoras-ai.github.io/helm-charts
$ helm template testit thoras/thoras | awk '/image:/ {print $NF}' | sort -u
us-east4-docker.pkg.dev/thoras-registry/platform/nginx:1.29.4-alpine3.23-slim
us-east4-docker.pkg.dev/thoras-registry/platform/services:4.53.0
us-east4-docker.pkg.dev/thoras-registry/platform/thoras-dashboard-v2:4.53.0
us-east4-docker.pkg.dev/thoras-registry/platform/thoras-forecast:4.53.0
us-east4-docker.pkg.dev/thoras-registry/platform/timescaledb:2.21.3-pg16
```

#### Using Docker Buildx

The recommended approach is to use `docker buildx imagetools create`, which
preserves the multi-platform manifest:

```sh theme={null}
# Authenticate to both registries
docker login us-east4-docker.pkg.dev/thoras-registry/platform
docker login your-private-registry.example.com

# Copy the image with all platforms preserved
docker buildx imagetools create \
  --tag your-private-registry.example.com/thoras/services:4.53.0 \
  us-east4-docker.pkg.dev/thoras-registry/platform/services:4.53.0
```

#### Verifying Multi-Platform Support

After copying, verify that both platforms are available:

```sh theme={null}
docker buildx imagetools inspect your-private-registry.example.com/thoras/services:4.53.0
```

You should see both `linux/amd64` and `linux/arm64` in the output.

### Questions?

For help downloading container images or configuring your private registry,
email [support@thoras.ai](mailto:support@thoras.ai).
