Air-gapped install
Build a signed offline bundle on a connected host, carry it across, verify it, mirror the images into a private registry and install MAQPNA with no internet access.
MAQPNA makes no outbound calls by default: no phone-home, no usage analytics, no update checks and no licence server. For a disconnected cluster you build an offline bundle on a connected host, carry it across, verify it, push its images to your private registry and install the chart from the bundle.
flowchart LR
subgraph Connected host
A[maqpna airgap bundle] --> B[maqpna-airgap-TAG.tar.gz<br/>images, chart, SBOMs,<br/>SHA256SUMS + signature]
end
B -->|removable media| C
subgraph Air-gapped site
C[maqpna airgap verify<br/>--require-signature] --> D[maqpna airgap push<br/>--registry REG]
D --> E[maqpna install --chart ./chart/...<br/>airgap.imageRegistry=REG]
E --> F[maqpna smoke / doctor]
end
Goal#
An installation whose every image comes from your in-country registry, installed from a bundle whose checksums and signature you verified offline.
Prerequisites#
| Where | You need |
|---|---|
| Connected build host | A MAQPNA source checkout (the bundle script lives in hack/), docker, and optionally helm (to package the chart), syft (SBOMs) and cosign (signature). Access to the MAQPNA images. |
| Air-gapped site | maqpna and maqpna-install (copy the binaries across too), cosign for signature checks, one of skopeo, crane or docker for the push, kubectl and helm if you use hack/airgap-install.sh. A private registry, for example registry.eu.internal:5000/maqpna. The agent-sandbox controller is in the bundle. |
What a bundle contains#
| Path | Content |
|---|---|
images/ |
docker-archive tarballs of the 11 MAQPNA images (maqpna-operator, maqpna-gateway, maqpna-identity, maqpna-attest, maqpna-attest-agent, maqpna-exec, maqpna-egress-relay, maqpna-cli, mcp-echo, maqpna-browser, maqpna-code-interpreter) and the upstream agent-sandbox controller images |
images.txt |
<source-ref> <archive> <repo:tag> per image |
chart/ |
The packaged Helm chart and values-sovereign-eu.yaml (the chart sources when helm was not installed) |
manifests/ |
The upstream agent-sandbox release manifest (sandbox-with-extensions.yaml) |
sbom/ |
SPDX SBOMs per image, when syft is installed |
hack/airgap-install.sh |
The install script |
SHA256SUMS |
Checksums of every file above |
SHA256SUMS.sig or SHA256SUMS.cosign.bundle |
A key-based or keyless cosign signature of SHA256SUMS |
Steps#
1. Build the bundle (connected host)#
cd maqpna # a checkout of the MAQPNA source (https://maqpna.com/github)
maqpna airgap bundle --registry "$REGISTRY" --tag 0.1.1 --pull --key cosign.key --out bundle/
REGISTRY is the release registry listed on maqpna.com/download.
maqpna airgap bundle runs hack/airgap-bundle.sh from the checkout (--repo DIR, default the current directory or
a parent). The flags map to the script's environment variables:
| Flag | Env | Default |
|---|---|---|
--registry |
REGISTRY |
ghcr.io/maqpna |
--tag |
TAG |
dev |
--agent-sandbox-version |
AGENT_SANDBOX_VERSION |
v1.0.4 |
--out |
OUT_DIR |
dist |
--key REF |
COSIGN_KEY |
none: a key-based SHA256SUMS.sig (file, awskms://, pkcs11:) |
--keyless |
COSIGN_KEYLESS=1 |
off: a keyless SHA256SUMS.cosign.bundle |
--pull |
PULL=1 |
off: use local images and pull only missing ones |
The script writes maqpna-airgap-<TAG>.tar.gz to the output directory. Without --key or --keyless the bundle is
not signed; sign it, so the site can check where it came from.
2. Verify the bundle (air-gapped site)#
Extract it and verify the checksums, the signature and that all 11 images are present:
tar xzf maqpna-airgap-0.1.1.tar.gz && cd maqpna-airgap-0.1.1
maqpna airgap verify . --require-signature --key cosign.pub
For a keyless bundle, pass the identity that signed it (the person or CI workflow on the build host) instead of a
key, and a Sigstore trusted_root.json for offline verification:
maqpna airgap verify . --require-signature \
--certificate-identity release-builder@example.eu \
--certificate-oidc-issuer https://accounts.google.com \
--trusted-root trusted_root.json
Without --certificate-identity or --certificate-identity-regexp, the default identity is the MAQPNA release
workflows.
Real output of a key-signed test bundle without images (--no-image-check skips the image list):
$ maqpna airgap verify bundle --require-signature --key cosign.pub --no-image-check
checksums: 54 ok, 0 failed
signature: cosign verify-blob OK (key)
bundle OK
A changed file fails, with exit status 3:
$ maqpna airgap verify bundle --no-image-check
FAIL chart/maqpna/values.yaml: checksum mismatch
checksums: 53 ok, 1 failed
signature: neither SHA256SUMS.sig nor SHA256SUMS.cosign.bundle present - NOT VERIFIED
Without --no-image-check, a bundle that lacks images fails too:
images: 0 of 11 MAQPNA images, 0 upstream (tag )
FAIL images missing from the bundle: maqpna-operator, maqpna-gateway, maqpna-identity, maqpna-attest, maqpna-attest-agent, maqpna-exec, maqpna-egress-relay, maqpna-cli, mcp-echo, maqpna-browser, maqpna-code-interpreter
FAIL no upstream agent-sandbox images in the bundle
The standalone offline binary maqpna-sovereign verify-bundle [-key cosign.pub] [-require-signature] DIR runs the same
checksum and signature checks (not the image list) for CI pipelines that carry only that binary. It exits 1 on a
failure.
3. Push the images to your registry#
maqpna airgap push . --registry registry.eu.internal:5000/maqpna --dry-run # print the copies
maqpna airgap push . --registry registry.eu.internal:5000/maqpna
push copies every image in images.txt with skopeo, crane or docker, whichever it finds.
4. Install from the bundle#
Install the agent-sandbox controller from the bundle's manifest (its images now come from your registry; edit the image references if your nodes cannot pull from the upstream names), then install the chart from the bundle:
kubectl apply -f manifests/agent-sandbox-v1.0.4.yaml
maqpna install --chart ./chart/maqpna-0.1.1.tgz --profile sovereign-eu -f site-values.yaml \
--set airgap.imageRegistry=registry.eu.internal:5000/maqpna --wait
airgap.enabled: true (set by the sovereign-eu profile) makes the chart refuse to render public registry references
for MAQPNA images; airgap.imageRegistry replaces image.registry. Also set
sovereignty.allowedRegistries to your mirror, so sandboxes may only run images from it.
Alternatively, the bundle's script does the same with Helm and kubectl:
REGISTRY=registry.eu.internal:5000/maqpna SKIP_PUSH=1 EXTRA_VALUES=site-values.yaml \
COSIGN_PUB=cosign.pub hack/airgap-install.sh
| Variable | Meaning |
|---|---|
REGISTRY |
Required. Private registry and prefix. |
NAMESPACE, RELEASE |
Default maqpna-system, maqpna. |
VALUES |
Default chart/values-sovereign-eu.yaml. |
EXTRA_VALUES |
Your site overrides (recommended). |
COSIGN_PUB |
Public key to verify SHA256SUMS.sig (recommended). |
SKIP_PUSH=1 |
Images already mirrored; only install. |
SKIP_DEPS=1 |
Do not apply the agent-sandbox manifest. |
DRY_RUN=1 |
Print what would be done. |
5. Verify the installation#
maqpna smoke --gateway "$GW"
maqpna doctor --strict
Single-node edge sites (k3s)#
Import the images into k3s's containerd instead of pushing to a registry, then install from the bundle's chart:
for f in images/*.tar; do sudo k3s ctr images import "$f"; done
maqpna install --chart ./chart/maqpna-0.1.1.tgz --profile dev --wait
Upgrade an air-gapped installation#
Build, carry and verify the new bundle, push its images, then upgrade from its chart:
maqpna upgrade check --chart ./chart/maqpna-0.2.0.tgz
maqpna upgrade --chart ./chart/maqpna-0.2.0.tgz --dry-run
maqpna upgrade --chart ./chart/maqpna-0.2.0.tgz --wait --atomic
See Upgrade and rollback.
Troubleshooting#
| Symptom | Cause | Fix |
|---|---|---|
signature: neither SHA256SUMS.sig nor SHA256SUMS.cosign.bundle present - NOT VERIFIED |
The bundle was built without --key or --keyless |
Rebuild with a signature, or accept an unsigned bundle by omitting --require-signature (not recommended). |
bundle --key stops with must specify --bundle with --new-bundle-format |
cosign 3.x refuses a key-based sign-blob --output-signature without the legacy-format flags, and the script stops on the error |
Use --keyless, or cosign 2.x on the build host. |
FAIL images missing from the bundle |
Images were not available locally and --pull was not set, or --tag does not match the published tag |
Rebuild with --pull and --tag X.Y.Z. |
Pods in ImagePullBackOff |
airgap.imageRegistry does not match where you pushed |
Use the same value as push --registry. |
preflight: image-registry-allowed fails |
The mirror is not in sovereignty.allowedRegistries |
Add the registry prefix. |
Next steps#
- Verify releases: verify the images and chart before you bundle them.
- Sovereignty profiles: jurisdiction, egress and attestation.
- Command reference:
maqpna airgap bundle,maqpna airgap verify,maqpna airgap push.