# Advanced Deployment Guide

{% hint style="info" %}
**Recommended** for **Scalable Activation** Across Organization&#x20;
{% endhint %}

{% hint style="danger" %}
**This guide is for production deployments only.** If you are looking to evaluate MOTAR locally, refer to the [MOTARQuickStart ](/welcome/getting-started/local-installation/motar-quickstart.md) / Docker deployment guide instead. The Docker configuration is **not suitable for production use** — deploying it in a live environment may result in broken service connectivity and degraded performance.
{% endhint %}

<details>

<summary><strong>Helm Chart Installation</strong></summary>

### Helm Chart for Kubernetes

{% hint style="danger" %}
This cloud or local installation method is considered an **advanced install option**.
{% endhint %}

You can **install** MOTAR in a high availability fashion and optionally configure MOTAR components for **advanced deployments** (production, multi-node Kubernetes cluster deployments).

You will access the MOTAR Helm installation package through Dynepic's **Helm Repository**.

This Helm chart installs MOTAR with all of its dependencies in a Kubernetes cluster. Our Helm chart also employs non-MOTAR supporting charts, such as:

* A custom Minio chart branched from the Minio public chart.
* The Bitnami PostgreSQL chart.
* The ingress-nginx Kubernetes chart.
* The NATS public chart.

#### Prerequisites

* \[ ] Kubernetes 1.23+
* \[ ] Helm 3.8.0+
* \[ ] PV provisioner support in the underlying infrastructure
* \[ ] ReadWriteMany volumes for deployment scaling

{% hint style="info" %}
**Don't have a Kubernetes cluster yet?** If you are deploying on a single server or EC2 instance and need a simple path to get MOTAR running, see [#single-node-setup-with-microk8s](#single-node-setup-with-microk8s "mention") (Development / Evaluation).&#x20;

**Note** that a single-node MicroK8s instance is **not highly available or horizontally scalable** and is not recommended for large or mission-critical deployments.&#x20;

For true production environments, consider a managed Kubernetes service such as AWS EKS, GKE, or AKS.
{% endhint %}

The MOTAR Helm chart supports deployments in Kubernetes clusters hosted in nearly any cloud provider, local server, and self-hosted cloud. As such, many of the values are left to be filled in by the installing individual.

The default **values.yaml** should provide information sufficient to help you prepare the installation for your circumstance. If you find anything confusing or not intuitive, please reach out via [Contact Us!](/support/contact-us.md)

Below we will provide some general **recommended values**, and then some recommended values based on deployment type. You will be providing the values to the deployment which overrides the default where the values are set.

{% hint style="info" %}
&#x20;If MOTAR is already installed, proceed to the [Upgrading MOTAR with Helm](https://claude.ai/chat/2c294665-e061-4ca1-ba20-0a71ad3672af#upgrading-motar-with-helm) section below.&#x20;
{% endhint %}

{% hint style="warning" %}
&#x20;If using the Dynepic-provided Helm chart and images, you will need to authenticate with the Dynepic registry to pull the Helm chart, and provide a Secret in the cluster which contains the `dockerconfigjson` to pull the images. See the **imagePullSecret** section below.&#x20;
{% endhint %}

***

#### Step 1 — Configuration

* **Create** a values file in a known directory (replace `your_directory` with your actual path):

```sh
touch /your_directory/your_value.yaml
```

* **Edit** the values file:

```sh
nano /your_directory/your_value.yaml
```

* **Add** the following values. Update any field containing `your` or `you` with your actual values.
* **Save** with `CTRL-X` (if using nano).

{% hint style="warning" %}
Note that `environment` is set to `development` in the samples below. For a live or customer-facing deployment, change this to `production`.
{% endhint %}

**Sample values.yaml — Single Node Using NodePort**

{% code overflow="wrap" %}

