RootTrace

Air-gapped installation

Follow this page instead of the install guide when the target host has no internet access. You build a self-contained bundle on a connected staging host, carry it across the boundary, and start the same Compose stack with no registry and no package downloads.

The bundle carries the signed server and agent images (retagged with content-addressed offline names docker load restores), pinned MongoDB and nginx images, the Compose stack and its config files, SBOMs and checksums, and optionally an Elasticsearch image plus collector and APM package mirrors.

The generated configuration selects ROOTTRACE_SECURITY_PROFILE=air_gapped, which disables remote LLM calls and the vendor email relay, prevents image pulls, forces offline embeddings, and makes the application network internal. Startup validates all of that, but validation is not a firewall. Keep a default-deny egress rule at the network boundary and review every endpoint you configure.

1. Prepare and verify on a connected host

Start from a signed deployment archive, not a repository checkout. Open https://packages.roottrace.io/releases/latest.json through the signed-in portal and download the versioned bundleUrl, checksumUrl, signatureUrl, publicKeyUrl, and manifestUrl objects into an empty directory. Verify the archive before extracting it:

sha256sum -c roottrace-deployment-<version>.tar.gz.sha256
cosign verify-blob \
  --key roottrace-cosign.pub \
  --insecure-ignore-tlog=true \
  --signature roottrace-deployment-<version>.tar.gz.sig \
  roottrace-deployment-<version>.tar.gz
tar -xzf roottrace-deployment-<version>.tar.gz
cd roottrace-deployment-<version>

On macOS, use shasum -a 256 -c for the checksum line. Stop if either verification fails.

Take the release number and the immutable image digests from release/release-manifest.json in this verified directory, then pull every image the bundle needs:

RELEASE=REPLACE_WITH_RELEASE
TARGET_PLATFORM=linux/amd64
SERVER_IMAGE='public.ecr.aws/byteaffinity/roottrace/server@sha256:REPLACE_WITH_DIGEST'
AGENT_IMAGE='public.ecr.aws/byteaffinity/roottrace/agent@sha256:REPLACE_WITH_DIGEST'

docker pull --platform "${TARGET_PLATFORM}" "${SERVER_IMAGE}"
docker pull --platform "${TARGET_PLATFORM}" "${AGENT_IMAGE}"
docker pull --platform "${TARGET_PLATFORM}" \
  'mongo:8.0.28-noble@sha256:277f9152905bd1f32d3ece4526e0f90906dc238f7133b24ede8446ac9740b76d'
docker pull --platform "${TARGET_PLATFORM}" \
  'nginx:1.27.5-alpine@sha256:65645c7bb6a0661892a8b03b89d0743208a18dd2f3f17a54ef4b76fb8e2f2a10'

Use linux/arm64 for an ARM 64 target. Every image must resolve to the same architecture: one bundle cannot serve both, and the builder checks this.

Verify the image signatures and SBOM attestations before exporting them:

./verify-images.sh

Server and agent version numbers do not have to match.

2. Build the offline bundle

Run from the extracted, verified deployment directory. Both image variables must be full @sha256: digests, not a numbered or floating tag:

ROOTTRACE_SERVER_IMAGE="${SERVER_IMAGE}" \
ROOTTRACE_AGENT_IMAGE="${AGENT_IMAGE}" \
scripts/build_airgap_bundle.sh "${RELEASE}"

The builder refuses to continue when an image is absent. It does not pull an image on your behalf; the license-verification key is already in the signed server image.

Optional inputs:

# Include a previously pulled, site-approved Elasticsearch image.
export ROOTTRACE_ES_IMAGE='registry.example/elasticsearch@sha256:REPLACE_WITH_DIGEST'

# Include prepared native collector and published APM package mirrors.
export ROOTTRACE_PACKAGE_MIRROR_DIR=/staging/roottrace-collector-packages
export ROOTTRACE_APM_PACKAGES_DIR=/staging/roottrace-apm-packages

Only Python and Node.js APM packages are currently published. Java, PHP, and Go are source-only previews; carry their reviewed source trees separately if the site is evaluating them.

The output name includes the verified platform, for example dist/roottrace-airgap-${RELEASE}-linux-amd64.tar.gz. Record and transfer an outer checksum with the archive:

cd dist
PLATFORM_SLUG=linux-amd64
BUNDLE="roottrace-airgap-${RELEASE}-${PLATFORM_SLUG}.tar.gz"
sha256sum "${BUNDLE}" > "${BUNDLE}.sha256"

Use the site's approved removable-media and chain-of-custody process.

3. Verify and load inside the gap

On the target host:

RELEASE=REPLACE_WITH_RELEASE
PLATFORM_SLUG=linux-amd64
BUNDLE="roottrace-airgap-${RELEASE}-${PLATFORM_SLUG}.tar.gz"
sha256sum -c "${BUNDLE}.sha256"
tar -xzf "${BUNDLE}"
cd "roottrace-airgap-${RELEASE}-${PLATFORM_SLUG}"

cd images
sha256sum -c SHA256SUMS
for archive in *.tar; do
  docker load -i "${archive}"
done
cd ..

