DataCore Puls8 on Air-Gapped Environments

Explore this Page

Overview

This document describes how to install DataCore Puls8 on a Kubernetes cluster that has no direct internet access (an air-gapped or offline environment). The examples use Puls8 v4.5, substitute your target release wherever the version appears.

In an air-gapped environment, cluster nodes cannot pull images from public registries such as Docker Hub or registry.k8s.io. The installation therefore mirrors the required container images into a private container registry that the cluster can reach, and transfers the DataCore Puls8 Helm chart directly to the air-gapped environment. The chart is then installed against that mirrored registry using Helm value overrides.

This document walks through three parts.

Section Where It Runs What It Does
Downloading the Artifacts Internet-connected host Pull the Helm chart and extract the image list.
Mirroring Images to Your Private Registry Internet-connected host (and, if disconnected, a registry-connected host) Copy every image into your private registry.
Installing on the Air-Gapped Cluster Air-gapped cluster Deploy the chart against the private registry.

The chart version, the images.txt bundled inside that chart, and the image tags you mirror must all come from the same Puls8 release. Mixing versions leads to missing-image and digest-mismatch errors during installation.

Requirements

Ensure the following requirements are met before installing DataCore Puls8 in an air-gapped environment.

On the Internet-Connected Host (the Download Host)

  • Docker or Podman, with authenticated access (docker login) to your private registry.
  • Helm v3.8 or later (for OCI registry support) and kubectl.
  • Sufficient free disk space to pull, and if needed archive, all images.

On the Air-Gapped Cluster

  • A private OCI registry reachable from every node - for example Harbor, JFrog Artifactory, Amazon ECR, Quay, or a self-hosted registry:2.
  • Registry credentials, if the registry requires authentication.
  • Nodes prepared for the storage engines you intend to use (for example, HugePages and the nvme_tcp kernel module for Replicated PV Mayastor).

All Puls8 storages - Replicated PV Mayastor, Local PV Hostpath, Local PV LVM, and Local PV ZFS support amd64 (x86_64). On arm64, every storage is supported except Replicated PV Mayastor, which is currently amd64-only. If your cluster runs on arm64, follow the arm64 guidance in Mirroring Images to Your Private Registry and Installing on the Air-Gapped Cluster.

Conventions

This document uses two shell variables in its examples. Set them to match your environment before running the commands.

Copy
Set the Private Registry and Puls8 Version Shell Variables
export PRIVATE_REGISTRY="registry.internal.example.com:5000"   # your registry host (include port/project path if applicable)
export PULS8_VERSION="4.5.0"                                    # the Puls8 release you are installing

Wherever you see <PRIVATE_REGISTRY> in a configuration file, replace it with your registry host.

Downloading the Artifacts

Perform the below steps on the internet-connected download host.

Pull the Helm Chart

The Puls8 chart is published as an OCI artifact on Docker Hub. Pull it with Helm.

Copy
Pull the DataCore Puls8 Helm Chart
mkdir -p puls8-airgap && cd puls8-airgap

helm pull oci://docker.io/datacoresoftware/puls8 --version ${PULS8_VERSION}
# Produces: puls8-4.5.0.tgz

Extract the Image List

Each Puls8 chart bundles an images.txt file that lists every container image the chart can deploy. This list is the authoritative source for mirroring. Extract it from the chart archive.

Copy
Extract images.txt from the Chart Archive
tar -xzf puls8-${PULS8_VERSION}.tgz puls8/images.txt
cp puls8/images.txt images.txt

grep -cvE '^\s*(#|$)' images.txt   # prints the number of images to mirror

The images span four public registries: docker.io, ghcr.io, quay.io, and registry.k8s.io.

