Skip to content
ABRQ DATADocs Abrq DIP · latest
Product page Request a trial
On this page

Air-Gapped Installation

The release bundle is the offline variant of the Docker Compose path: the same single-host stack, but with pre-built image tarballs shipped inside the archive. Once the bundle is on the server, no internet egress is needed — not at install time and not at runtime.

You receive from your vendor:

  1. The tarball abrq-dip-<VERSION>.tar.gz plus its .sha256.
  2. A signed license.json, issued against your customer name and expiry.

Prerequisites#

Component Minimum
OS Linux x86_64. Tested on Ubuntu 22.04 LTS and RHEL 9.
Docker Engine 24.0+
Docker Compose plugin v2.20+ (docker compose version — the legacy v1 docker-compose binary is not supported)
RAM 4 GB free
Disk 5 GB free under /var/lib/docker; ~2 GB for the install overall
Ports 8080 free on the host (configurable via ABRQ_FRONTEND_PORT)

1. Verify and extract#

shasum -a 256 abrq-dip-<VERSION>.tar.gz

Compare against the value in abrq-dip-<VERSION>.tar.gz.sha256. If the hashes do not match, stop and contact the vendor — the file was corrupted in transit.

tar -xzf abrq-dip-<VERSION>.tar.gz
cd abrq-dip-<VERSION>/

The extracted directory contains docker-compose.yml, .env.example, INSTALL.md, UPGRADE.md, VERSION, images/, and license/.

2. Load the four image tarballs#

docker load -i images/abrq-dip-backend.tar
docker load -i images/abrq-dip-frontend.tar
docker load -i images/postgres-15-alpine.tar
docker load -i images/redis-7-alpine.tar

Each load prints a Loaded image: line — confirm all four before continuing:

docker images | grep -E "abrq-|postgres:15-alpine|redis:7-alpine"

3. Configure .env#

cp .env.example .env
chmod 600 .env

Keys in the bundle's .env template:

Key Preset / default Effect
ABRQ_VERSION pre-filled by the release build with this bundle's version Image tag for every app service. Leave it alone — see the gotcha below.
POSTGRES_PASSWORD you set it Password for the bundled PostgreSQL; also interpolated into the app's connection URLs.
ABRQ_MASTER_KEY you set it Fernet master key — encrypts all stored secrets.
ABRQ_JWT_SECRET you set it Session-token signing secret (32+ chars).
ABRQ_INITIAL_ADMIN_PASSWORD you set it First-boot admin password; forced change at first login.
ABRQ_CORS_ALLOWED_ORIGINS you set it The exact URL your users hit the UI at (comma-separate multiples).
ABRQ_ENABLE_HSTS true HSTS header. Flip to false only for plain-HTTP LAN trials.
ABRQ_ALLOW_INSECURE 0 Set 1 to disable the production plain-HTTP refusal (for example TLS terminated upstream, or a LAN trial).
ABRQ_GUNICORN_WORKERS 2 API worker processes.
ABRQ_CELERY_CONCURRENCY 4 Concurrency per worker container.
ABRQ_WORKER_REPLICAS 2 Worker container count.
ABRQ_FRONTEND_PORT 8080 Host port for the web UI.
ABRQ_AI_REQUEST_TIMEOUT_SECONDS 120 Per-request timeout for the optional customer-managed AI provider.

Secret generation commands are in choosing a deployment path — the bundle's backend image can generate the Fernet key and JWT secret offline:

docker run --rm abrq-dip-backend:<VERSION> python -c \
  "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

4. Place the license#

cp /path/to/your/vendor-supplied/license.json license/license.json

The compose file mounts exactly this single file read-only into the backend, worker, and beat containers at /var/abrq-dip/license.json (mounting the whole directory would shadow /var/abrq-dip and break beat state and the initial-password file — keep the single-file layout).

Hardware-bound licenses and the machine fingerprint#

The compose file also mounts the host's /etc/machine-id read-only. If your license is hardware-bound, the runtime hashes this file (SHA-256) and requires it to match one of the license's fingerprints. To give the vendor your fingerprint before they mint the license, run the bundled helper on the host that will run Abrq DIP:

sh host_fingerprint.sh

Send the printed hash to your vendor contact. Unbound licenses ignore the mount entirely — it is always mounted so a later renewal that adds binding needs no compose change. Details: installing a license.

5. Start and watch the logs#

docker compose up -d
docker compose ps
docker compose logs -f backend

Expected in the backend logs:

license_verified  customer=<CUSTOMER> expires_at=<EXPIRY> days_remaining=NNN
app_started       env=prod
Application startup complete.

