ApiaryLensOpen Source Apiary Intelligence

Authoritative project documentation

Air-gap install and transported update

Tested against apiarylens-0.1.0-preview.6-airgap-b07141c0b494.tar from the published v0.1.0-preview.6 release on 2026-07-19, end to end on a pristine reference host: Ubuntu 24.04.4 LTS x86-64, Docker Engine 29.6.2 with the Compose v2 plugin from Docker's apt repository, the daemon on its shipped defaults (containerd image store, no daemon.json), and all egress blocked at both the container and host level for the entire run. The walk covered the connected-side trust boundary (SHA-256 against SHA256SUMS and the release manifest, keyless attestation via gh attestation verify), verify-bundle.sh, install-airgap.sh (image load, post-load identity gate, network-isolated migration to head 0004, offline --no-build activation), /health reporting the exact release identity ApiaryLens@0.1.0-preview.6+8d5d4ff, a verified backup.sh archive, and a passing restore-test.sh — with zero egress observed. Full evidence: UAT re-run record. The two preview.5 defects that run regression-proves fixed — the image-store-dependent post-load gate (#91) and the broken release identity binding (#92) — are closed on that evidence. Use v0.1.0-preview.6 or later; the preview.4 and preview.5 air-gap bundles fail install on current default-configured Docker hosts.

Run ApiaryLens Compose on a Linux host with zero outbound network — no registry pulls, no package downloads, no certificate authority, no update checks. Install and update happen entirely from a verified offline bundle carried to the host on transport media.

ApiaryLens is built for this: the API container runs on an internal-only network with no route out, database migrations are embedded in the product image, and no telemetry or phone-home exists to disable. The one thing an air-gapped host cannot do is public certificate issuance, covered below.

The offline bundle

Each supported release publishes one transportable artifact:

apiarylens-<version>-airgap-<sha12>.tar
├── bundle-manifest.json      versions, dual image identities, member hashes, migration head
├── compatibility-manifest.json
├── release-identity.json
├── checksums.sha256          verify every member with sha256sum -c
├── images/                   prebuilt images (docker load), incl. helper image
├── compose/                  compose.yaml + air-gap override + Caddyfile + .env.example
├── migrations/               ordered migration history with checksums
├── scripts/                  verify, load, install, update, backup, restore, rollback
└── docs/AIRGAP.md            the bundled offline runbook

For every image the manifest records two identities, both derived from the bundle's own archive bytes: the config-blob digest (apiImageId, webImageId, helperImageId) and the OCI manifest digest (apiImageManifestDigest, webImageManifestDigest, helperImageManifestDigest). After docker load, the post-load identity gate accepts a loaded image whose reported ID equals either recorded value, because what docker image inspect reports as .Id depends on the daemon's image store — the classic graphdriver store reports the config digest, while the containerd image store (the default on current Docker Engine) reports the OCI manifest digest. The gate is therefore image-store independent without being weakened: anything matching neither recorded identity is refused. No daemon reconfiguration is needed on either store.

The air-gap Compose override removes all build: sections and sets pull_policy: never, so the deployment cannot reach for a registry even accidentally.

The host envelope matches the standard Compose install — Ubuntu Server 24.04 LTS x86-64, Docker Engine with Compose v2 — except that every "outbound HTTPS" requirement is removed. The bundle manifest records the minimum Compose version the release was tested with; the installer's preflight compares before installing.

On the connected machine (export)

  1. Download from the release page: the air-gap bundle, the SHA256SUMS file, and the release manifest (release-manifest.json).
  2. Verify, in this order: the bundle's SHA-256 (sha256sum --check --ignore-missing SHA256SUMS, and confirm the same digest appears in the release manifest's entry for the bundle); the release's signing attestation (gh attestation verify apiarylens-<version>-airgap-<sha12>.tar --repo ApiaryLens/apiarylens); then extract checksums.sha256 and run sha256sum -c over every member. This connected-side check is the trust boundary — the air-gapped side re-verifies integrity, not provenance.
  3. Record the verification output with your deployment records.
  4. Copy the tar to dedicated or write-once transport media, and re-hash the copy on the media before it leaves the machine.