The bundled list includes ghcr.io/openebs/mayastor/dev/* entries. These are upstream development mirrors of the Replicated PV Mayastor images and are not pulled by a released install (the chart uses the docker.io/openebs/mayastor-* images instead). Mirroring them is harmless, to reduce the mirror size you may remove them with grep -v '^ghcr.io/' images.txt > images.txt.

Mirroring Images to Your Private Registry

Puls8 images originate from several public registries. When you later set global.imageRegistry, the chart replaces the source registry host but preserves the remainder of the image path. Each image must therefore be mirrored to your private registry with its repository path intact.

Source Image Mirrored Image
docker.io/openebs/etcd:3.6.4-debian-12-r0 <PRIVATE_REGISTRY>/openebs/etcd:3.6.4-debian-12-r0
quay.io/minio/minio:RELEASE.2024-12-18T13-15-44Z <PRIVATE_REGISTRY>/minio/minio:RELEASE.2024-12-18T13-15-44Z
registry.k8s.io/sig-storage/csi-attacher:v4.8.1 <PRIVATE_REGISTRY>/sig-storage/csi-attacher:v4.8.1

In short: remove the leading registry host and prepend <PRIVATE_REGISTRY>/.

Two helper scripts automate this, driven by the images.txt from Downloading the Artifacts. Their full source is provided in Save Images Script and Push Images Script.

Script Role
puls8-save-images.sh Pulls every image in images.txt, and optionally exports them to a single tar.gz.
puls8-push-images.sh Retags each image onto <PRIVATE_REGISTRY> (preserving the repository path and tag) and pushes it.

Both scripts accept -l|--image-list, support Podman via the CONTAINER_CLI=podman environment variable, and ignore blank and commented lines so you can edit images.txt freely. Make them executable once.

Copy
Make the Mirroring Scripts Executable
chmod +x puls8-save-images.sh puls8-push-images.sh

Select the workflow that matches your network topology:

  • Connected jump host - a single host can reach both the internet and the private registry. Mirror images directly.
  • Fully disconnected - save images to an archive, transfer it across the air gap on physical media, then load and push.

Option A: Connected Jump Host

No archive is required. Pull the images, then push them directly to the registry. Omit --images on both steps so the script works with the locally pulled images.

Copy
Pull and Push Images Directly from a Connected Jump Host
docker login ${PRIVATE_REGISTRY}

# Pull every image listed in images.txt
./puls8-save-images.sh --image-list images.txt

# Retag and push each image to the private registry
./puls8-push-images.sh --registry ${PRIVATE_REGISTRY} --image-list images.txt

Option B: Fully Disconnected

  1. On the download host, pull and export all images to a single archive.

    Copy
    Pull and Export All Images to an Archive
    ./puls8-save-images.sh \
      --image-list images.txt \
      --images puls8-images-${PULS8_VERSION}.tar.gz

    Copy the following files onto your transfer media:

    • puls8-images-4.5.0.tar.gz - the image bundle
    • puls8-4.5.0.tgz - the Helm chart
    • images.txt - the image list
    • puls8-push-images.sh - the push script
  2. Transfer the media across the air gap to a host that can reach the private registry.
  3. On the registry-connected host, load and push. The --images flag instructs the script to load the archive before pushing.

    Copy
    Load and Push Images from the Transferred Archive
    docker login ${PRIVATE_REGISTRY}

    ./puls8-push-images.sh \
      --registry ${PRIVATE_REGISTRY} \
      --image-list images.txt \
      --images puls8-images-${PULS8_VERSION}.tar.gz

    Also copy puls8-4.5.0.tgz to the host from which you will run helm install in Installing on the Air-Gapped Cluster.

The docker save and docker load commands carry only the architecture that was pulled. If the download host and the cluster nodes differ in architecture, pull for the target platform with ./puls8-save-images.sh --platform linux/arm64 ..., or use skopeo copy --all to preserve full multi-architecture manifests.

arm64 clusters - Replicated PV Mayastor is not supported. The Replicated PV Mayastor ships amd64 images only and has no arm64 manifests, so pulling its images with --platform linux/arm64 will fail. The Local PV Hostpath, Local PV LVM, and Local PV ZFS storages all support arm64.

To install on an arm64 cluster:

  1. Disable Replicated PV Mayastor at install time. See Installing on the Air-Gapped Cluster.
  2. Remove the Replicated PV Mayastor image entries from your copy of images.txt before mirroring, so they are not pulled for arm64. Keep the remaining entries for the other storages and the platform components.
  3. Mirror the edited images.txt using Option A or Option B above, adding --platform linux/arm64 to the save step.

Installing on the Air-Gapped Cluster

Run the below steps from a host with kubectl and helm access to the air-gapped cluster.

Create the Namespace

Copy
Create the Puls8 Namespace
kubectl create namespace puls8

Create an Image Pull Secret

If your private registry requires authentication, create a pull secret in the puls8 namespace.

Copy
Create a Docker Registry Secret for Puls8
kubectl create secret docker-registry puls8-regcred \
  --namespace puls8 \
  --docker-server="${PRIVATE_REGISTRY}" \
  --docker-username='<user>' \
  --docker-password='<password>'

If your registry is unauthenticated, skip this step and omit the imagePullSecrets entries from the values file below.

Create airgap-values.yaml

Setting global.imageRegistry redirects most images to your private registry. However, several third-party sub-chart images - Grafana Alloy, Loki, NATS, MinIO, Velero, kubectl, External Secrets, and the Alloy config-reloader - do not honor the global setting and require explicit per-sub-chart overrides.

The values file below covers all of these images. It has been verified with helm template so that every rendered image resolves to the private registry. Replace <PRIVATE_REGISTRY> with your registry host before applying.

Copy
airgap-values.yaml
global:
  imageRegistry: "<PRIVATE_REGISTRY>" # Used by ZFS, LVM, Rawfile, Mayastor components and etcd
  imagePullSecrets:
    - puls8-regcred # Used by ZFS, LVM, Rawfile, Mayastor components and etcd
  image:
    registry: "<PRIVATE_REGISTRY>" # Used by Loki and Alloy
    pullSecrets:
      - name: puls8-regcred # Used by Alloy

# --- Sub-chart images NOT covered by global.imageRegistry ---

openebs:
  loki:
    imagePullSecrets:
      - name: puls8-regcred
    sidecar:
      image:
        repository: "<PRIVATE_REGISTRY>/kiwigrid/k8s-sidecar"
    minio:
      imagePullSecrets:
        - name: puls8-regcred
      image:
        repository: "<PRIVATE_REGISTRY>/minio/minio"
      mcImage:
        repository: "<PRIVATE_REGISTRY>/minio/mc"
  mayastor:
    nats:
      imagePullSecrets:
        - name: puls8-regcred
      nats:
        image:
          registry: "<PRIVATE_REGISTRY>"      # nats
      reloader:
        image:
          registry: "<PRIVATE_REGISTRY>"      # natsio/nats-server-config-reloader
      exporter:
        image:
          registry: "<PRIVATE_REGISTRY>"      # natsio/prometheus-nats-exporter

# Only required if backup is enabled (backup.enabled: true, for Velero integration)
backup:
  velero:
    image:
      repository: "<PRIVATE_REGISTRY>/velero/velero"            # tag comes from chart default
      imagePullSecrets:
        - puls8-regcred
    kubectl:
      image:
        repository: "<PRIVATE_REGISTRY>/openebs/kubectl"        # tag comes from chart default

# Only required if External Secrets is enabled (external-secrets.enabled: true, for KMS)
external-secrets:
  imagePullSecrets:
    - name: puls8-regcred # omit this block if your registry is unauthenticated
  image:
    repository: "<PRIVATE_REGISTRY>/datacoresoftware/external-secrets"   # tag comes from chart default
  webhook:
    imagePullSecrets:
      - name: puls8-regcred # omit this block if your registry is unauthenticated
    image:
      repository: "<PRIVATE_REGISTRY>/datacoresoftware/external-secrets"
  certController:
    imagePullSecrets:
      - name: puls8-regcred # omit this block if your registry is unauthenticated
    image:
      repository: "<PRIVATE_REGISTRY>/datacoresoftware/external-secrets"

Monitoring is enabled by default (monitoring.enabled: true), so the Grafana, Alloy, Loki, NATS, config-reloader, and Loki-MinIO overrides above apply to a standard installation. Backup (backup.enabled: false) and External Secrets (external-secrets.enabled: false) are disabled by default, so the Velero, kubectl, and External Secrets overrides take effect only when you enable those stacks. Overrides for a disabled stack are simply ignored, so it is safe to keep the full file. To disable monitoring, set monitoring.enabled: false.

arm64 clusters - disable Replicated PV Mayastor. It has no arm64 images. On an arm64 cluster, add the following to airgap-values.yaml (or pass --set openebs.engines.replicated.mayastor.enabled=false at install time). The remaining storages - Local PV Hostpath, Local PV LVM, and Local PV ZFS - operate normally.

Copy
Disable Replicated PV Mayastor for arm64 Clusters
openebs:
  engines:
    replicated:
      mayastor:
        enabled: false

Install the Chart

Copy
Install DataCore Puls8 with the Air-Gapped Values File
helm install puls8 ./puls8-${PULS8_VERSION}.tgz \
  --namespace puls8 \
  --values airgap-values.yaml

To also enable the backup stack, append --set backup.enabled=true. Monitoring is enabled by default.

Verifying the Installation

Confirm that all pods start and that every image is served from your private registry.

Copy
Verify Pods, Image Sources, and StorageClasses
# All pods should reach Running/Completed with no ImagePullBackOff:
kubectl get pods -n puls8 -o wide

# Confirm images resolve to your private registry:
kubectl get pods -n puls8 \
  -o jsonpath='{range .items[*]}{range .spec.containers[*]}{.image}{"\n"}{end}{end}' \
  | sort -u

# Confirm imagePullSecrets is set on all pods:
kubectl get pods -n puls8 -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.imagePullSecrets}{"\n"}{end}'

# StorageClasses created by the enabled storage components:
kubectl get sc

If the second command lists any image from docker.io/, quay.io/, registry.k8s.io/, or ghcr.io/, an override was missed. Add the corresponding key to airgap-values.yaml and apply it with helm upgrade.

You can confirm that every image will resolve to your private registry without deploying anything by rendering the chart locally. The following command prints any image that is not redirected; an empty result means the configuration is complete.

Copy
Validate Registry Overrides Without Deploying
# Monitoring is on by default; --set backup.enabled=true also renders the
# backup stack so its overrides are validated as well.
helm template puls8 ./puls8-${PULS8_VERSION}.tgz \
  --values airgap-values.yaml \
  --set backup.enabled=true \
  | grep -oE 'image: *"?[^"]*' | sed 's/image: *//; s/"//g' | sort -u \
  | grep -vE "^${PRIVATE_REGISTRY}/"

If a pod is stuck in ImagePullBackOff, an image tag does not resolve, or a pod fails to start on a node of a different architecture, refer to Air-Gapped Installation Issues in the Troubleshooting document.

Benefits of Using DataCore Puls8 in Air-Gapped Environments

  • Regulatory and Security Compliance: Air-gapped installation lets organizations operating in tightly regulated, classified, or otherwise isolated environments deploy DataCore Puls8 without exposing the cluster to the public internet, satisfying strict network isolation and compliance requirements.
  • Supply-Chain Control: Mirroring every image into a private registry gives you an auditable, versioned record of exactly what is running in your cluster, reducing exposure to upstream registry outages or unexpected upstream image changes.
  • Consistent, Repeatable Deployments: Because the Helm chart, the mirrored images, and the storages all come from a single pinned release, air-gapped installs are reproducible across environments without relying on live internet access.

Learn More