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.comand 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:
crane uses the same logins as docker. Log it in to the source too:
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:
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:
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.