```yaml
global:
  motarImageRegistry: harbor.dynepic.net
  domainName: your_domain.com
  initialAdminEmail: you@email.com
  environment: production
  mailConfig:
  reportTargets:
    - you@email.com
  securityTargets:
    - you@email.com
  s3Config:
    source: minio
    url: api-minio.your_domain.com

motar:
  ingress:
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-cluster-issuer # If using certmanager clusterIssuer
    nginx.ingress.kubernetes.io/cors-allow-headers: DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,clientid
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
  tls:
    enabled: true
    tlsSecret: "webapps-cert" # Substitute with your tlsSecret
  serviceAccount:
    create: true
    name: motar
  minio:
    ingress:
      enabled: true
      className: nginx
      hostname: api-minio.your_domain.com
      annotations:
        apiVersion: networking.k8s.io/v1
        className: nginx
        cert-manager.io/cluster-issuer: letsencrypt-cluster-issuer # If using CertManager/ClusterIssuer
        nginx.ingress.kubernetes.io/proxy-body-size: "0"
      tls:
        enabled: true
        tlsSecret: "api-minio-cert" # Substitute with your tlsSecret
    consoleIngress:
      enabled: true
      className: "nginx"
      annotations:
        apiVersion: networking.k8s.io/v1
        className: nginx
        cert-manager.io/cluster-issuer: letsencrypt-cluster-issuer # If using CertManager/ClusterIssuer
        nginx.ingress.kubernetes.io/proxy-body-size: "0"
      hosts:
        - console-minio.your_domain.com
      tls:
        enabled: true
        tlsSecret: "console-minio-cert" # Substitute with your tlsSecret

storageClass:
  enabled: true
  provisioner: driver.longhorn.io
  parameters:
    fsType: ext4
    numberOfReplicas: "1"
    staleReplicaTimeout: "30"

ingress-nginx:
  enabled: true
  controller:
    service:
      type: "NodePort"
      nodePorts:
        http: 31080
        https: 31443

minio:
  auth:
    existingSecret: "motar-s3-auth"
    rootUserSecretKey: "rootUser"
    rootPasswordSecretKey: "rootPassword"

postgresql:
  sslDisabled: true
  auth:
    existingSecret: "motar-pg-auth"
    secretKeys:
      adminPasswordKey: "postgres-password"
      motarPasswordKey: "motar-password"
```

{% endcode %}

**Sample values.yaml — AWS EKS**

{% code title="aws\_values.yaml" overflow="wrap" %}

```yaml
global:
  motarImageRegistry: harbor.dynepic.net
  domainName: your_domain.com
  initialAdminEmail: you@email.com
  environment: production
  mailConfig:
  reportTargets:
    - you@email.com
  securityTargets:
    - you@email.com
  s3Config:
    source: minio
    url: api-minio.your_domain.com

motar:
  ingress:
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-cluster-issuer # If using certmanager clusterIssuer
    nginx.ingress.kubernetes.io/cors-allow-headers: DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,clientid
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
  tls:
    enabled: true
    tlsSecret: "webapps-cert" # Substitute with your tlsSecret
  serviceAccount:
    create: true
    name: motar
  minio:
    ingress:
      enabled: true
      className: nginx
      hostname: api-minio.your_domain.com
      annotations:
        apiVersion: networking.k8s.io/v1
        className: nginx
        cert-manager.io/cluster-issuer: letsencrypt-cluster-issuer # If using CertManager/ClusterIssuer
        nginx.ingress.kubernetes.io/proxy-body-size: "0"
      tls:
        enabled: true
        tlsSecret: "api-minio-cert" # Substitute with your tlsSecret
    consoleIngress:
      enabled: true
      className: "nginx"
      annotations:
        apiVersion: networking.k8s.io/v1
        className: nginx
        cert-manager.io/cluster-issuer: letsencrypt-cluster-issuer # If using CertManager/ClusterIssuer
        nginx.ingress.kubernetes.io/proxy-body-size: "0"
      hosts:
        - console-minio.your_domain.com
      tls:
        enabled: true
        tlsSecret: "console-minio-cert" # Substitute with your tlsSecret

storageClass:
  enabled: true
  provisioner: kubernetes.io/aws-ebs
  parameters:
    fsType: ext4
    type: gp3

ingress-nginx:
  enabled: true
  controller:
    config:
      use-proxy-protocol: false
    service:
      type: LoadBalancer
      external:
        enabled: false
      internal:
        externalTrafficPolicy: Local
        enabled: true
        annotations:
          service.beta.kubernetes.io/aws-load-balancer-internal: false
          service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp
          service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: true
          service.beta.kubernetes.io/aws-load-balancer-type: nlb-ip

minio:
  auth:
    existingSecret: "motar-s3-auth"
    rootUserSecretKey: "rootUser"
    rootPasswordSecretKey: "rootPassword"

postgresql:
  sslDisabled: true
  auth:
    existingSecret: "motar-pg-auth"
    secretKeys:
      adminPasswordKey: "postgres-password"
      motarPasswordKey: "motar-password"
```