If the license check fails the backend exits and the log names the reason — file not found at /var/abrq-dip/license.json, invalid signature (file altered, or the release's embedded key does not match the key that signed your license), or expired. See expiry and clock behaviour.

6. First login#

Open http://<HOST>:8080 (or your ABRQ_FRONTEND_PORT). The bundle's install guide documents the initial username as abrq; the password is your ABRQ_INITIAL_ADMIN_PASSWORD. You are forced to change the password immediately.

7. Smoke test#

curl -fsS http://localhost:8080/api/v1/health | jq

Expected: {"status": "ok", "deps": {"framework_db": "ok", "redis": "ok"}} (plus version fields). A dep reporting down means something inside the stack is broken — docker compose logs framework_db redis backend will surface the cause.

8. Take the first backup#

docker compose exec backend uv run python scripts/backup_metadata.py \
  dump --out /var/abrq-dip/first-backup.json
docker compose cp backend:/var/abrq-dip/first-backup.json ./

Schedule this on your ops cadence (daily or weekly) and store copies off the host. The dump is an encrypted envelope; see backup and restore.

The version-tag gotcha: "pull access denied"#

If docker compose up fails with pull access denied for abrq-dip-backend (or -frontend), it is not an authentication problem. The compose file pins images by ${ABRQ_VERSION}; if that variable is unset or empty the tag resolves to :latest, which docker load never imported (the shipped images are tagged with the version). Compose then falls back to a registry pull and is denied.

Confirm what compose actually resolved:

docker compose config | grep 'image:'

Every app image should show :<VERSION> (for example :1.4.1), never :latest or an empty tag. Make sure you ran cp .env.example .env (the template pre-fills ABRQ_VERSION), or pass it inline:

ABRQ_VERSION=<VERSION> docker compose up -d

Tip. sudo drops shell variables — keep ABRQ_VERSION in .env, not just exported in your shell.

Differences from docker-compose.prod.yml — read before relying on them#

The shipped bundle's compose file differs from the in-repo production compose in two security-relevant ways. These are stated plainly so you can compensate:

Warning. In the release bundle, Redis runs without a password (requirepass is not set), unlike docker-compose.prod.yml, which refuses to start without REDIS_PASSWORD. The bundled Redis publishes no host port, so exposure is limited to the Docker network, but any process that can reach that network reaches an unauthenticated broker.

Warning. The bundle's compose file has no host bind-mount for backups (and no ownership-fixing init step): ADR 0040 auto-backups are written inside the container filesystem and do not survive container removal. Treat the scheduled backup_metadata.py dump + docker compose cp off the host (step 8) as your real backup path on this deployment.

Both are known gaps in the shipped bundle, flagged to the vendor; do not assume the in-repo compose behaviour applies here.

Air-gapped Kubernetes#

For a cluster with no registry egress, the flow is: save images on a connected machine, move the tarball across, load and push into your private registry, and point the chart at it.

On the connected side, the repository ships a helper:

deploy/release/save-images.sh <VERSION>

It saves abrq-dip-backend:<VERSION>, abrq-dip-frontend:<VERSION>, and — only needed if you keep the in-cluster subcharts enabled — the pinned bitnamilegacy PostgreSQL and Redis images into a single abrq-images-<VERSION>.tar.

On the air-gapped side:

docker load -i abrq-images-<VERSION>.tar
docker tag abrq-dip-backend:<VERSION> registry.example.com/abrq/abrq-dip-backend:<VERSION>
docker tag abrq-dip-frontend:<VERSION> registry.example.com/abrq/abrq-dip-frontend:<VERSION>
docker push registry.example.com/abrq/abrq-dip-backend:<VERSION>
docker push registry.example.com/abrq/abrq-dip-frontend:<VERSION>

Then set the chart's image coordinates:

image:
  repository: registry.example.com/abrq/abrq-dip-backend
  tag: "<VERSION>"
frontend:
  image:
    repository: registry.example.com/abrq/abrq-dip-frontend
    tag: "<VERSION>"

The subchart dependencies are vendored as committed tarballs inside the chart, so helm install needs no chart-repository access either.

Warning. The chart currently has no values key for image pull secrets — no template renders imagePullSecrets, so a registry that requires authentication cannot be wired through chart values. This is a known product gap; until it is fixed, use a registry the cluster can pull from without credentials, or attach pull credentials outside the chart through your own cluster tooling.

Note. save-images.sh pins the bitnamilegacy PostgreSQL and Redis image tags in the script itself, and those pins can drift from the tags the chart's subcharts resolve. If you run the in-cluster data tier, verify after install that the running pods use the images you actually pushed (kubectl -n <NAMESPACE> get pods -o jsonpath='{..image}').

Once installed, no path in the system requires internet egress: licensing is fully offline (file-based verification, no phone-home), and the only outbound connections are the ones you configure (connectors, notification channels, an optional AI provider you host).

When a new release arrives, continue with upgrade and rollback.