Skip to content

Air-gapped install

KubeManta runs on clusters with no route to the internet. You copy everything it needs into your own container registry first, then install from there with air-gap mode on.

The chart ships the tool that does the copying, so it always matches the version you install. It works two ways:

  • Copy — one machine can reach both registry.kubemanta.com and your registry. Images go straight across.
  • Export, then import — no machine reaches both. Download everything into one file on a connected machine, carry it across, and push it into your registry from the inside.

Both end with the same thing: your registry holds every image, and a generated values file points the install at it.

Before you start

You need Notes
A container registry inside the sealed network Any Docker Registry HTTP API v2 registry: Harbor, Artifactory, Nexus, Quay, GitLab, a cloud registry (ECR, ACR, Artifact Registry), or registry:2. It must create repositories on push. ECR does not — see ECR below.
python3 and crane On the machine that copies, and on the inside machine when you use a bundle. Nothing else is required: the tool uses only the Python standard library.
helm on a connected machine To download the chart, which contains the tool.
An offline licence file (.lic) Ask your KubeManta contact. A sealed cluster cannot validate a licence key online, so without the file the install runs on Free.
A CNI that enforces egress NetworkPolicies Calico and Cilium do. Air-gap mode's boundary is a default-deny egress policy; check it is enforced after installing.
A Kubernetes API endpoint on a private address Or add its address to security.airgap.extraEgressCidrs.

Space. A full mirror is about 3.5 GB: KubeManta's images, the third-party images it starts on demand (the vulnerability scanner, the network probe, the guided Prometheus), the optional cert-manager subchart's images and the scanner's vulnerability databases. The bundle file is the same size; exporting and importing need about 1 GB of scratch space on top.

1. Get the tool

On a connected machine, log in with your licence key (the user name is license) and download the chart for the version you will install:

helm registry login registry.kubemanta.com -u license      # password: your licence key
helm pull oci://registry.kubemanta.com/kubemanta/charts/stable/kubemanta \
  --version 1.5.0 --untar

The tool is kubemanta/airgap/kubemanta-airgap.py. List what it will copy:

python3 kubemanta/airgap/kubemanta-airgap.py list

crane uses the same logins as docker. Log it in to the source too:

crane auth login registry.kubemanta.com -u license --password-stdin < licence.key

2a. Copy straight across

When this machine can also reach your registry, log in to it and copy:

crane auth login registry.internal.example.com -u <user> --password-stdin < password.txt

python3 kubemanta/airgap/kubemanta-airgap.py copy \
  --to registry.internal.example.com/kubemanta \
  --pull-secret kubemanta-registry

Everything is copied by digest, so the images on your cluster are byte-identical to the released ones. The chart is copied too, to <registry>/charts/kubemanta. Re-running is safe: images already there are skipped by the registry.

2b. Export, carry across, import

On the connected machine:

python3 kubemanta/airgap/kubemanta-airgap.py export --out kubemanta-airgap-1.5.0.tar

Move kubemanta-airgap-1.5.0.tar into the sealed network by whatever route your organisation approves. The bundle carries its own copy of the tool, so on the inside machine you need only the file:

tar -xf kubemanta-airgap-1.5.0.tar airgap Chart.yaml
crane auth login registry.internal.example.com -u <user> --password-stdin < password.txt

python3 airgap/kubemanta-airgap.py import \
  --bundle kubemanta-airgap-1.5.0.tar \
  --to registry.internal.example.com/kubemanta \
  --pull-secret kubemanta-registry

Import checks every image's digest after pushing it and stops if one does not match. It also leaves kubemanta-1.5.0.tgz in the current directory, so you can install from the file if your registry does not hold Helm charts.

A registry on plain HTTP or with a self-signed certificate: add --insecure before the command (kubemanta-airgap.py --insecure import …).

3. Install

Create the namespace first, and label it so the API server enforces the Kubernetes restricted Pod Security profile every KubeManta pod meets (recommended; see Install):

kubectl create namespace kubemanta-system
kubectl label namespace kubemanta-system \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/warn=restricted \
  pod-security.kubernetes.io/audit=restricted

copy and import both write kubemanta-airgap-values.yaml. It points every image at your registry, including the ones KubeManta starts later, and turns air-gap mode on. Install with it and the licence file:

helm install kubemanta oci://registry.internal.example.com/kubemanta/charts/kubemanta \
  --version 1.5.0 -n kubemanta-system --create-namespace \
  -f kubemanta-airgap-values.yaml \
  --set-file license.file=customer.lic

Use --set-file for the licence, never --set: the file is multi-line.

If your registry needs a credential, create the pull secret named with --pull-secret before installing:

kubectl -n kubemanta-system create secret docker-registry kubemanta-registry \
  --docker-server=registry.internal.example.com \
  --docker-username=<user> --docker-password=<password>

