Skip to main content

Offline install

Use the offline bundle when you cannot, or do not want to, pull from our registry. That covers a cluster with no AWS account behind it, and a network with no route to the registry at all.

The bundle is one tarball. It holds the Helm chart, the Align images as docker save files, a manifest of the digests the images were built from, and a SHA256SUMS file over everything.

Get the bundle​

Ask sales@align.tech for the release you want. We build it and send you two things:

  1. align-offline-<release>.tar
  2. Its sha256, on a different channel than the tarball (a call, or a separate email)

The images are linux/amd64 only.

Optional, on request: the k3s airgap images for a k3s version you name.

Verify it​

The checksum we send protects you from a bundle that was altered on the way. Hash the tarball yourself first, with your own tool. The scripts inside the bundle are part of the bundle, so a tampered tarball could carry a verify.sh that always says yes.

sha256sum align-offline-<release>.tar # macOS: shasum -a 256 align-offline-<release>.tar
# compare with the hash we sent you on the other channel, and stop if it differs
tar -xf align-offline-<release>.tar
bash align-offline-<release>/verify.sh align-offline-<release>.tar --expected-sha256 <hash>

verify.sh refuses a tarball with no --expected-sha256. --allow-unverified-origin overrides that, and then it proves only that the bundle is self-consistent.

verify.sh needs no network. It needs cosign (v2.4 or later) and jq on the machine. It fails unless:

  • the tarball matches the hash we sent you
  • every line of SHA256SUMS is well formed, and every file matches it
  • no file is present that SHA256SUMS does not list, and nothing is a symlink
  • each image file holds exactly the image tag the manifest names
  • each image has a cosign signature in signatures/ that cosign verify-blob --offline accepts, with the Fulcio chain, Rekor key and CT log key all taken from trust/trusted_root.json. The certificate must name our release.yaml workflow at this release's tag, with the GitHub Actions issuer. The signed payload must name the digest the manifest lists for that image.

How the signature check works with no network. Release signatures are keyless. Each one comes with a short-lived Fulcio certificate and an entry in the Rekor transparency log. At build time we save both next to the signature, and save Sigstore's trusted root (the Fulcio roots and the Rekor and CT log keys) in trust/. verify.sh then checks the signature, the certificate chain, the log entry's signed timestamp and the certificate's embedded SCT locally. It does not look anything up. It prints cosign's own error for each signature if none passes.

What it does not prove. The trusted root travels inside the bundle, so it is covered by SHA256SUMS and by the hash you got on a second channel, not by anything Sigstore signs for you. If you want it independent, run cosign initialize once on a connected machine (with an empty HOME) and compare ~/.sigstore/root/targets/trusted_root.json with trust/trusted_root.json. SHA256SUMS alone shows the files are unchanged since we built the bundle. The hash you got on a second channel shows the bundle is the one we sent. The signatures show the image digests listed in the manifest came out of our release workflow. verify.sh does not recompute a registry digest from the docker save tarballs, so tie the tarballs to the signed digests by the outer hash, not by the signatures. To pin the trust root yourself, pass --trusted-root <file> to verify.sh. A bundle built with --skip-signature-check carries no signatures. verify.sh refuses it unless you pass --allow-no-signatures.

What was actually run. On 27 Oct 2026 we built a bundle for release v0.57.16 (image migrations) and ran verify.sh with cosign v2.4.1 inside a network-less Linux namespace (unshare -rn, empty HOME). It printed Verified OK. A one-character change to the signed payload, and a manifest naming a different release tag, both failed in cosign. Only the migrations image was run end to end, not the other images. cosign versions other than v2.4.1 are untested. The release workflow pins the cosign version it signs with to v2.4.1, the same one we tested, so releases cut after that pin are signed by it. A release cut before the pin used whatever cosign was current then.

Reject an older bundle. An older bundle with valid signatures also verifies, because each one is genuinely ours. Pass --expected-release <tag> (for example --expected-release v0.57.16) with the release you were told to expect. verify.sh then fails when the manifest names any other release.

Load the images​

On every machine whose container runtime will run Align pods:

bash align-offline-<release>/load.sh align-offline-<release>.tar --expected-sha256 <hash>

load.sh runs verify.sh first and loads nothing if it fails. Then it:

RuntimeCommandWhat happens
Docker as the node runtimedefaultdocker load for each image
containerd (kubeadm, EKS and most others)add --runtime containerdctr -n k8s.io images import for each image. docker load would not reach the kubelet here
A registry you already runadd --target-registry registry.internal:5000Loads, retags, and pushes each image there
k3sadd --runtime k3s (as root)Copies each file into /var/lib/rancher/k3s/agent/images, which k3s imports

It ends by printing the value to use for global.imageRegistry. On a cluster with several nodes, k3s and plain Docker both need the images on each node that can schedule Align pods, unless you used --target-registry.

Install the chart from the file​

The chart is in the bundle, so helm install needs no registry login. The bundle also holds values-offline.yaml, generated for that release. It sets the registry, the release tag, the tag of each connector you bundled, and the migrations pull policy. Those last two matter: the chart defaults connectors to latest and migrations to Always, and either one makes the cluster reach for the registry.

helm install align align-offline-<release>/chart/align-<chart version>.tgz \
--namespace align --create-namespace \
--values values.yaml \
--values align-offline-<release>/values-offline.yaml

Pass your own file first, so values-offline.yaml wins on the image keys. If you loaded with --target-registry, add --set global.imageRegistry=<that registry> last.

You do not need global.imagePullSecrets, because nothing is pulled. If a pod sits in ImagePullBackOff, the image is not on that node, or its name differs from the one the chart builds. Compare the two:

kubectl -n align describe pod <pod> | grep -i image
docker images | grep align/ # on that node

Images the bundle does not carry​

The bundle holds Align's images only. These are third-party images from their own public registries, and each one needs you to load it yourself or turn its feature off:

ImageUsed whenDefault
pgvector/pgvector:pg16postgresql.enabled (bundled Postgres)Off
redis:7-alpineredis.enabled (bundled Redis)Off
postgres:15-alpineThe telemetry CronJobsOn
otel/opentelemetry-collector-contrib:0.149.0observability.enabledOff
bitnami/pgbouncergateway.pgbouncer.enabledOff

The names for pgvector, Redis, the telemetry CronJobs and the OTel collector are written into the chart templates, and the tags above were read from this release's chart. Read the tags from your chart before you save anything: helm template it and grep for image:. Only the pgbouncer image has a value you can repoint. To load one yourself, docker save it on a connected machine. To skip the telemetry CronJobs, set telemetry.aggregation.enabled=false and telemetry.dailyRollup.enabled=false. If you use the GPU server, its vLLM image and the NVIDIA plugin image come from elsewhere too.

What an offline install still needs​

The bundle removes the registry pull for Align's images. The Brain still calls the LLM and embedding endpoints you configure, and connectors still call the tools they read. The images in the table above still come from those third-party registries. See LLM configuration for the provider options.