Air-Gap Deployment
Air-gap mode deploys the Arbitex Outpost in an environment with no network access to the Arbitex Platform management plane. All configuration — policy bundles, DLP models, and GeoIP databases — is provisioned via local filesystem volumes rather than downloaded at runtime.
Air-gap deployments are used for:
- Classified or air-gapped government networks
- Healthcare environments with strict outbound network controls
- Financial institutions with network segmentation requirements
- Environments where SEC 17a-4 WORM compliance requires isolation from external systems
Enabling Air-Gap Mode
Section titled “Enabling Air-Gap Mode”Set the OUTPOST_AIRGAP environment variable to true:
OUTPOST_AIRGAP=trueWhen air-gap mode is enabled at startup, the Outpost:
- Skips mTLS certificate validation (no Platform connectivity to validate against)
- Skips cert bundle auto-download
- Does not start the background policy sync task
- Does not start the heartbeat sender
- Does not start the cert rotation scheduler
- Loads all configuration from local filesystem volumes
All five disabled capabilities have local-volume equivalents described in this guide.
Policy Bundle Provisioning
Section titled “Policy Bundle Provisioning”Configuration
Section titled “Configuration”OUTPOST_AIRGAP=trueAIRGAP_POLICY_PATH=/opt/arbitex/policies # defaultThe Outpost loads policy_bundle.json from $AIRGAP_POLICY_PATH/policy_bundle.json at startup.
Bundle Format
Section titled “Bundle Format”The policy bundle is a JSON document containing:
- DLP rules (all active rules from the compliance and custom policy configuration)
- OAuth scope enforcement configuration
- Model access controls and risk tier assignments
To generate a policy bundle from the Platform:
# Export current active policy bundle from Platform admin APIcurl https://your-platform/api/v1/admin/policy-bundle/export \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -o policy_bundle.jsonVolume Mount (Kubernetes)
Section titled “Volume Mount (Kubernetes)”env: - name: OUTPOST_AIRGAP value: "true" - name: AIRGAP_POLICY_PATH value: /opt/arbitex/policiesvolumeMounts: - name: policy-bundle mountPath: /opt/arbitex/policies readOnly: truevolumes: - name: policy-bundle configMap: name: arbitex-policy-bundleCreate the ConfigMap from your exported bundle:
kubectl create configmap arbitex-policy-bundle \ --from-file=policy_bundle.json=./policy_bundle.json \ -n arbitexBundle Updates in Air-Gap Mode
Section titled “Bundle Updates in Air-Gap Mode”Since there is no background sync, policy bundle updates require a rolling restart:
- Export a new
policy_bundle.jsonfrom the Platform (or prepare manually). - Update the ConfigMap or volume source.
- Perform a rolling restart of the Outpost deployment.
The Outpost logs the bundle version at startup: air_gap=True policy_bundle version=<version>.
DeBERTa ONNX Model Provisioning
Section titled “DeBERTa ONNX Model Provisioning”The DeBERTa v3 ONNX model provides Tier 3 contextual DLP classification. In air-gap mode, the model is loaded from a local volume rather than downloaded.
Configuration
Section titled “Configuration”OUTPOST_AIRGAP=trueAIRGAP_MODEL_PATH=/opt/arbitex/models # default
# Alternatively, override directly:DEBERTA_MODEL_PATH=/opt/arbitex/models/model.onnxAt startup, the Outpost resolves the model path in this order:
- If
DEBERTA_MODEL_PATHis explicitly set and the file exists, use it directly. - Otherwise, look for
model.onnxin$AIRGAP_MODEL_PATH/model.onnx. - If neither is found, Tier 3 classification is disabled (Tier 1 and Tier 2 DLP remain active).
Volume Mount (Kubernetes)
Section titled “Volume Mount (Kubernetes)”The model file (model.onnx) is large (~500 MB). Use a PersistentVolume rather than a ConfigMap:
env: - name: AIRGAP_MODEL_PATH value: /opt/arbitex/modelsvolumeMounts: - name: deberta-model mountPath: /opt/arbitex/models readOnly: truevolumes: - name: deberta-model persistentVolumeClaim: claimName: arbitex-deberta-model-pvcPre-populate the PVC with the promoted model artifact. The Outpost uses either optimum.onnxruntime (preferred, if available in the container image) or raw onnxruntime.InferenceSession as a fallback loader.
Model Versioning
Section titled “Model Versioning”The model artifact used in air-gap deployments should match the version shipped with the Outpost container image. Check the model version in the startup logs:
DeBERTa Tier3 scanner loaded: model.onnx (label_map={0: 'pii', 1: 'clean'}, threshold=0.7)GeoIP MMDB Provisioning
Section titled “GeoIP MMDB Provisioning”The Outpost uses GeoIP enrichment to annotate audit log entries with country, city, and ISP data from the request IP address. In air-gap mode, the MMDB database is provisioned via volume or baked into the container image.
Resolution Order
Section titled “Resolution Order”The Outpost resolves the GeoIP database in this order:
- Explicit path (
GEOIP_MMDB_PATH): if set and the file exists, use it. - Download on start (
GEOIP_DOWNLOAD_ON_START=true): skipped in air-gap mode — the download step is bypassed entirely whenOUTPOST_AIRGAP=true. - Bundled fallback (
GEOIP_MMDB_FALLBACK_PATH): a pre-baked MMDB baked into the container image or mounted separately. Used when steps 1 and 2 are unavailable.
Configuration for Air-Gap
Section titled “Configuration for Air-Gap”# Option A: explicit volume mountGEOIP_MMDB_PATH=/opt/arbitex/geoip/GeoLite2-City.mmdb
# Option B: use baked-in fallbackGEOIP_MMDB_FALLBACK_PATH=/app/geoip/GeoLite2-City.mmdbIf neither path resolves to a valid file, GeoIP enrichment is disabled and audit records omit geographic fields. This is a graceful degradation — the Outpost continues operating without GeoIP.
Volume Mount (Kubernetes)
Section titled “Volume Mount (Kubernetes)”env: - name: GEOIP_MMDB_PATH value: /opt/arbitex/geoip/GeoLite2-City.mmdbvolumeMounts: - name: geoip-db mountPath: /opt/arbitex/geoip readOnly: truevolumes: - name: geoip-db configMap: name: arbitex-geoipkubectl create configmap arbitex-geoip \ --from-file=GeoLite2-City.mmdb=./GeoLite2-City.mmdb \ -n arbitexNote: MaxMind GeoLite2 databases require a free MaxMind account to download. The anonymous-IP database (GEOIP_ANON_DB_PATH) requires a commercial MaxMind subscription.
SQLite Local Audit Queue
Section titled “SQLite Local Audit Queue”When the Outpost loses connectivity to the Platform audit sync endpoint — or in air-gap mode where no connectivity exists — audit events are written to a local SQLite queue rather than dropped.
Configuration
Section titled “Configuration”AUDIT_QUEUE_DB_PATH=audit_queue/audit_queue.db # defaultThe SQLite database file is created automatically at the configured path. In Kubernetes, mount a PersistentVolume at this path to ensure queue durability across pod restarts.
Queue Behavior
Section titled “Queue Behavior”| Condition | Behavior |
|---|---|
| Platform reachable (circuit CLOSED) | Audit events sent directly to Platform; SQLite queue drained asynchronously |
| Platform unreachable (circuit OPEN) | Audit events written to SQLite queue; no data loss |
| Circuit transitions to CLOSED | Flush task drains queue in 50-event batches to Platform |
The queue stores events as JSON in the local_audit_queue table:
CREATE TABLE local_audit_queue ( id INTEGER PRIMARY KEY AUTOINCREMENT, event_json TEXT NOT NULL, queued_at REAL NOT NULL, retry_count INTEGER NOT NULL DEFAULT 0);Flush-on-Reconnect
Section titled “Flush-on-Reconnect”When connectivity is restored (audit circuit breaker closes), the Outpost automatically flushes the SQLite queue:
_sync_all_pending()detectslocal_queue.count() > 0and circuit stateCLOSED._flush_local_queue()sends batches of 50 events toPOST /v1/internal/outpost-audit-sync.- Successfully delivered events are deleted (
mark_synced(ids)). - Failed events increment their
retry_countfor next flush attempt.
There is no manual flush required. The flush runs automatically on each sync cycle once the circuit closes.
Monitoring Queue Depth
Section titled “Monitoring Queue Depth”Check queue depth via the Outpost admin API:
curl http://localhost:8080/admin/api/sync-status \ -H "Authorization: Bearer $OUTPOST_ADMIN_TOKEN"Response includes audit_queue_depth — the number of events pending flush. A persistently non-zero queue depth in non-air-gap mode indicates connectivity issues with the Platform audit endpoint.
Full Air-Gap Operation
Section titled “Full Air-Gap Operation”In a permanent air-gap deployment (no connectivity ever), the SQLite queue accumulates all audit events indefinitely. To export audit events from a permanently air-gapped Outpost:
- Access the SQLite database file directly (volume mount or kubectl exec).
- Extract events:
sqlite3 audit_queue.db "SELECT event_json FROM local_audit_queue;" - Transfer the extracted events to the Platform via a secure offline transfer process.
Cert Rotation in Offline Mode
Section titled “Cert Rotation in Offline Mode”Standard Cert Rotation (disabled in air-gap)
Section titled “Standard Cert Rotation (disabled in air-gap)”In a connected deployment, the Outpost automatically renews its mTLS certificate 30 days before expiry by calling the Platform cert renewal endpoint. This scheduler is disabled in air-gap mode:
Air-gap mode: cert rotation disabled (no platform connectivity)Manual Cert Rotation for Air-Gap Deployments
Section titled “Manual Cert Rotation for Air-Gap Deployments”In air-gap mode, operators are responsible for certificate rotation:
- Monitor cert expiry: The
/admin/api/sync-statusendpoint returnscert_expiry_days. - Generate replacement certs: Provision a new cert/key pair using your PKI (step-ca, internal CA, or manual).
- Update volume mounts: Replace the cert and key files on the mounted volume.
- Rolling restart: Restart the Outpost pods to load the new certificates.
# Check cert expiry from the Outpost admin APIcurl http://localhost:8080/admin/api/sync-status \ -H "Authorization: Bearer $OUTPOST_ADMIN_TOKEN" | jq '.cert_expiry_days'Set up monitoring alerts when cert_expiry_days drops below your rotation lead time (recommend: 45 days for air-gap environments where restarts require scheduling).
Cert Bundle Configuration
Section titled “Cert Bundle Configuration”In air-gap mode, CERT_BUNDLE_AUTO_DOWNLOAD is implicitly disabled. Configure the cert bundle path directly:
OUTPOST_TLS_CERT=/opt/arbitex/certs/outpost.crtOUTPOST_TLS_KEY=<path-to-outpost-key>OUTPOST_CA_BUNDLE=/opt/arbitex/certs/ca-bundle.crtComplete Air-Gap Configuration Reference
Section titled “Complete Air-Gap Configuration Reference”# === Core air-gap switch ===OUTPOST_AIRGAP=true
# === Policy bundle ===AIRGAP_POLICY_PATH=/opt/arbitex/policies # contains policy_bundle.json
# === DeBERTa model ===AIRGAP_MODEL_PATH=/opt/arbitex/models # contains model.onnx# or: DEBERTA_MODEL_PATH=/opt/arbitex/models/model.onnx
# === GeoIP ===GEOIP_MMDB_PATH=/opt/arbitex/geoip/GeoLite2-City.mmdb# or: GEOIP_MMDB_FALLBACK_PATH=/app/geoip/GeoLite2-City.mmdb
# === Audit queue ===AUDIT_QUEUE_DB_PATH=/opt/arbitex/audit/audit_queue.db
# === Certificates ===OUTPOST_TLS_CERT=/opt/arbitex/certs/outpost.crtOUTPOST_TLS_KEY=<path-to-outpost-key>OUTPOST_CA_BUNDLE=/opt/arbitex/certs/ca-bundle.crt
# === DLP settings ===DLP_DEBERTA_ENABLED=true # enable if model is provisionedDEBERTA_MODEL_PATH=/opt/arbitex/models/model.onnxKubernetes Deployment Template
Section titled “Kubernetes Deployment Template”apiVersion: apps/v1kind: Deploymentmetadata: name: arbitex-outpost-airgap namespace: arbitexspec: replicas: 2 selector: matchLabels: app: arbitex-outpost template: metadata: labels: app: arbitex-outpost spec: containers: - name: outpost image: arbitex/outpost:latest env: - name: OUTPOST_AIRGAP value: "true" - name: AIRGAP_POLICY_PATH value: /opt/arbitex/policies - name: AIRGAP_MODEL_PATH value: /opt/arbitex/models - name: GEOIP_MMDB_PATH value: /opt/arbitex/geoip/GeoLite2-City.mmdb - name: AUDIT_QUEUE_DB_PATH value: /opt/arbitex/audit/audit_queue.db - name: DLP_DEBERTA_ENABLED value: "true" volumeMounts: - name: policy-bundle mountPath: /opt/arbitex/policies readOnly: true - name: deberta-model mountPath: /opt/arbitex/models readOnly: true - name: geoip-db mountPath: /opt/arbitex/geoip readOnly: true - name: audit-queue mountPath: /opt/arbitex/audit - name: tls-certs mountPath: /opt/arbitex/certs readOnly: true volumes: - name: policy-bundle configMap: name: arbitex-policy-bundle - name: deberta-model persistentVolumeClaim: claimName: arbitex-deberta-model-pvc - name: geoip-db configMap: name: arbitex-geoip - name: audit-queue persistentVolumeClaim: claimName: arbitex-audit-queue-pvc - name: tls-certs secret: secretName: arbitex-outpost-tlsStartup Validation
Section titled “Startup Validation”At startup, the Outpost logs its air-gap configuration summary:
INFO Air-gap mode enabledINFO Policy bundle loaded: version=2026-03-13, rules=47INFO DeBERTa Tier3 scanner loaded: model.onnx (threshold=0.7)INFO GeoIP: city database loaded (path=/opt/arbitex/geoip/GeoLite2-City.mmdb)INFO Audit queue: SQLite backend at /opt/arbitex/audit/audit_queue.dbINFO Air-gap mode: cert rotation disabled (no platform connectivity)INFO Air-gap mode: heartbeat disabled (no platform connectivity)INFO Air-gap mode: policy sync disabled (no platform connectivity)If any required volume is missing or the policy bundle fails to parse, the Outpost logs an error and continues with the subsystem disabled (graceful degradation) — it does not abort startup.
Feature Availability in Air-Gap Mode
Section titled “Feature Availability in Air-Gap Mode”| Feature | Air-Gap Support | Notes |
|---|---|---|
| DLP scanning (Tier 1 + Tier 2) | Full | Pattern matching and compliance bundles |
| DLP scanning (Tier 3 DeBERTa) | Full | Requires model volume mount |
| GeoIP enrichment | Full | Requires MMDB volume mount |
| Audit logging | Full | Events stored in SQLite queue |
| Policy enforcement | Full | Loaded from policy_bundle.json |
| mTLS cert rotation | Manual only | No automatic rotation |
| Platform heartbeat | Disabled | No connectivity to Platform |
| CredInt breach lookup | Disabled | No connectivity to CredInt microservice |
| Policy bundle sync | Manual only | Rolling restart required for updates |
Network Requirements
Section titled “Network Requirements”Understanding what requires external connectivity versus what operates fully offline prevents configuration surprises when bringing an air-gapped Outpost online.
No connectivity required (fully air-gapped)
Section titled “No connectivity required (fully air-gapped)”These capabilities load from local volumes and require zero egress:
| Capability | Source |
|---|---|
| DLP scanning (all tiers) | Local volume mounts |
| Policy enforcement | policy_bundle.json on local volume |
| GeoIP enrichment | MMDB file on local volume |
| Audit logging | SQLite queue — no egress needed |
Prometheus /metrics scrape |
Always available, pull-only |
| Pushgateway metrics push | Works to an internal Pushgateway |
| OTel trace export | Works to an internal OTel Collector |
| SIEM direct sink | Works to internal syslog or Splunk HEC |
| Local audit log export | Writes to a local mounted volume |
Requires platform connectivity (disabled in air-gap)
Section titled “Requires platform connectivity (disabled in air-gap)”These capabilities are disabled when OUTPOST_AIRGAP=true and cannot be re-enabled without platform network access:
- Background policy sync (runs every 60 seconds in connected mode)
- Heartbeat sender
- Cert rotation scheduler
- Cert bundle auto-download
- GeoIP MMDB download on start
- CredInt CDN bloom filter refresh
- Audit sync to Platform
- Platform management URL validation
Optional internal network services
Section titled “Optional internal network services”These services are disabled by default. When your air-gapped network includes them, you can opt in with the corresponding config keys:
| Service | Purpose | Config key |
|---|---|---|
| Prometheus server | Scrapes /metrics endpoint |
None — always on |
| Pushgateway | Receives metrics push | PUSHGATEWAY_ENABLED=true |
| OTel Collector | Receives trace/span export | OTEL_ENABLED=true |
| Syslog server or Splunk HEC | Receives audit export | SIEM_DIRECT_ENABLED=true |
Software Updates in Air-Gap Mode
Section titled “Software Updates in Air-Gap Mode”Online update flow (unavailable in air-gap)
Section titled “Online update flow (unavailable in air-gap)”In a connected deployment, the admin API drives updates automatically:
# Check for new release (requires SOFTWARE_UPDATE_RELEASE_URL)POST /admin/updates/check
# Download and stage bundle + .sig, verify Ed25519 or HMAC-SHA256POST /admin/updates/downloadSetting SOFTWARE_UPDATE_RELEASE_URL="" (empty string) disables the online check. This is the correct setting for air-gap deployments.
Air-gap update procedure
Section titled “Air-gap update procedure”Updates in an air-gapped environment use a signed-bundle workflow: download and verify on a connected machine, transfer via approved removable media, then trigger local verification inside the air-gap.
Step 1 — Download the release bundle on a connected machine.
Obtain the versioned bundle and its detached signature from the Arbitex releases page:
outpost-X.Y.Z.tar.gzoutpost-X.Y.Z.tar.gz.sigStep 2 — Verify the Ed25519 signature on the connected machine.
Before transferring, confirm the bundle is authentic:
# Decode the base64-encoded Ed25519 public key from your Outpost configecho "$SOFTWARE_UPDATE_ED25519_KEY" | base64 -d > outpost-pubkey.bin
# Verify (requires openssl 3.x or a compatible Ed25519 tool)openssl pkeyutl -verify \ -pubin -inkey outpost-pubkey.bin \ -sigfile outpost-X.Y.Z.tar.gz.sig \ -in <(sha256sum outpost-X.Y.Z.tar.gz | awk '{print $1}')Alternatively, use the Outpost CLI verify command if available in your toolchain.
Step 3 — Transfer bundle and signature to the air-gapped environment via your approved removable media process.
Step 4 — Place both files in the stage directory.
# Override with SOFTWARE_UPDATE_STAGE_DIRcp outpost-X.Y.Z.tar.gz /tmp/outpost-update-stage/cp outpost-X.Y.Z.tar.gz.sig /tmp/outpost-update-stage/Step 5 — Trigger local check via the admin API.
curl -X POST http://localhost:8080/admin/updates/check_local \ -H "Authorization: Bearer $OUTPOST_ADMIN_TOKEN"Step 6 — Inspect the response.
A successful verification returns "status": "STAGED". The Outpost verifies the Ed25519 signature internally using cryptography.hazmat.primitives.asymmetric.ed25519 and extracts the bundle.
If signature verification fails:
{ "status": "VERIFY_FAILED", "reason": "signature mismatch" }The bundle is not staged and the running Outpost is not affected. Re-download and re-transfer the bundle.
Step 7 — Restart the Outpost to apply the update.
kubectl rollout restart deployment/arbitex-outpost-airgap -n arbitexSoftware update configuration reference
Section titled “Software update configuration reference”SOFTWARE_UPDATE_RELEASE_URL="" # empty = online updates disabledSOFTWARE_UPDATE_ED25519_KEY=<base64-key> # base64-encoded 32-byte Ed25519 public keySOFTWARE_UPDATE_SIGNING_KEY=<base64-key> # alias for ED25519_KEY (either accepted)SOFTWARE_UPDATE_STAGE_DIR=/tmp/outpost-update-stageMonitoring in Air-Gap Mode
Section titled “Monitoring in Air-Gap Mode”The Outpost exposes multiple independent monitoring integration points. All of them work in air-gap mode when their target service is inside the same isolated network.
Prometheus scrape (always available)
Section titled “Prometheus scrape (always available)”The /metrics endpoint serves Prometheus text-format metrics. It requires no external dependencies and is always active regardless of air-gap state. A Prometheus server inside the air-gapped network can scrape it directly.
# prometheus.yml (inside the air-gapped network)scrape_configs: - job_name: arbitex-outpost static_configs: - targets: ['outpost:8080'] scrape_interval: 15sAir-gap-relevant metrics to monitor:
| Metric | Description |
|---|---|
outpost_airgap_bundle_age_seconds |
Age of the loaded policy bundle |
outpost_dlp_scan_duration_seconds |
Per-tier DLP scan latency |
outpost_policy_eval_total |
Policy evaluation counters |
outpost_cert_expiry_seconds |
Seconds until TLS cert expiry |
outpost_audit_queue_depth |
Events pending export from SQLite |
Pushgateway (optional)
Section titled “Pushgateway (optional)”When your air-gapped network includes a Prometheus Pushgateway, the Outpost can push metrics to it on a configurable interval:
PUSHGATEWAY_ENABLED=truePUSHGATEWAY_URL=http://pushgateway:9091PUSHGATEWAY_JOB=outpost # defaultPUSHGATEWAY_INTERVAL_SECONDS=60 # defaultPUSHGATEWAY_INSTANCE= # defaults to hostnameThe Pushgateway exporter includes a circuit breaker: five consecutive push failures open the breaker for a 300-second recovery window before retrying.
OpenTelemetry trace export (optional)
Section titled “OpenTelemetry trace export (optional)”When your air-gapped network includes an OTel Collector, the Outpost exports spans via OTLP gRPC:
OTEL_ENABLED=trueOTEL_EXPORTER_ENDPOINT=http://otel-collector:4317OTEL_SERVICE_NAME=arbitex-outpost # defaultWhen OTEL_ENABLED=false (the default), the Outpost uses a no-op tracer with zero overhead. Enabling tracing requires an OTel Collector reachable within the air-gapped network. There is no external OTel endpoint to configure for air-gap deployments.
Local audit log export (optional)
Section titled “Local audit log export (optional)”The Outpost can write rotating JSONL or CSV audit log files to a local volume for ingestion by a log aggregator running inside the air-gapped network:
LOG_EXPORT_ENABLED=trueLOG_EXPORT_PATH=/var/log/outpost/audit/ # defaultLOG_EXPORT_FORMAT=jsonl # or: csvLOG_EXPORT_ROTATION_MB=100LOG_EXPORT_MAX_FILES=10Mount a PersistentVolume at LOG_EXPORT_PATH to retain logs across pod restarts. A local log aggregator (Fluentd, Filebeat, etc.) can tail the JSONL files directly.
SIEM direct sink (optional)
Section titled “SIEM direct sink (optional)”The Outpost can write audit events directly to an internal syslog server or Splunk HEC endpoint:
SIEM_DIRECT_ENABLED=trueSIEM_DIRECT_TYPE=syslog # or: splunk_hecSIEM_DIRECT_URL=udp://syslog:514Both syslog and Splunk HEC targets operate entirely within the air-gapped network — no internet egress is required.
Monitoring capability summary
Section titled “Monitoring capability summary”| Feature | Default | Air-Gap Behavior | Config key |
|---|---|---|---|
Prometheus /metrics |
Always on | No change | None needed |
| Pushgateway push | Off | Works with internal Pushgateway | PUSHGATEWAY_ENABLED=true |
| OTel traces | Off | Works with internal OTel Collector | OTEL_ENABLED=true |
| Local log export | Off | Writes to local volume | LOG_EXPORT_ENABLED=true |
| SIEM direct sink | Off | Works with internal SIEM | SIEM_DIRECT_ENABLED=true |
| Heartbeat to Platform | On (connected) | Disabled — cannot enable | N/A |
CredInt in Air-Gap Mode
Section titled “CredInt in Air-Gap Mode”Credential Intelligence (CredInt) uses a bloom filter to check bearer tokens and API keys against a breach corpus. In a connected deployment, the bloom filter is refreshed from the CredInt CDN periodically. In air-gap mode, you provision the bloom filter file manually.
Configuration
Section titled “Configuration”CREDINT_ENABLED=trueCREDINT_BLOOM_PATH=/opt/arbitex/credint/corpus.bf # required in air-gapCREDINT_CDN_URL="" # empty = no CDN refreshCREDINT_REFRESH_INTERVAL_SECONDS=86400 # irrelevant when CDN URL is emptyProvisioning the bloom filter
Section titled “Provisioning the bloom filter”- On a connected machine, download the latest bloom filter file from the Arbitex CredInt distribution endpoint.
- Verify the file integrity using the SHA256 checksum provided alongside the download.
- Transfer the
.bffile to the air-gapped environment via approved removable media. - Place the file at the path configured in
CREDINT_BLOOM_PATH. - Restart the Outpost to load the new bloom filter.
The bloom filter is not auto-updated in air-gap mode. Operators must repeat this procedure periodically to keep the breach corpus current. Arbitex releases updated bloom filter files on a regular schedule — check the release notes for the recommended refresh cadence.
Degraded behavior when bloom filter is unavailable
Section titled “Degraded behavior when bloom filter is unavailable”If CREDINT_BLOOM_PATH is empty or the file is not found at startup, CredInt disables gracefully:
WARNING CredInt bloom filter not found at /opt/arbitex/credint/corpus.bf — Tier 4 scanning inactiveTier 1, Tier 2, and Tier 3 DLP scanning remain fully active. Only Tier 4 credential breach detection is affected.
Troubleshooting
Section titled “Troubleshooting”Policy bundle not loading
Section titled “Policy bundle not loading”Symptom: WARNING Policy bundle not found at /opt/arbitex/policies/policy_bundle.json
Cause: The volume is not mounted, the mount path does not match AIRGAP_POLICY_PATH, or the file was not placed at the correct path inside the volume.
Resolution:
# Verify the env varkubectl exec -n arbitex deploy/arbitex-outpost-airgap -- \ env | grep AIRGAP_POLICY_PATH
# Verify the file is present inside the podkubectl exec -n arbitex deploy/arbitex-outpost-airgap -- \ ls -la /opt/arbitex/policies/
# Verify the ConfigMap contains the bundlekubectl get configmap arbitex-policy-bundle -n arbitex -o yaml | head -20DeBERTa model not loading
Section titled “DeBERTa model not loading”Symptom: WARNING DeBERTa model not found, Tier 3 disabled
Cause: model.onnx is not present at the resolved path, or onnxruntime is not installed in the container image.
Resolution:
# Check which path the Outpost is looking atkubectl exec -n arbitex deploy/arbitex-outpost-airgap -- \ env | grep -E 'AIRGAP_MODEL_PATH|DEBERTA_MODEL_PATH'
# Verify the file exists on the mounted volumekubectl exec -n arbitex deploy/arbitex-outpost-airgap -- \ ls -lh /opt/arbitex/models/
# Confirm onnxruntime is installed in the imagekubectl exec -n arbitex deploy/arbitex-outpost-airgap -- \ python -c "import onnxruntime; print(onnxruntime.__version__)"Tier 1 and Tier 2 DLP remain active if Tier 3 fails to load. This is a graceful degradation, not a startup failure.
GeoIP enrichment disabled
Section titled “GeoIP enrichment disabled”Symptom: WARNING GeoIP: no valid MMDB found, enrichment disabled
Cause: No MMDB file was found at any tier of the resolution chain (GEOIP_MMDB_PATH, download-on-start, GEOIP_MMDB_FALLBACK_PATH).
Resolution:
# Check configured pathskubectl exec -n arbitex deploy/arbitex-outpost-airgap -- \ env | grep GEOIP
# Verify the MMDB file is accessiblekubectl exec -n arbitex deploy/arbitex-outpost-airgap -- \ ls -lh /opt/arbitex/geoip/GeoIP enrichment is optional. The Outpost continues operating without it — audit records simply omit geographic fields.
Certificate expiry
Section titled “Certificate expiry”Symptom: cert_expiry_days is negative or near zero in /admin/api/sync-status.
Cause: Manual cert rotation was not performed before expiry. In air-gap mode there is no automatic rotation scheduler.
Resolution:
# Check current cert expirycurl http://localhost:8080/admin/api/sync-status \ -H "Authorization: Bearer $OUTPOST_ADMIN_TOKEN" | jq '.cert_expiry_days'
# After replacing cert/key files on the volume:kubectl rollout restart deployment/arbitex-outpost-airgap -n arbitexSet up a monitoring alert when cert_expiry_days drops below 45 days to ensure sufficient lead time for scheduling maintenance in air-gapped environments.
Audit queue growing indefinitely
Section titled “Audit queue growing indefinitely”Symptom: audit_queue_depth reported by /admin/api/sync-status increases continuously and never drains.
Cause: In a permanent air-gap deployment there is no Platform connectivity to drain the queue to. This is expected behavior — the SQLite queue is the durable buffer.
Resolution options:
- Enable local log export to drain events to a file-based sink:
LOG_EXPORT_ENABLED=trueLOG_EXPORT_PATH=/var/log/outpost/audit/LOG_EXPORT_FORMAT=jsonl- Periodically extract events from the SQLite database directly:
kubectl exec -n arbitex deploy/arbitex-outpost-airgap -- \ sqlite3 /opt/arbitex/audit/audit_queue.db \ "SELECT event_json FROM local_audit_queue;" > audit-export.jsonlTransfer the extracted file to the Platform via your approved offline transfer process.
Software update signature failure
Section titled “Software update signature failure”Symptom: POST /admin/updates/check_local returns "status": "VERIFY_FAILED".
Cause: The signing key configured in SOFTWARE_UPDATE_ED25519_KEY does not match the key used to sign the bundle, or the bundle was corrupted during transfer.
Resolution:
# Verify the bundle checksum on the transfer medium before copyingsha256sum outpost-X.Y.Z.tar.gz
# Compare against the published SHA256 checksum from the Arbitex release page# Re-download if they do not match
# Confirm the correct public key is configuredkubectl get secret arbitex-outpost-config -n arbitex -o jsonpath='{.data.SOFTWARE_UPDATE_ED25519_KEY}' \ | base64 -dRe-download the bundle on the connected machine, verify the checksum before physical transfer, and retry check_local.
HMAC verification failure on policy bundle
Section titled “HMAC verification failure on policy bundle”Symptom: ERROR Policy bundle HMAC verification failed
Cause: Either the POLICY_HMAC_KEY environment variable does not match the key used when the bundle was generated, or the bundle was generated more than BUNDLE_MAX_AGE_SECONDS (default: 3600 seconds) ago and has exceeded its validity window.
Resolution:
For air-gap deployments where the bundle is generated offline and age is not a meaningful constraint, disable HMAC verification:
POLICY_BUNDLE_VERIFY=falseAlternatively, regenerate the bundle with the correct key and ensure the bundle is transferred and loaded within the configured BUNDLE_MAX_AGE_SECONDS window. For development and test environments only:
INSECURE_SKIP_HMAC=true # dev/test only — do not use in productionRelated
Section titled “Related”- Outpost Deployment (Kubernetes) — standard connected deployment guide
- Outpost Resilience — circuit breaker and degradation behavior
- Compliance Frameworks — policy bundles for regulatory compliance
- Credential Intelligence — CredInt breach corpus (unavailable in air-gap)
- Distributed Tracing — OTel tracing configuration for air-gapped environments
Building the Air-Gap Package
Section titled “Building the Air-Gap Package”The scripts/make-airgap.sh script runs on a machine with internet access and produces a self-contained tarball ready for transfer to the isolated host.
Prerequisites (build machine)
Section titled “Prerequisites (build machine)”- Docker Engine 24 or later with Compose V2 (
docker compose version) - Access to the
arbitex-outpostrepository - (Optional) A MaxMind GeoLite2-City MMDB file at
geoip/GeoLite2-City.mmdb— required for GeoIP-based routing and compliance features
Prerequisites (air-gapped target host)
Section titled “Prerequisites (air-gapped target host)”- Docker Engine 24 or later with Compose V2
- A user account in the
dockergroup, or root - systemd (optional — for automatic startup on boot)
- Sufficient disk space: the package tarball is approximately 5–6 GB depending on configuration
- Sufficient RAM: 2 GB minimum; 4 GB recommended for Tier 3 (DeBERTa) DLP
mTLS certificates
Section titled “mTLS certificates”Before the Outpost can connect to the management plane, obtain three certificate files from Arbitex Cloud when you register the outpost:
| File | Purpose |
|---|---|
outpost.pem |
Outpost mTLS client certificate |
outpost.key |
Private key for the client certificate |
ca.pem |
Arbitex CA certificate for server verification |
Register the outpost in the Arbitex admin console and download the certificate bundle before starting the installation.
Running the build script
Section titled “Running the build script”On the internet-connected build machine, from the arbitex-outpost project root:
bash scripts/make-airgap.shTo build a specific version:
bash scripts/make-airgap.sh 1.2.0If no version is provided, the script uses the current git tag if one exists on HEAD, or falls back to the current date (YYYYMMDD).
The script automatically:
- Verifies Docker Engine 24+ and Compose V2 are available
- Builds the CPU Docker image:
arbitex/outpost:<VERSION> - Builds the GPU Docker image:
arbitex/outpost:<VERSION>-gpu(--build-arg INFERENCE_MODE=gpu) - Saves both images to a single compressed tarball:
dist/outpost-image-<VERSION>.tar.gz - Stages compose file,
.env.example,airgap-install.sh(renamedinstall.sh), Helm chart, default policy bundle bootstrap, and the GeoIP MMDB (if present) - Generates
image.sha256checksum for the image tarball - Archives everything into
dist/arbitex-outpost-airgap-<VERSION>.tar.gz - Writes a SHA-256 checksum file alongside the tarball
Output files:
| File | Description |
|---|---|
dist/arbitex-outpost-airgap-<VERSION>.tar.gz |
Full air-gap package — transfer this to the target host |
dist/arbitex-outpost-airgap-<VERSION>.tar.gz.sha256 |
SHA-256 checksum — transfer alongside the tarball |
Transferring the package
Section titled “Transferring the package”scp dist/arbitex-outpost-airgap-<VERSION>.tar.gz \ dist/arbitex-outpost-airgap-<VERSION>.tar.gz.sha256 \ user@target-host:/tmp/Installing on the Air-Gapped Host
Section titled “Installing on the Air-Gapped Host”On the target host, verify the package integrity before proceeding:
cd /tmpsha256sum -c arbitex-outpost-airgap-<VERSION>.tar.gz.sha256Expected output: arbitex-outpost-airgap-<VERSION>.tar.gz: OK. If verification fails, do not proceed.
Extract and run the installer:
tar -xzf arbitex-outpost-airgap-<VERSION>.tar.gzcd airgap-<VERSION>/bash install.shInstaller walkthrough
Section titled “Installer walkthrough”Checksum validation — the installer re-verifies the Docker image tarball checksum against the embedded image.sha256 file. If verification fails, installation stops.
Docker image load — both the CPU and GPU images are loaded into the local Docker daemon from the tarball (docker load). No network access is required. This step may take 2–5 minutes.
Configuration prompts — the installer prompts for the following values. Pre-set them as environment variables for unattended installation (see below).
| Prompt | Environment variable | Required | Notes |
|---|---|---|---|
| Outpost ID | OUTPOST_ID |
Yes | UUID issued at outpost registration (Arbitex admin console) |
| Platform URL | PLATFORM_MANAGEMENT_URL |
No | Defaults to https://api.arbitex.ai |
| Audit HMAC key | AUDIT_HMAC_KEY |
Yes | Long random string; generate with openssl rand -hex 32 |
| Emergency admin key | OUTPOST_EMERGENCY_ADMIN_KEY |
No | Local admin access when management plane is unreachable |
| GPU mode | GPU_MODE |
No | Enter y to use the GPU image; defaults to CPU |
The audit HMAC key seeds the tamper-evident chain on the local audit log. Retain it — audit log verification requires the original key.
Install directory — default is /opt/arbitex-outpost. Override with INSTALL_DIR.
Policy cache bootstrap — the installer places the included default-policy-bundle.json as a bootstrap bundle so the Outpost can start without network access. Until the Outpost reaches the management plane, no DLP rules or provider routing are active. Add mTLS certificates before allowing user traffic.
GeoIP database — if GeoLite2-City.mmdb was included in the package, the installer copies it to ${INSTALL_DIR}/geoip/ and sets MAXMIND_DB_PATH=/app/geoip/GeoLite2-City.mmdb in .env. If absent, a warning is printed and GeoIP features are disabled.
Runtime directory creation — the installer creates three required directories:
| Directory | Purpose |
|---|---|
certs/ |
mTLS certificate files |
audit_buffer/ |
Local HMAC-chained audit log |
policy_cache/ |
Cached policy bundle |
systemd service — if systemd is running, the installer offers to install an arbitex-outpost service for automatic startup on boot (requires root). To skip, answer N; the installer starts the Outpost directly with docker compose up -d.
| Property | Value |
|---|---|
| Service name | arbitex-outpost |
| Service file | /etc/systemd/system/arbitex-outpost.service |
| Start command | docker compose -f docker-compose.outpost.yml up |
| Restart policy | Always, with 10-second delay |
After installation, place the mTLS certificate files and restart:
cp outpost.pem outpost.key ca.pem /opt/arbitex-outpost/certs/sudo systemctl restart arbitex-outpostGPU vs CPU mode
Section titled “GPU vs CPU mode”The package includes both a CPU image and a GPU image. Mode is selected at installation. To change mode after installation, edit .env and the image tag in docker-compose.outpost.yml, then restart.
CPU mode (default): DeBERTa Tier 3 DLP runs on CPU — enable with DLP_DEBERTA_ENABLED=true. CPU DeBERTa inference adds 50–500 ms per request depending on text length.
GPU mode: Requires NVIDIA GPU with CUDA support and the NVIDIA Container Toolkit (nvidia-docker2). Reduces DeBERTa inference to 10–50 ms and enables higher request throughput.
Unattended installation
Section titled “Unattended installation”Pre-set environment variables to skip all interactive prompts:
export OUTPOST_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"export PLATFORM_MANAGEMENT_URL="https://api.arbitex.ai"export AUDIT_HMAC_KEY="$(openssl rand -hex 32)"export OUTPOST_EMERGENCY_ADMIN_KEY="$(openssl rand -hex 24)"export GPU_MODE="N"export INSTALL_DIR="/opt/arbitex-outpost"
bash install.shDay-to-Day Operations (Air-Gap)
Section titled “Day-to-Day Operations (Air-Gap)”Service management
Section titled “Service management”systemd (recommended)
sudo systemctl status arbitex-outpostsudo systemctl start arbitex-outpostsudo systemctl stop arbitex-outpostsudo systemctl restart arbitex-outpost # required after .env changes or cert replacement
# Logsjournalctl -u arbitex-outpost -fjournalctl -u arbitex-outpost -n 200journalctl -u arbitex-outpost --since "2026-03-01 00:00:00"Docker Compose (without systemd)
cd /opt/arbitex-outpostdocker compose -f docker-compose.outpost.yml psdocker compose -f docker-compose.outpost.yml up -ddocker compose -f docker-compose.outpost.yml downdocker compose -f docker-compose.outpost.yml logs -fdocker compose -f docker-compose.outpost.yml restartHealth probes
Section titled “Health probes”curl http://localhost:8300/healthz # liveness — 200 if process is runningcurl http://localhost:8300/readyz # readiness — 200 if policy bundle is loaded; 503 if notLog rotation
Section titled “Log rotation”Application logs (systemd journal) — rotated automatically by journald. To cap journal storage for the Outpost service, set SystemMaxUse=500M in /etc/systemd/journald.conf and restart journald.
Audit buffer — the audit buffer at audit_buffer/audit.jsonl is managed as a ring buffer by the Outpost. The maximum number of entries is set by MAX_AUDIT_BUFFER_ENTRIES (default: 100,000). When the buffer is full, the oldest entries are rotated out automatically.
Monitor buffer size periodically in long-running air-gap deployments:
wc -l /opt/arbitex-outpost/audit_buffer/audit.jsonldu -sh /opt/arbitex-outpost/audit_buffer/audit.jsonlSIEM dead-letter file — if SIEM_DIRECT_DEAD_LETTER_PATH is set, failed SIEM deliveries are appended without rotation. Add an OS-level logrotate entry:
/var/log/arbitex/siem-dead-letter.jsonl { daily rotate 30 compress missingok notifempty copytruncate}Audit log extraction for manual SIEM import
Section titled “Audit log extraction for manual SIEM import”In air-gapped environments without a SIEM direct sink, audit events accumulate in the local buffer. Extract them manually for import into your SIEM system:
cp /opt/arbitex-outpost/audit_buffer/audit.jsonl \ /tmp/arbitex-audit-$(date +%Y%m%d-%H%M%S).jsonlThe file is JSONL format (one JSON object per line). Each entry includes:
| Field | Description |
|---|---|
event_id |
Unique event UUID |
outpost_id |
Outpost identifier |
timestamp |
ISO 8601 event time |
request_id |
Request correlation ID |
user_id |
User who made the request |
model |
Target model |
dlp_action |
DLP outcome: ALLOW, BLOCK, REDACT, PROMPT |
dlp_entities |
List of detected entity types |
hmac |
HMAC-SHA256 chain signature |
prev_hmac |
Previous entry’s HMAC (chain link) |
Verify HMAC chain integrity before importing — see Audit log verification for the full procedure.
Splunk import:
/opt/splunkforwarder/bin/splunk add oneshot /tmp/arbitex-audit-<DATE>.jsonl \ -sourcetype arbitex:outpost:audit \ -index arbitex_auditFor Microsoft Sentinel, use the custom log ingestion API or upload the JSONL file via the Log Analytics workspace data upload feature.
Policy bundle offline update
Section titled “Policy bundle offline update”When the Outpost can temporarily reach the management plane, the PolicySyncClient automatically fetches the latest bundle. To push a specific bundle to an Outpost with no management plane connectivity:
- Export the policy bundle from the cloud portal as a JSON file.
- Transfer it to the air-gapped host.
- Replace the cached bundle:
cp exported-policy-bundle.json /opt/arbitex-outpost/policy_cache/policy_bundle.json- Restart the Outpost to reload the bundle:
sudo systemctl restart arbitex-outpost