> For the complete documentation index, see [llms.txt](https://docs.cybus.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.cybus.io/2-4-1/documentation/installation-and-upgrades/installing-connectware/offline-installation-kubernetes.md).

# Offline Installation (Kubernetes)

This guide walks you through installing Connectware on Kubernetes in environments without Internet access. When your Kubernetes cluster cannot reach external registries, you must adapt the standard installation procedure:

1. **Images:** Container images cannot be pulled directly from `registry.cybus.io`.
2. **Helm Chart:** The chart cannot be downloaded dynamically from the Cybus Helm repository.
3. **Licensing:** The Connectware license key cannot be validated online. You must use an offline license file provided by Cybus.

The examples use a local single-node Kubernetes cluster with an internal image and Helm chart registry. While this is not a likely production environment, the concept translates to large Kubernetes clusters, and mimics some infrastructure you may already have, like an OCI registry.

{% hint style="info" %}
In production environments, isolation setups vary widely. You might be working with a completely air-gapped system requiring physical transfer mediums such as a USB drive, or a corporate network where you can use a bastion host or sync images directly into an internal registry.

This guide demonstrates the entire procedure on a local cluster with a registry running on the same host. Adapt the registry address and transfer method to match your environment.
{% endhint %}

## Prerequisites

* A local system with Internet access
* Docker installed on your host system
* `kind`, `helm`, `skopeo`, and `kubectl` installed on your host system
* Connectware license key (for authenticating with the Cybus registry)
* Access to the [Cybus Portal](https://portal.cybus.io/) to download license files

{% stepper %}
{% step %}

## Download License Files

Download the license key and offline license file from the Cybus Portal. You need the license key to authenticate with the Cybus registry when downloading container images, and the offline license file to validate Connectware in environments without Internet access.

1. Go to the [Cybus Portal](https://portal.cybus.io/).
2. Sign in with your credentials.
3. Navigate to **Licenses** to view your available Connectware licenses.
4. Select your license and download both the **license key file** (`.key`) and **license file** (`.lic`).
5. Save both files in a secure location on your system.

For detailed instructions on obtaining license files, see [Acquiring License Keys](/2-4-1/documentation/installation-and-upgrades/licensing.md#acquiring-license-keys).
{% endstep %}

{% step %}

## Setting Up the Local Registry

Set up a local Docker registry as a standalone container and configure the `kind` cluster to trust and communicate with it. Skip this section if you already have a Kubernetes cluster and OCI registry.

1. Create the registry container:

{% code lineNumbers="true" %}

```bash
docker run -d --restart=always -p 5001:5000 --name kind-registry registry:2
```

{% endcode %}

2. Create a file named `kind-config.yaml` to configure `containerd` to use the standalone registry:

{% code title="kind-config.yaml" lineNumbers="true" %}

```yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
  - |-
    [plugins."io.containerd.grpc.v1.cri".registry]
      config_path = "/etc/containerd/certs.d"
nodes:
  - role: control-plane
    kubeadmConfigPatches:
      - |
        kind: InitConfiguration
        nodeRegistration:
          kubeletExtraArgs:
            node-labels: "ingress-ready=true"
```

{% endcode %}

3. Apply the cluster configuration:

{% code lineNumbers="true" %}

```bash
kind create cluster --config kind-config.yaml
```

{% endcode %}

4. Connect the `kind-registry` to the `kind` network and configure each node to route `localhost:5001` to it:

{% code lineNumbers="true" %}

```bash
# Connect the registry to the cluster network
docker network connect kind kind-registry

# Configure each node to route localhost:5001 to the registry
REGISTRY_DIR="/etc/containerd/certs.d/localhost:5001"
for node in $(kind get nodes); do
  docker exec "${node}" mkdir -p "${REGISTRY_DIR}"
  cat <<EOF | docker exec -i "${node}" cp /dev/stdin "${REGISTRY_DIR}/hosts.toml"
[host."http://kind-registry:5000"]
EOF
done
```

{% endcode %}

{% hint style="info" %}
The registry is now accessible from your host machine at `localhost:5001`. The Kubernetes cluster routes pod-level image pulls for `localhost:5001` directly to the `kind-registry` container.

These steps follow the official [Kind Local Registry documentation](https://kind.sigs.k8s.io/docs/user/local-registry/). If you already have a Kubernetes environment and registry configured, skip this section and substitute `localhost:5001` with your registry's URL in the steps below.
{% endhint %}
{% endstep %}

{% step %}

## Preparing the Artifacts

### Downloading the Helm Chart

Download the `connectware` Helm chart from the Cybus OCI repository as a `.tgz` package and push it to your local registry:

{% code lineNumbers="true" %}

```bash
# Download the chart
helm pull oci://repo.cybus.io/charts/connectware --version ${CHART_VERSION}

# Push to your local registry
# --plain-http is required because the local registry has no TLS certificate configured
helm push connectware-${CHART_VERSION}.tgz oci://localhost:5001/helm --plain-http
```

{% endcode %}

Replace `${CHART_VERSION}` with the `connectware` Helm chart version you want to install. See the [Compatibility Matrix](/2-4-1/cybus-helm-charts/compatibility-matrix.md#connectware-helm-chart) for the chart version that matches your target Connectware version.

Repeat this step for the `connectware-agent` Helm chart, if required.

### Downloading Container Images

Generate a list of images required by the connectware Helm chart, then copy them directly to your local registry using `skopeo`:

{% code lineNumbers="true" %}

```bash
# Generate the image list from the chart
helm template connectware ./connectware-${CHART_VERSION}.tgz | awk '/image:/ {print $2}' | sed 's/"//g' | sort | uniq > images.txt

# Copy images to the local registry
while read image; do
  image_name=$(basename $image)
  skopeo copy --src-creds "license:${LICENSE_KEY}" \
    docker://$image docker://localhost:5001/$image_name \
    --dest-tls-verify=false
done < images.txt
```

{% endcode %}

Replace `${LICENSE_KEY}` with the Connectware license key [downloaded in step 1](#download-license-files).

{% hint style="info" %}
`--dest-tls-verify=false` is used because the local registry in this example has no TLS certificate configured. In production environments with secure registries, omit this flag.
{% endhint %}
{% endstep %}

{% step %}

## Configuring the Image Registry

The `connectware` Helm chart needs to know which registry to pull its component images from. Set `global.image.registry` to your internal registry address.

If your internal registry requires authentication, first create a pull secret in the namespace where Connectware will be deployed. If it does not require authentication, skip this step.

{% code lineNumbers="true" %}

```bash
kubectl create secret docker-registry my-local-registry-secret \
  --docker-server=my.internal.registry.com \
  --docker-username=${REGISTRY_USERNAME} \
  --docker-password=${REGISTRY_PASSWORD} \
  --namespace=${NAMESPACE}
```

{% endcode %}

Replace `${REGISTRY_USERNAME}` and `${REGISTRY_PASSWORD}` with the credentials for your internal registry, and `${NAMESPACE}` with the Kubernetes namespace where you plan to install Connectware.

Then reference the registry in your `values.yaml`:

{% code title="values.yaml" lineNumbers="true" %}

```yaml
global:
  image:
    registry: 'localhost:5001'
    # Only required if your registry uses authentication
    # pullSecrets:
    #   - name: my-local-registry-secret
```

{% endcode %}
{% endstep %}

{% step %}

## Providing the Offline License Details

Connectware requires a license key in all installations. In an online environment, this key is validated against the Cybus Portal. For offline installations, you must also provide a pre-validated offline license file [downloaded in step 1](#download-license-files).

You can provide license details in the following ways.

### Using Direct Helm Values

Provide both your Connectware license key and the license file payload directly in your `values.yaml`:

{% code title="values.yaml" lineNumbers="true" %}

```yaml
global:
  licenseKey: ${LICENSE_KEY}
  licenseFile: ${LICENSE_FILE}
```

{% endcode %}

Replace `${LICENSE_KEY}` with your Connectware license key and `${LICENSE_FILE}` with the contents of the offline license file (`.lic`) downloaded in step 1.

### Using Existing Kubernetes Secrets

Store the license key and license file in Kubernetes Secrets and reference them in the Helm values. This approach is more secure than embedding credentials directly in values files and prevents secrets from being stored in version control.

{% hint style="info" %}
**Recommended for production.** Using Kubernetes Secrets separates sensitive credentials from configuration and follows Kubernetes security best practices.
{% endhint %}

1. Create the Secrets:

{% code lineNumbers="true" %}

```bash
# Store the license key
kubectl create secret generic connectware-offline-license-key \
  --from-literal=licenseKey=${LICENSE_KEY} \
  --namespace=${NAMESPACE}

# Store the offline license file saved as connectware-license.txt
# The explicit licenseFile= ensures the secret key is correctly named
kubectl create secret generic connectware-offline-license-file \
  --from-file=licenseFile=connectware-license.txt \
  --namespace=${NAMESPACE}
```

{% endcode %}

Replace `${LICENSE_KEY}` with your Connectware license key.

2. Reference the Secrets in your `values.yaml`:

{% code title="values.yaml" lineNumbers="true" %}

```yaml
global:
  existingLicenseKeySecret: connectware-offline-license-key
  existingLicenseFileSecret: connectware-offline-license-file
```

{% endcode %}

{% hint style="warning" %}
Define either `licenseFile` or `existingLicenseFileSecret` — not both. The same applies to `licenseKey` and `existingLicenseKeySecret`. If both are set, the Secret reference takes priority.
{% endhint %}
{% endstep %}

{% step %}

## Installing Connectware

With your registry populated and license configured, install Connectware using the Helm chart and images from your local registry. Your final `values.yaml` combines the registry configuration and license details:

{% code title="values.yaml" lineNumbers="true" %}

```yaml
global:
  # Using the offline license via Secret references
  existingLicenseKeySecret: connectware-offline-license-key
  existingLicenseFileSecret: connectware-offline-license-file

  image:
    # Internal registry configured on the cluster nodes
    registry: 'localhost:5001'
    # pullSecrets are not needed if the registry does not require authentication
    # pullSecrets:
    #   - name: my-local-registry-secret
```

{% endcode %}

Install the Helm chart from your local OCI registry:

{% code lineNumbers="true" %}

```bash
helm upgrade --install connectware oci://localhost:5001/helm/connectware \
  --version ${CHART_VERSION} \
  --namespace ${NAMESPACE} \
  --create-namespace \
  -f values.yaml
```

{% endcode %}

Connectware initializes and pulls all images from your local registry.
{% endstep %}
{% endstepper %}

## Verifying the Installation

Verify that all Connectware pods are running and pulling images from your local registry:

{% code lineNumbers="true" %}

```bash
kubectl get pods -n ${NAMESPACE}
```

{% endcode %}

All pods should show a `Running` status. To verify that images are being pulled from your local registry, check the image URLs for all Connectware pods:

{% code lineNumbers="true" %}

```bash
kubectl get pod -n ${NAMESPACE} -l app.kubernetes.io/part-of=connectware -o jsonpath='{range .items[*]}{@.metadata.name}{": "}{@.spec.containers[0].image}{"\n"}{end}'
```

{% endcode %}

The image URLs should reference your local registry (`localhost:5001` in this example).

## Accessing Connectware

After installation, access Connectware through your web browser:

1. Determine the ingress hostname configured during installation.
2. Navigate to `https://${HOSTNAME}` in your web browser, replacing `${HOSTNAME}` with the ingress hostname.
3. Log in using the default credentials:
   * **Username:** `admin`
   * **Password:** `admin`

{% hint style="warning" %}
For security reasons, change the default admin password immediately after your first login. See [Default Admin User](/2-4-1/documentation/user-management/users/default-admin-user.md).
{% endhint %}

## License Validation in Offline Environments

When running Connectware in an offline environment, you may see a warning message in the system status indicating that the system cannot connect to the license validation server at `https://graphql-server.cybus.io/graphql`.

{% hint style="info" %}
This warning can be safely ignored in offline installations. The license validation has already been performed locally using the offline license file you provided. Your license will show as valid.
{% endhint %}

The license information is stored in the Kubernetes Secret and does not require Internet connectivity to function. However, you will not be able to refresh the license automatically from the Admin UI, as this feature requires Internet access.


---

# 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://docs.cybus.io/2-4-1/documentation/installation-and-upgrades/installing-connectware/offline-installation-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.
