Authoritative project documentation
Air-gap install and transported update
Tested against
apiarylens-0.1.0-preview.6-airgap-b07141c0b494.tarfrom the publishedv0.1.0-preview.6release 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, nodaemon.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 againstSHA256SUMSand the release manifest, keyless attestation viagh attestation verify),verify-bundle.sh,install-airgap.sh(image load, post-load identity gate, network-isolated migration to head0004, offline--no-buildactivation),/healthreporting the exact release identityApiaryLens@0.1.0-preview.6+8d5d4ff, a verifiedbackup.sharchive, and a passingrestore-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. Usev0.1.0-preview.6or 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)
- Download from the release page: the air-gap bundle, the
SHA256SUMSfile, and the release manifest (release-manifest.json). - 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 extractchecksums.sha256and runsha256sum -cover every member. This connected-side check is the trust boundary — the air-gapped side re-verifies integrity, not provenance. - Record the verification output with your deployment records.
- 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
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.Verify the bundle before touching anything:
bundle/scripts/verify-bundle.sh --bundle-dir bundleThis checks every member against
checksums.sha256and evaluates the compatibility manifest. Stop on any failure.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-fileand--auth-root-secret-file); stages the release under/opt/apiarylens/releases/<version>; loads the images withdocker loadand enforces the post-load identity gate described above; runs the database migration as a one-shot container with--network nonebefore any service starts; activates the release through the air-gap override with--no-build; verifies/healthreports the exact release identity; and commits the install to the append-only update ledger.--http-port,--https-port, and--backend-onlyare available when the defaults do not fit.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:
- Download the new version's bundle and manifest.
- Verify exactly as for install (manifest hash, attestation, member checksums).
- Record the verification output.
- 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:
- 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-changeopt-in). - Preflight: architecture, Compose v2, and no interrupted prior update in the ledger.
- Back up first with the bundled backup procedure and verify the archive. The update does not proceed past this step without a verified backup.
- 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.
- 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.
- Activate the new release with the air-gap override (
--no-build), and verify/healthreports the new release identity. - 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.