On macOS, use shasum -a 256 -c for both checksum files. images/PLATFORM must match the target host. images/IMAGE_REFS.txt records both the original signed digest and the content-addressed offline tag restored by docker load. The generated Compose environment and single-node Kubernetes manifest use those offline tags, so ROOTTRACE_PULL_POLICY=never works without a registry.

For a multi-node Kubernetes cluster, load each archive on every node or push the loaded image into the enclave's private registry. If pushed, resolve the private-registry digest and replace the DaemonSet image with that digest before applying it.

4. Configure

The generated deploy/.env already has the offline image references, ROOTTRACE_PULL_POLICY=never, the air_gapped profile, the disabled LLM, and the internal application network. Leave those alone. They are what keeps the enclave sealed:

ROOTTRACE_SECURITY_PROFILE=air_gapped
ROOTTRACE_APP_NETWORK_INTERNAL=true
ROOTTRACE_PULL_POLICY=never
ROOTTRACE_ON_PREM_EMAIL_RELAY_ENABLED=false
ROOTTRACE_EMBEDDING_OFFLINE=true
ROOTTRACE_LLM_PROVIDER=disabled
ROOTTRACE_EMAIL_SENDING_ENABLED=false
ROOTTRACE_ELASTICSEARCH_HOST=

Fill in the secrets, the public identity block, and TLS material at deploy/certs/ exactly as in step 2 of the install guide:

openssl rand -hex 32 > deploy/secrets/app_secret
chmod 700 deploy/secrets
chmod 600 deploy/secrets/app_secret
chmod 600 deploy/certs/host_key.pem
chmod 600 deploy/.env

Two air-gap specifics:

  • If your license is already bound to an installation id, set ROOTTRACE_INSTALLATION_ID to that exact value. Otherwise leave it empty and use the id RootTrace generates during the offline exchange below.
  • The embedding model ships inside the server image. Leave ROOTTRACE_EMBEDDING_MODEL_PATH unset unless you deliberately mount and test a replacement.

If an internal SMTP, Elasticsearch, LLM, or other integration is needed, attach it through a site-maintained internal network override. It must be reachable from the API container without adding a default route outside the enclave. Enable email only after an internal TLS-protected SMTP relay is reachable:

ROOTTRACE_EMAIL_SENDING_ENABLED=true
ROOTTRACE_SMTP_HOST=smtp.internal.example
ROOTTRACE_SMTP_PORT=587
ROOTTRACE_SMTP_STARTTLS=true

5. Start and verify

Keep the HTTPS listener restricted to the operator's source address until the first owner has enrolled MFA.

cd deploy
docker compose config -q
docker compose up -d --pull never
docker compose ps
docker compose logs --since 10m roottrace

Wait until MongoDB, the API, and nginx report healthy. Verify through the same internal HTTPS name users open:

curl --fail --silent --show-error \
  --cacert /path/to/roottrace-ca.pem \
  https://roottrace.internal.example/readyz

6. First sign-in and the offline license exchange

  1. Create the first owner exactly as in step 5 of the install guide: local username, bootstrap token, then TOTP and recovery codes before you open the listener to its normal audience.
  2. Copy the installation id from the license panel.
  3. Carry that id out to RootTrace or your OEM partner, and bring a signed license token back over your approved offline channel.
  4. Paste the token into the license panel. It verifies locally against the public key inside the signed server image. No network is involved.
  5. Create the first workspace once the license reads valid.

Renew before the displayed expiry date. The installation never phones home to renew.

7. Install collectors and SDKs

Use only artifacts that crossed the boundary with the approved release:

  • Single-host container collectors use the loaded content-addressed offline tag. Multi-node installations use the digest in the site's private registry.
  • Native Linux collectors use the signed dnf/apt repository mirror included through ROOTTRACE_PACKAGE_MIRROR_DIR.
  • Python and Node.js APM clients use the package mirror included through ROOTTRACE_APM_PACKAGES_DIR.

Point every agent at the enclave's own API. Collectors require ROOTTRACE_API_URL and stop without it, but the APM SDKs fall back to RootTrace Cloud, so each instrumented service must set api_url / apiUrl (or ROOTTRACE_API_URL) explicitly. Inside the gap that fallback cannot reach anything; it simply produces failing flushes and outbound attempts against the egress boundary until it is corrected.

The minimal Docker collector command observes its own container. Use a native package or the shipped Kubernetes DaemonSet for host/node telemetry.

Refresh the server, collector image, native package mirror, and published APM package mirror on the site's scheduled release cadence. Verify each new bundle as a separate release.

8. Prove no egress

After setup:

  1. Confirm /readyz and authenticated /metrics work with the external firewall denying egress.
  2. Inspect docker compose config and images/IMAGE_REFS.txt; no runtime image may refer to a public registry pull.
  3. Review every configured URL, SMTP host, webhook, status watch, repository, and model endpoint.
  4. Confirm the site firewall reports no permitted connection from the RootTrace application network to the public internet.
  5. Stop any transparent proxy or DNS forwarding path that could bypass that boundary.

Use the upgrade guide before importing a later bundle.