Air-gapped installation
Nothing in Nazeel reaches the internet. An air-gapped install therefore needs three things carried across the boundary: the chart, the images, and the licence key.
1. Carry one file across
Ask for the air-gap bundle attached to the release, or build it on a connected machine:
make bundle # dist/bundle/nazeel-airgap-0.1.0.tar.gz, plus its .sha256
It carries the images, the packaged chart, the CRDs, the CLI binaries for every platform,
the SBOMs and signatures (on release bundles), and the mirror.sh that pushes the images
into your registry. One file crosses the boundary instead of a list of steps to follow
correctly on a machine with no internet to check against.
Take the bundle and your licence key (a single line beginning NZL2.) into the isolated
network, and check the tarball arrived intact:
shasum -a 256 -c nazeel-airgap-0.1.0.tar.gz.sha256
tar -xzf nazeel-airgap-0.1.0.tar.gz && cd nazeel-airgap-0.1.0
2. Mirror the images
./mirror.sh registry.bank.internal
# a registry that requires a project or path prefix:
./mirror.sh harbor.bank.internal platform
It loads each image and pushes it, using docker or podman, whichever the host has. It fails loudly if an image is missing from the bundle rather than leaving you to find out at install time.
One image carries the operator and the router; the API image carries the dashboard. The
bundle also carries the images that only some installations use — the bundled registry, and
rootless BuildKit, the git image and the Paketo builder for building from source — because
the alternative is discovering one is missing after the bundle has crossed the boundary.
make images-list prints the same list outside the bundle, and make images-check fails the
build if the chart can deploy an image the list does not name.
The one exception is the vulnerability scanner. imageScan.image is empty by default,
because the scanner and its database are your choice; if you enable scanning, mirror that
image yourself along with the database PVC it reads.
What was tested, and why the bundle is the durable copy
Third-party images are pinned to exact versions so a moved tag cannot change what an assessed installation runs. These are the digests of the 0.1.0 bundle, so you can verify that what you mirrored is what we shipped:
| Image | Digest |
|---|---|
distribution/distribution:3.0.0 |
sha256:4ba3adf47f5c866e9a29288c758c5328ef03396cb8f5f6454463655fa8bc83e2 |
moby/buildkit:v0.17.2-rootless |
sha256:5b45405a38c579692f6fcd47ceef2002fe4fa61bb04ef0c2c644cf74cbbd57b8 |
alpine/git:2.45.2 |
sha256:16ad8e788e1d3b0c30f18da8dde5c0ace3b187445a62d8af893b003ca1e70592 |
paketobuildpacks/builder-jammy-base:latest |
sha256:baacac1ea303146e5c86f7b947eb7f814163c798c6801247b0ba4d5f6bd3e32d |
Check one after mirroring:
docker image inspect <image> --format '{{index .RepoDigests 0}}'
The Paketo builder is the one image with no version tag to pin to — Paketo publishes it as
a rolling latest. That matters for a product expected to work years after a sale, so:
- The bundle carries the image. Once it is in your registry it is yours, and nothing upstream can remove it. Keep the bundle tarball with your other release artefacts; it is the durable copy, not Docker Hub.
- You can use your own builder. Set
build.buildpackBuilderto any CNB-compatible builder already in your registry and nothing reaches Paketo at all. - It is only needed for repositories with no Dockerfile. A repository that has one is built by BuildKit and never touches a builder image.
The same reasoning applies to every image here: after mirror.sh has run, an installation
pulls exclusively from your registry, and its ability to start and to build does not depend
on any external registry continuing to exist.
3. Install
kubectl apply -f chart/crds/
kubectl create namespace nazeel-system
kubectl -n nazeel-system create secret generic nazeel-bootstrap --from-literal=token="$(openssl rand -hex 24)"
kubectl -n nazeel-system create secret generic nazeel-licence --from-file=licence=./licence.txt
helm install nazeel chart/nazeel-0.1.0.tgz -n nazeel-system \
--set global.imageRegistry=registry.bank.internal \
--set ingress.domain=dev.bank.internal \
--set ingress.className=nginx \
--set ingress.tlsSecret=wildcard-dev-bank-internal \
--set ingress.dashboard.host=nazeel.bank.internal \
--set ingress.dashboard.tlsSecret=nazeel-tls \
--set api.bootstrap.existingSecret=nazeel-bootstrap \
--set api.licence.existingSecret=nazeel-licence
Then check it answers, from inside the cluster:
helm test nazeel -n nazeel-system
global.imageRegistry rewrites every image reference; the operator passes it on to the
router image it deploys. Application images stay wherever your Baselines point; use
imageRegistry on a Baseline to rewrite a chart's images to your mirror.
What the admission webhook needs
Nothing. The operator issues its own CA and serving certificate, stores them in the Secret
nazeel-webhook-cert in its namespace, renews them yearly and publishes the CA into its
ValidatingWebhookConfiguration. If the cluster already runs cert-manager and policy
requires it, set operator.webhook.certMode=cert-manager and an issuerRef.
Git access from the operator
Baselines pull from your internal git server with a Secret in the application namespace.
The Secret must carry the label nazeel.sa/git-credentials=true: the operator only
reads Secrets that carry it, so connecting an application never exposes your other
application secrets to it.
kubectl -n payments create secret generic shop-git \
--from-literal=token="$GIT_TOKEN" --from-literal=username=nazeel
kubectl -n payments label secret shop-git nazeel.sa/git-credentials=true
| Key | Purpose |
|---|---|
token, username |
HTTPS token (username defaults to nazeel) |
ssh-privatekey, known_hosts, passphrase |
SSH; known_hosts is mandatory |
ca.crt |
PEM bundle of your private CA for HTTPS |
Helm chart dependencies must be vendored under charts/ in the repository; the operator
never contacts a chart repository.
Verifying the install
kubectl -n nazeel-system get pods
kubectl get validatingwebhookconfiguration nazeel-validating-webhook -o jsonpath='{.webhooks[0].clientConfig.caBundle}' | wc -c # > 0
curl -k https://nazeel.bank.internal/api/v1/auth/providers
hack/e2e-chart.sh runs this whole flow on a kind cluster with images loaded from disk and
no registry at all, which is the closest reproducible stand-in for an air-gapped site.