{% endcode %}

***

#### Step 2 — Prepare the Namespace

* Create the namespace:

```sh
kubectl create ns motar
```

* **Apply** the secrets for S3 storage and PostgreSQL.

{% hint style="warning" %}
**S3 connectivity is required for core MOTAR functionality.** Ensure your global.s3Config.url in values.yaml is reachable from within the cluster with tls to ensure file scanning and external connections function properly. Misconfigured or missing S3 secrets are the most common cause of broken deployments.
{% endhint %}

Both internal Minio and external S3 configurations require secrets for `rootUser` and `rootPassword`. Apply them from a YAML file:

```sh
kubectl apply -f mysecrets.yaml -n motar
```

Example YAML:

```yaml
apiVersion: v1
data:
  rootPassword: base64enc_value
  rootUser: base64enc_value
kind: Secret
metadata:
  labels:
    app.kubernetes.io/part-of: motar
  name: motar-s3-auth
type: Opaque
```

Or apply directly using literals:

```sh
kubectl -n motar create secret generic motar-pg-auth \
  --from-literal=motar-password=supersecret \
  --from-literal=postgres-password=topsecret

kubectl -n motar create secret generic motar-s3-auth \
  --from-literal=rootUser=minioUser \
  --from-literal=rootPassword=topsecretPassword
```

{% hint style="info" %}
&#x20;If you would like to apply a TLS certificate for use on your ingress and are not using CertManager, this is the point to do so.
{% endhint %}

***

#### Step 3 — Deploy the Chart

<pre class="language-sh" data-overflow="wrap"><code class="lang-sh">helm install motar --version <a data-footnote-ref href="#user-content-fn-1">3.</a>2.6 oci://harbor.dynepic.net/helmrelease/motar -f your_values.yaml -n motar
</code></pre>

***

#### Step 4 — Access the Site

If you have correctly configured DNS routing to your hosting device, MOTAR should now be accessible at `https://admin.your_domain.com`.

