Air-gapped installation
Images and CVE feeds with no internet access
Edit this pageOn this page
Loomscope is designed to run with no internet access. Nothing phones home: no telemetry, no licence check, no update ping. Two things normally reach out, and both have an offline path.
What needs carrying across
| Thing | Why it reaches out | Offline path |
|---|---|---|
| Container images | Pulled from ghcr.io | docker save / docker load |
| CVE feeds | OSV and NVD advisories | A mirror directory, populated on a connected host |
That is the whole list. The AI assistant would be a third, which is why it is off by default — and why the Ollama provider exists, so an air-gapped deployment that wants an assistant can have one without a network egress.
Container images
On a connected machine:
VERSION=0.13.1
for img in server daemon cve-ingest; do
docker pull ghcr.io/bendaamerahmed/loomscope-$img:$VERSION
done
docker pull postgres:17-alpine
docker save \
ghcr.io/bendaamerahmed/loomscope-server:$VERSION \
ghcr.io/bendaamerahmed/loomscope-daemon:$VERSION \
ghcr.io/bendaamerahmed/loomscope-cve-ingest:$VERSION \
postgres:17-alpine \
-o loomscope-$VERSION-images.tarCarry the tar across, then:
docker load -i loomscope-0.13.1-images.tarPin the version in your compose file to the one you loaded. A moving tag in an air-gapped install is worse than useless: it will fail to pull rather than silently upgrading, but it will fail at the least convenient moment.
The CVE mirror
The ingest worker reads a local directory given by LOOMSCOPE_CVE_MIRROR
instead of fetching from the network.
Build it on a connected machine:
./scripts/airgap-cve-snapshot.sh /var/tmp/cve-mirrorThe script fetches OSV and NVD data and packages it with a checksum. Copy the result to the air-gapped host and point the worker at it:
LOOMSCOPE_CVE_MIRROR=/var/lib/loomscope/cve-mirrorIn the compose stack this is a named volume, cve-mirror, so populating it
means copying into the volume rather than editing a bind path.
Freshness is now your problem
This is the honest part. If the mirror is stale, matches are stale, and nothing in the interface will look wrong — the vulnerability list will show findings, the counts will be plausible, and none of it will include anything published since the last copy in.
Two habits make that visible:
- Check the ingest worker's last successful sync. A mirror that has not been refreshed in a month is a month of advisories you have not seen.
- Copy on a schedule you actually keep, and record the date somewhere a human reads.
The coverage check on the vulnerabilities page reports how old the advisory data is, precisely so this does not have to be remembered.
Installing with no registry at all
Nothing else in the stack fetches anything at runtime. The control plane serves its own assets, migrations are files in the image, and the daemon loads signatures from the image plus whatever YAML you mounted.
The one thing to check is that your compose file has no build: stanzas
pointing at Dockerfiles you did not carry across, and no :latest anywhere.
Updating an air-gapped install
The same procedure as any upgrade, with the transfer added:
- Read the changelog for the target version.
- Back up the database and confirm you hold
LOOMSCOPE_KMS_KEY. See Backup and restore. - Save, carry and load the new images.
- Stop the daemons — they tolerate a control plane that is briefly away.
- Start the new control plane, run migrations, then start the daemons.
Migrations follow expand-and-contract, so a new schema stays readable by the previous release. Upgrade one minor version at a time; skipping several is not tested.
What is still manual
There is no single bundling script that produces one air-gap archive
containing images, chart and mirror together. infra/AIRGAP.md in the
repository covers both transfers in full, and the roadmap tracks the bundle.