Skip to main content

Air-gapped installation

How to install Octostar into a cluster with no public internet access. Images come from a private registry; Helm charts come from a vendored charts-offline/ directory; the installer is patched to skip internet-only probes.

📝 First: request the delivery bundle from Octostar

An air-gapped install is driven from a pre-rendered delivery bundle that Octostar produces for you — you do not build the charts and images yourself. Before you begin, contact Octostar to obtain, for your target release, a bundle containing:

  • a container image tarball — every image the deployment needs, in docker save format;
  • a populated charts-offline/ directory — every Helm chart .tgz pinned in versions.yaml;
  • the full octostar-singlenode repository and the client helper scripts (offline-installer.sh, push-images.sh, setup-local-env.sh, the OPA Gatekeeper bundle, …).

When you move to a new Octostar version, request a fresh bundle for that version — the images and charts-offline/ must match the release you install.

📝 The tooling operates on octostar-singlenode — The offline tooling is not a standalone installer — it operates on the full octostar-singlenode repo included in the bundle, rewriting its install.sh, update-existing.sh, and helmfile-*.yaml files to run fully offline. Always keep the full repo; never copy only a wrapper script.

What's in the bundle

ItemPurpose
Image tarball (e.g. octostar-image-list-vN-images.tar)All container images, loaded and pushed to your private registry
octostar-singlenode/ (incl. charts-offline/)The install repo plus every chart .tgz
Client helper scripts (bin/ + lib/)offline-installer.sh, offline-update-existing.sh, offline-precheck.sh, push-images.sh, setup-local-env.sh, patch_charts_offline_helmfiles.py
gatekeeper-k8s-generic/OPA Gatekeeper bundle for private-registry image rewrites

Before you begin

  • A private container registry reachable from all cluster nodes.
  • kubectl, helm, helmfile, yq, python3 on the install host (or use --install-tools).
  • A kube context pointing at the target cluster (kubectl config get-contexts).

Recommended order: stage helpers → Gatekeeper → push-images.shsetup-local-env.shoffline-precheck.sh → create namespaces → offline-installer.sh → verify.

Steps

Run these from the octostar-singlenode repo root on the install host.

0. Stage the helper scripts

Copy the client helpers from the bundle into octostar-singlenode/bin/ (next to install.sh):

  • push-images.sh, setup-local-env.sh
  • offline-installer.sh, offline-update-existing.sh, offline-precheck.sh
  • patch_charts_offline_helmfiles.py
  • the lib/ directory (offline-patch.sh)

Keep the gatekeeper-k8s-generic/ tree from the bundle available on the host.

⚠️ Path assumptions — These scripts expect to live in bin/ next to install.sh, helmfile.yaml, and the chart layout. The Python helper must sit beside offline-installer.sh (the installer invokes it). Running them from another path without adjusting options breaks path assumptions.

1. Install Gatekeeper (registry rewrite)

OPA Gatekeeper rewrites container image registry hosts for labelled namespaces via an AssignImage mutation, so workloads pull from your private registry.

cd gatekeeper-k8s-generic
export PRIVATE_REGISTRY=your.registry.example.com:5000

# Preview the images the rendered chart will use
envsubst < values/values-airgap.yaml \
| helm template gatekeeper ./vendor/gatekeeper-3.22.0.tgz -f - \
| grep -E '^\s+image:' | sort -u

# Install
./scripts/helm-install.sh

Then configure the rewrite:

# 1. Set data.privateRegistry in policies/assignimage/config-registry.yaml, then:
kubectl apply -k policies/assignimage # the -k (Kustomize) is REQUIRED

# 2. Confirm the live object resolved the registry:
kubectl get assignimage rewrite-pod-containers \
-o jsonpath='{.spec.parameters.assignDomain}{"\n"}'
# Expected: your mirror host:port. If you see REPLACE_WITH_PRIVATE_REGISTRY, re-apply with -k.

# 3. Label ONLY the namespaces whose images should be rewritten:
kubectl label namespace YOUR_NS private-registry-enforced=enabled

⚠️ Common Gatekeeper pitfalls

  • Always apply with -k. kubectl apply -f policies/assignimage/assignimages.yaml alone skips Kustomize replacements and leaves assignDomain as REPLACE_WITH_PRIVATE_REGISTRY — no rewrite happens.
  • Use fully qualified image names in manifests (e.g. docker.io/library/busybox:1.36, not busybox). Bare names have no registry domain to rewrite.
  • This bundle is registry-rewrite onlydisableValidatingWebhook: true and disableAudit: true. Do not apply policies/constraints/ unless you flip disableValidatingWebhook: false in values/values-airgap.yaml and reinstall.

The Gatekeeper images (openpolicyagent/gatekeeper:v3.22.0 and openpolicyagent/gatekeeper-crds:v3.22.0) are included in the bundle's image tarball and pushed to your registry in the next step.

2. Push images — push-images.sh

Loads the docker save tarball from the bundle, retags each image under your private registry (stripping the upstream host segment, e.g. docker.io/...registry.client.com/...), and pushes.