If you will deploy the guided Prometheus (Admin → Observability → Prometheus → Deploy), create the same secret in the namespace you deploy it into. If your nodes can pull from the registry without a secret, leave --pull-secret out.

If your registry is on a public address range, the vulnerability scanner (which downloads its database from your registry) cannot reach it through the air-gap policy. Add its address:

  --set 'security.airgap.extraEgressCidrs={203.0.113.10/32}'

Keep the values file with your install. Regenerate it on every upgrade rather than editing it, so new images are picked up.

4. Verify the install

Every pod should run images from your registry:

kubectl -n kubemanta-system get pods \
  -o jsonpath='{range .items[*]}{range .spec.containers[*]}{.image}{"\n"}{end}{end}' | sort -u

And public egress should be blocked. This must print OK:

kubectl exec -n kubemanta-system deploy/kubemanta-agent -c agent -- \
  python3 -c "import socket; socket.create_connection(('1.1.1.1',443),timeout=6); print('REACHED')" \
  2>&1 | grep -q REACHED && echo "FAIL: egress is not enforced" || echo "OK: public egress is blocked"

FAIL means your CNI is not enforcing NetworkPolicies, and the cluster is not sealed: install or enable a policy-enforcing CNI before relying on air-gap mode.

On Cilium the chart also renders a CiliumNetworkPolicy, <release>-apiserver, that allows the Kubernetes API server on ports 443 and 6443: Cilium cannot match the API server with a plain NetworkPolicy. It adds no other destination, so the air-gap boundary is unchanged. If you render the chart with helm template or a GitOps tool that cannot see the cluster's APIs, set security.cilium.apiServerPolicy=true; if your API server listens on another port, add it to security.cilium.apiServerPorts.

Verify the images are genuine

KubeManta's images are signed, and the signatures are mirrored with them. You can check one from inside the sealed network: --offline uses the transparency-log proof that travels with the signature, so nothing is fetched.

cosign verify --offline \
  --certificate-identity-regexp '^https://github.com/kubemanta/kubemanta/\.github/workflows/ci\.yml@refs/tags/v' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  registry.internal.example.com/kubemanta/kubemanta-agent:1.5.0

The output names the release that signed the image. Do not substitute --insecure-ignore-tlog: it fails for these signatures.

What air-gap mode changes

In air-gap mode
Network A default-deny egress policy: pods reach DNS and private (RFC 1918) ranges only, plus extraEgressCidrs. Cloud metadata stays blocked.
AI In-cluster Ollama only. External providers are refused.
Licence Validated from the licence file, entirely offline. The file has an expiry date; ask for a new one before it passes, or the install drops to Free.
Updates The in-product update check is off and cannot be turned on. Follow release announcements instead.
Vulnerability scanning Works offline against the database in your registry.
Pre-install checks The preflight hook is skipped.
SSO, SMTP, alert webhooks Work when the destination is on a private address, or listed in extraEgressCidrs.
Expert Mode egress (expertMode.networkPolicy.egress.enabled, opt-in) The expert-exec pod gets a narrower policy of its own: the API server, cluster DNS and expertMode.networkPolicy.egress.extraCIDRs only — not the private ranges above, and never the internet. List a private chart registry it installs from in extraCIDRs.
TLS certificates Bring your own, or use cert-manager with an issuer that works inside your network (global.certManagerIssuer). The optional cert-manager subchart (global.installCertManager) is mirrored with everything else, but the Let's Encrypt issuer it can create needs the internet.

Keep the scanner database fresh

The vulnerability database is updated upstream several times a day, independently of KubeManta releases. A scan against an old database reports fewer vulnerabilities, not an error. Re-run copy, or export and import, on a schedule (weekly is a reasonable minimum), not only when you upgrade.

Upgrade

Repeat steps 1 to 3 with the new version: download its chart, copy or export and import, then helm upgrade with the newly generated values file and your licence file. Copy the new version into the registry before upgrading; nothing on the sealed side can fetch a missing image later.

Amazon ECR

ECR does not create repositories on push, so create them once before copying. The names are the last path segment of each image, under your prefix:

for r in kubemanta-agent kubemanta-ui trivy haproxy litestream netshoot python \
         prometheus kube-state-metrics trivy-db trivy-java-db trivy-checks \
         cert-manager-controller cert-manager-webhook cert-manager-cainjector \
         cert-manager-startupapicheck cert-manager-acmesolver charts/kubemanta; do
  aws ecr create-repository --repository-name "kubemanta/$r" >/dev/null 2>&1 || true
done

The five cert-manager-* repositories hold the optional cert-manager subchart (v1.21.2), which the tool copies whether or not you enable it, so an install that turns on global.installCertManager later can still pull it. kubemanta-airgap.py list prints the full set for your version. Then copy to <account>.dkr.ecr.<region>.amazonaws.com/kubemanta.