On the air-gapped host — first install

  1. Copy the tar onto the host and check its SHA-256 against the value carried with it. Extract it, for example into a directory named bundle.

  2. Verify the bundle before touching anything:

    bundle/scripts/verify-bundle.sh --bundle-dir bundle
    

    This checks every member against checksums.sha256 and evaluates the compatibility manifest. Stop on any failure.

  3. Run the installer:

    bundle/scripts/install-airgap.sh --bundle-dir bundle \
      --target /opt/apiarylens --project apiarylens \
      --site-address <your-internal-name>
    

    The installer performs the whole first-install lifecycle and stops at the first failed step: it re-verifies the bundle; preflights the host (x86-64, Compose v2 at or above the bundle's recorded minimum, no interrupted prior operation in the update ledger); creates the two secret files locally from /dev/urandom (or accepts your own via --bootstrap-secret-file and --auth-root-secret-file); stages the release under /opt/apiarylens/releases/<version>; loads the images with docker load and enforces the post-load identity gate described above; runs the database migration as a one-shot container with --network none before any service starts; activates the release through the air-gap override with --no-build; verifies /health reports the exact release identity; and commits the install to the append-only update ledger. --http-port, --https-port, and --backend-only are available when the defaults do not fit.

  4. Verify health, bootstrap protection, sign-in, media privacy, and a working backup, as in the standard install. The installer prints the location of the one-time bootstrap token; delete that file after the first owner account is created.

HTTPS without a certificate authority

Public ACME issuance is impossible without egress. Use a non-public site address (an internal DNS name or localhost); the bundled web server then issues certificates from its own local CA with no network. Distribute that local CA root certificate to each family device once, or supply your own certificate from an internal CA you already operate. Do not publish plain HTTP instead.

Transported update

Updates follow the same lifecycle as every ApiaryLens deployment (discover → review → preflight → back up → stage → migrate → activate → verify → commit or recover), driven by the new bundle's scripts. The host keeps an append-only update ledger; transitions that skip versions, go backward incompatibly, or follow an interrupted update are refused before anything is touched.

Connected side:

  1. Download the new version's bundle and manifest.
  2. Verify exactly as for install (manifest hash, attestation, member checksums).
  3. Record the verification output.
  4. Copy to transport media and re-hash on the media.

Air-gapped host — copy the tar over, hash-check, and extract it, then run the update:

bundle/scripts/update-airgap.sh --bundle-dir bundle \
  --target /opt/apiarylens --project apiarylens

The update script drives the remaining lifecycle and refuses to proceed past any failed step:

  1. Verify: member checksums plus the compatibility check that the currently installed version is within the bundle's supported upgrade range (a channel change additionally requires the explicit --allow-channel-change opt-in).
  2. Preflight: architecture, Compose v2, and no interrupted prior update in the ledger.
  3. Back up first with the bundled backup procedure and verify the archive. The update does not proceed past this step without a verified backup.
  4. Stage: load the new images, enforce the post-load identity gate, unpack the release directory, prepare the new configuration — the running deployment is untouched so far.
  5. Migrate: the database migration runs as a one-shot container with networking disabled, before any service is replaced. A failed migration stops here, with services still on the old version and the backup available.
  6. Activate the new release with the air-gap override (--no-build), and verify /health reports the new release identity.
  7. Commit on success — the ledger records the transition and the previous release and images are retained for the rollback window. On failure, the previous release is reactivated; if the migration was incompatible with it, restore the step-3 backup instead (restore revokes all sessions).

Backup, restore, and uninstall

The bundled backup.sh, restore.sh, restore-test.sh, rollback.sh, and teardown.sh are the standard Compose lifecycle procedures with the helper image preloaded, so they work without registry access — see the Operations Guide backup procedure and uninstall section.

Keep at least one verified backup off the host, and keep the previous bundle on your transport media until the new version has passed its rollback window.

Was this page helpful?

Ask for help or suggest a documentation improvement in the public project.

Open an issue