./bin/push-images.sh --tarball PATH --registry REGISTRY \
[--username USER] [--password PASS] [--image-list PATH]
  • --tarball — the image tarball from the bundle.
  • --registry — host only, no scheme or trailing slash (e.g. registry.client.com or 100.65.53.20:5000).
  • --image-list — defaults to octo_image_list.txt in the repo root; comments (#) and <none> lines are skipped.
  • Credentials may come from --username/--password, the REGISTRY_PASSWORD env var, or an interactive docker login.

Registries that don't auto-create repositories (AWS ECR, some hardened Harbor/Nexus): supply a create-repo command with --create-repo-cmd (or PUSH_CREATE_REPO_CMD). It runs once per distinct repository before pushing, with {repo}, {registry}, and {image} placeholders substituted. Example for ECR:

./bin/push-images.sh --tarball images.tar \
--registry 123.dkr.ecr.eu-west-1.amazonaws.com \
--create-repo-cmd 'aws ecr describe-repositories --repository-names {repo} --region eu-west-1 >/dev/null 2>&1 || aws ecr create-repository --repository-name {repo} --region eu-west-1'

A nonzero exit from the create command is non-fatal (usually "already exists").

3. Generate local-env.yaml

./bin/setup-local-env.sh [--directory DIR | -d DIR | --installer-root DIR]

Interactively builds local-env.yaml from local-env.template.yaml (must exist in the repo root). Prompts for namespace, domains, and which optional components to enable. Requires yq.

For PostgreSQL (CNPG) in air-gap, set postgresql.imageName, postgresql.operatorImageRepository, and postgresql.operatorImageTag to match your mirrored images (the script's output notes this).

4. Pre-check compatibility — offline-precheck.sh

Strongly recommended before every install/upgrade. Non-destructive: works on a temp copy, touches no cluster.

./bin/offline-precheck.sh
# exit 0 → all install-critical patch anchors present
# exit 1 → an install-critical anchor is missing (bundle/repo drift)

It synthesizes a maximal local-env.yaml (PostgreSQL + monitoring enabled) so every feature-gated patch is exercised regardless of your real config. If it reports missing(fatal), the bundle is inconsistent — contact Octostar for a corrected bundle rather than forcing the install.

5. Pre-create namespaces

offline-installer.sh does not create namespaces. Create cert-manager and your app namespace (from local-env.yaml) first:

kubectl create namespace cert-manager
kubectl create namespace octostar-main # or your configured namespace

6. Install — offline-installer.sh

The air-gap wrapper around bin/install.sh. It patches a temporary copy of install.sh to skip internet probes and rewrites helmfiles to use the local charts from charts-offline/.

./bin/offline-installer.sh --context <kube-context> [ ...same flags as install.sh... ]

Prerequisites:

  • local-env.yaml exists (step 3).
  • kubectl, helm, helmfile, yq, python3 on PATH.
  • Namespaces cert-manager and your app namespace already exist (step 5).
  • Cluster image pulls work via private registry + pull secrets and/or Gatekeeper AssignImage (step 1).
  • Vendored charts present under ~/octostar-singlenode/charts-offline (from the bundle), or OCTOSTAR_CHARTS_OFFLINE.

Key environment variables:

VariableDefaultEffect
OCTOSTAR_CHARTS_OFFLINE$HOME/octostar-singlenode/charts-offlineDirectory of vendored chart .tgz files
OCTOSTAR_OFFLINE_HELMFILE_MAIN_NO_WAIT1Main sync runs without --wait so one slow release doesn't block the install; set 0 for strict wait

7. Verify

kubectl get pods -n octostar-main
kubectl get svc -n octostar-main

Open https://home.<domain> once pods are ready.


Local charts (charts-offline)

The offline installer always uses chart packages from disk — never remote Helm/OCI repos.

  1. Directory. Defaults to $HOME/octostar-singlenode/charts-offline, populated by the bundle with the .tgz files matching versions.yaml (e.g. kube-prometheus-stack-81.6.1.tgz, mysql-9.13.0.tgz, cert-manager-v1.19.2.tgz — note the v prefix on cert-manager). Override the location with OCTOSTAR_CHARTS_OFFLINE.

  2. Helmfile rewriting. On each run, offline-installer.sh calls patch_charts_offline_helmfiles.py, which idempotently rewrites the remote-driven helmfiles in the repo root to:

    • set repositories: [] (so helmfile adds no chart repos), and
    • replace chart:/version: pairs (Bitnami, OCI octostar charts, Jetstack, Prometheus Community, Grafana, OpenSearch, CNPG, Altinity, …) with local paths of the form chart: '{{ requiredEnv "OCTOSTAR_CHARTS_OFFLINE" }}/<archive>-{{ .Values... }}.tgz'.

    Files touched include helmfile.yaml, helmfile-monitoring.yaml, helmfile-nginx.yaml, helmfile-octostar.yaml, helmfile-postgresql.yaml, helmfile-preps.yaml, helmfile-data.yaml, helmfile-clickhouse.yaml, helmfile-metallb.yaml, helmfile-traefik.yaml, helmfile-photon.yaml, helmfile-seaweedfs.yaml, helmfile-temporal.yaml, helmfile-langfuse.yaml, and helmfile-backup.yaml.

  3. Not rewritten. Releases already using in-repo paths (./helm/..., ./charts/...) are left unchanged.

  4. First run mutates tracked YAML. These edits persist in your working tree. Commit or snapshot if you need to reproduce the same tree elsewhere.

  5. Grafana plugins. Monitoring values may set grafana.plugins: [] so the stack doesn't call grafana.com for GF_INSTALL_PLUGINS. The bundle's Grafana image bakes in grafana-clickhouse-datasource and points GF_PATHS_PLUGINS at it.


Order of operations (summary)

  1. Request the bundle from Octostar for your target release.
  2. Stage helpers (0) → Gatekeeper (1) → push-images.sh (2) → setup-local-env.sh (3) → offline-precheck.sh (4) → create namespaces (5) → offline-installer.sh (6) → verify (7).

For upgrading an existing air-gap install, see Upgrading.