You have completed Helm setup and may continue to [Step 3 — Access Your MOTAR Instance](https://docs.motar.com/motar-tutorials#id-3-access-your-motar-instance).

</details>

<details>

<summary><strong>NodePort Configuration</strong></summary>

{% hint style="warning" %}
If using a NodePort configuration for your ingress controller, you will need to ensure traffic is routed to the correct port within your deployed environment. Below is a sample setup using an nginx reverse proxy.
{% endhint %}

* **Install** the required services:

```bash
sudo apt update
sudo apt install nginx
sudo apt install libnginx-mod-stream
sudo vim /etc/nginx/nginx.conf
```

* **Include** the proxy passthrough by appending the following outside the `http` block:

```nginx
http {
  # default configuration
}
include /etc/nginx/passthrough.conf;
```

* **Create** `/etc/nginx/passthrough.conf` with ports matching the NodePort values in your values file:

```nginx
stream {
  server {
    listen 80;
    proxy_pass 127.0.0.1:31080;
  }
  server {
    listen 443;
    proxy_pass 127.0.0.1:31443;
  }
}
```

* **Forward** HTTP to HTTPS with a redirect rule. Create a site config:

```bash
sudo vim /etc/nginx/sites-available/your.domain
```

```nginx
server {
  if ($host ~ ^[^.]+\.your\.domain$) {
    return 301 https://$host$request_uri;
  }
  listen 80;
}
```

* Remove the default nginx site and reload:

```bash
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
```

</details>

<details>

<summary><strong>Upgrading MOTAR with Helm</strong></summary>

To upgrade an existing MOTAR installation to a new chart version:

```sh
helm upgrade motar --version <new_version> oci://harbor.dynepic.net/helmrelease/motar -f your_values.yaml -n motar
```

Replace `<new_version>` with the target chart version. Your existing values file can be reused unless otherwise noted in the release notes.

</details>

<details>

<summary><strong>Troubleshooting FAQ</strong></summary>

**Services are running but the platform is not loading or media is missing.**

This is most commonly caused by S3 connectivity not being established. Verify:

1. Your `global.s3Config.url` in values.yaml is correct and reachable from within the cluster.
2. The `motar-s3-auth` secret was applied to the `motar` namespace.
3. If using internal Minio, confirm the Minio ingress is healthy: `kubectl get ingress -n motar`

</details>

***

### Optional / Alternative Deployments

<details>

<summary><strong>MOTAR AI Engine with Zero Trust Orchestration Layer (beta) Installation</strong></summary>

> **Note:** This is an advanced configuration.

Our MOTAR system includes a dedicated query server that requires an LLM. The query server interfaces with the LLM using OLLAMA API endpoints. We provide an OCI image containing all necessary components for the query server. To leverage your NVIDIA GPU, ensure the `nvidia-container-toolkit` is installed.&#x20;

If you prefer to manage dependencies yourself, the query server also requires a Redis database and the following Docker environment variables:

```env
LLM_API_HOST:  ${LLM_API_HOST}
PG_USER: ${POSTGRES_USER}
PG_PASS: ${POSTGRES_PASSWORD}
PG_DBNAME: ${POSTGRES_DB}
PG_HOST: ${POSTGRES_HOST}
PG_PORT: ${POSTGRES_PORT}
MINIO_ENDPOINT: ${MINIO_ENDPOINT}
MINIO_ACCESS_KEY: ${MINIO_ROOT_USER}
MINIO_SECRET_KEY: ${MINIO_ROOT_PASSWORD}
REDIS_HOST: ${REDIS_HOST}
REDIS_PORT: ${REDIS_PORT}
REDIS_DB: ${REDIS_DB}
```

* `PG_*` variables: Access to MOTAR's PostgreSQL database.
* `MINIO_*` variables: Credentials for your MOTAR MinIO instance.

***

**Example: Running OLLAMA and Redis Containers**

**OLLAMA:**

```yaml
ollama:
    image: ollama/ollama:latest
    container_name: ollama
    restart: unless-stopped
    runtime: nvidia
    ports:
        - "11434:11434"
    volumes:
        - ollama_models:/models
    environment:
        LOG_LEVEL: debug
        OLLAMA_MODELS: /models
        OLLAMA_DEBUG: 1
    entrypoint: ["/bin/bash"]
    command: ["-c", "dpkg --configure -a && apt update && apt install curl -y && sleep 5 && ollama serve & sleep 15 && ollama pull llama3 && pkill ollama && ollama serve"]
    healthcheck:
        test: ["CMD-SHELL", "curl -sf http://localhost:11434/api/tags | grep llama3"]
        interval: 10s
        timeout: 5s
        retries: 60
        start_period: 15s
```

**Redis:**

```yaml
redis:
    image: redis:7-alpine
    container_name: redis
    restart: unless-stopped
    volumes:
        - redis_data:/data
```

***

**MOTAR Configuration (External Query Server)**

Ensure MOTAR can access your query server. Example values file (AI section only):

```yaml
motar:
    ai:
        enabled: false  # Only deploys AI dependencies via Helm
        queryServer:
            external:
                enabled: true
                url: http://IP.OF.QUERY.SERVER
                port: 8000
```

***

**MOTAR Configuration (In-Cluster Deployment)**

To run everything inside your cluster, use a node configured for NVIDIA GPU passthrough. Apply a taint (e.g., `ai`) to schedule the query server and OLLAMA. Example values:

```yaml
ai:
    enabled: true
    llmServer:
        NodeSelector: node-role.kubernetes.io/ai: "true"
        runtimeClassName: "nvidia"
        tolerations:
            - key: "ai"
              operator: "Equal"
              value: true
              effect: "NoSchedule"
        storageSize: 10Gi
        models:
            - llama3:latest
        storageClassName: ""
        resourcesPreset: "gpuMedium"
        resources: {}
    queryServer:
        NodeSelector: node-role.kubernetes.io/ai: "true"
        tolerations:
            - key: "ai"
              operator: "Equal"
              value: true
              effect: "NoSchedule"
        runtimeClassName: "nvidia"
```

***

> Adjust values and configuration as needed for your environment and deployment strategy.

</details>

<details>

<summary><strong>MOTAR Discovery: Connecting an XR Headset</strong></summary>

When testing MOTAR locally using a Discovery license, it is possible to connect wireless XR headsets to your instance. However, this configuration is advanced and highly dependent on your current networking hardware, networking knowledge, and configuration permissions.

{% hint style="warning" %}

#### Proceed with Caution

While none of the following configurations will "break" your system and can be reversed, they may cause a loss of functionality for some users.

**We don't recommend** this configuration if you are **not** comfortable with altering both your network and the headset configuration.
{% endhint %}

Your network and hardware should meet the following criteria:

* An accessible and configurable router
* Developer permissions on the XR headset
* Admin permissions on the hosting computer

Connecting a wireless XR device to your local MOTAR test install may require the following changes based on your network and headset hardware:

* **Router Based Configuration** — requires a configurable network router
  * Customized record(s) added to the network router
* **Device Based Configuration**
  * Hosting and configuring files directly on an XR headset
  * Update local host files and/or updates to primary DNS
  * Local Domain Name Service changes

#### Meta Quest 3 Configuration

*Coming soon.* For questions in the meantime, please [contact us](https://docs.motar.com/support/contact-us).

</details>

***

<details>

<summary><strong>Single-Node Setup with MicroK8s</strong></summary>

Single-Node Setup with MicroK8s (Development / Evaluation)

{% hint style="warning" %}
**This setup is not suitable for production.** A single-node MicroK8s instance has no high availability, no horizontal scaling, and no redundancy. It is appropriate for development, evaluation, or getting MOTAR running correctly while you plan your production infrastructure. For mission-critical or large-scale deployments, use a managed Kubernetes service such as **AWS EKS**, **GKE**, or **AKS**.
{% endhint %}

{% hint style="info" %}
This page is for teams that **do not already have a Kubernetes cluster** running. If you have an existing cluster (e.g. AWS EKS, GKE, or self-managed), skip this page and proceed directly to the Advanced Deployment Guide.
{% endhint %}

MOTAR's Helm-based deployment requires a running Kubernetes cluster. If you are deploying on a single server or EC2 instance and do not have a cluster set up, **MicroK8s** provides a simple path to get a correct Kubernetes environment running quickly. The same MOTAR Helm chart and values.yaml used here will work against any managed Kubernetes cluster when you are ready to scale.

> **Note:** MOTAR support covers the Helm chart deployment and platform configuration. Setting up and administering your Kubernetes environment is outside the scope of MOTAR support. This page provides general guidance to help you get started.

***

### Prerequisites

* An EC2 instance or server running **Ubuntu 24.04 LTS** (recommended)
* SSH access to the instance
* `sudo` privileges
* Minimum recommended specs: 4 vCPU, 8GB RAM, 50GB storage (adjust based on your workload)

***

### Step 1 — Install MicroK8s

MicroK8s is distributed via [snap](https://snapcraft.io/microk8s) and installs in a single command.

```bash
sudo snap install microk8s --classic
```

Add your user to the `microk8s` group to avoid needing `sudo` for every command:

```bash
sudo usermod -aG microk8s $USER
newgrp microk8s
```

Verify the cluster is ready:

```bash
microk8s status --wait-ready
```

***

### Step 2 — Enable Required Add-ons

MicroK8s uses add-ons for core functionality. Enable the following:

```bash
microk8s enable dns storage ingress
```

| Add-on    | Purpose                                                  |
| --------- | -------------------------------------------------------- |
| `dns`     | Internal cluster DNS resolution                          |
| `storage` | Persistent volume provisioning (required for MOTAR data) |
| `ingress` | HTTP/HTTPS ingress controller                            |

***

### Step 3 — Configure kubectl

MicroK8s ships with its own `kubectl`. You can either use it directly with the `microk8s kubectl` prefix, or alias it for convenience:

```bash
alias kubectl='microk8s kubectl'
```

To make the alias permanent, add it to your `~/.bashrc` or `~/.bash_profile`:

```bash
echo "alias kubectl='microk8s kubectl'" >> ~/.bashrc
source ~/.bashrc
```

Verify cluster access:

```bash
kubectl get nodes
```

You should see your node listed with a `Ready` status.

***

### Step 4 — Install Helm

MOTAR is deployed via Helm. Install it with:

```bash
sudo snap install helm --classic
```

Verify the installation:

```bash
helm version
```

***

### Next Step

Once MicroK8s is running and Helm is installed, you are ready to deploy MOTAR.

***

For full MicroK8s documentation, visit [canonical.com/microk8s](https://canonical.com/microk8s).

</details>

> #### Following the Installation and Tutorial Guide?
>
> If you are following along with the tutorial, you should be ready to access and configure your MOTAR Instance. Click the link below to return:
>
> [https://docs.motar.com/motar-tutorials#id-3-access-your-motar-instance](https://docs.motar.com/motar-tutorials#id-3-access-your-motar-instance "mention")

[^1]: Version number may be different. Check for the lastest version.


---

# Agent Instructions: 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:

```
GET https://docs.motar.com/welcome/getting-started/production-installation/advanced-deployment-guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
