Skip to main content

Air-Gapped Deployment

This guide covers deploying Infracast in a fully air-gapped environment — a network with no inbound or outbound internet connectivity. This is the standard model for classified networks, defense programs, ICS/OT enclaves, and any environment where egress is prohibited.

Infracast is designed to run completely standalone. All core capabilities — discovery, attack-path analysis, compliance assessment, document generation, SBOM, and reporting — operate without any external service. The only features that depend on the public internet are threat-intelligence feed refreshes (CISA KEV, EPSS, NVD enrichment), and these degrade gracefully and can be fed offline (see Threat Intelligence in Air-Gap).

Already offline-native

License validation is fully offline — Infracast verifies an Ed25519-signed license token locally with an embedded public key and never calls home. There is no telemetry, no activation server, and no usage beacon in a self-hosted deployment.


1. Architecture Overview​

A standalone Infracast deployment has three components, all of which run inside the enclave:

ComponentRoleImage / Artifact
Infracast backendAPI, discovery engine, analysis, doc generationAZgardTek-provided image tarball
PostgreSQL 16All persistent state (nodes, findings, reports, evidence)postgres:16-alpine
Infracast UIStatic React SPA, built into the backend image and served by it (same-origin)Included in the backend image: no separate artifact

Optional, also fully in-enclave:

  • On-prem Relay (relay/) — for scanning isolated network segments via SSH/SNMP/WinRM/VMware without giving the backend direct reachability. The relay tunnels back to the backend over an internal WebSocket; it needs no internet either.

Everything else — Stripe billing, AWS Marketplace, Bedrock/LLM, cloud-provider discovery plugins — is optional and simply unused when those connectors aren't configured. None of them are on the startup or core-analysis path.

┌──────────────────────────── Air-Gapped Enclave ────────────────────────────┐
│ │
│ [Operator Browser] ──HTTPS──> [Infracast Backend] ──> [PostgreSQL 16] │
│ │ │
│ └──WebSocket──> [On-Prem Relay] │
│ │ │
│ SSH/SNMP/WinRM/VMware │
│ ▼ │
│ [Isolated network segments] │
└─────────────────────────────────────────────────────────────────────────────┘
⛔ No path to the public internet

2. What Must Be Pre-Staged (the "diode crossing")​

Because the enclave has no egress, every artifact must be transferred in once — typically over a one-way data diode, a removable-media transfer, or a cross-domain solution. Pre-stage the following on the high side:

Container images​

The Infracast backend image is provided directly by AZgardTek as a pre-packaged, signed docker save tarball through a private, access-controlled delivery channel (the download link is sent with your license — see section 4). The image is not published to a public registry, and customers do not need access to AZgardTek's source or container registry.

On an internet-connected staging host:

# 1. Infracast backend: download the AZgardTek-provided image tarball + checksum
# (URL delivered with your license). Verify before transferring.
sha256sum -c infracast-backend-<release-tag>.tar.gz.sha256

# 2. PostgreSQL: pull from the public registry and save alongside
docker pull postgres:16-alpine
docker save postgres:16-alpine -o postgres-16-alpine.tar

Transfer infracast-backend-<release-tag>.tar.gz and postgres-16-alpine.tar into the enclave, then load and push to your enclave-internal registry (Harbor, ECR-in-region, Artifactory, or a plain registry:2):

gunzip -c infracast-backend-<release-tag>.tar.gz | docker load
docker load -i postgres-16-alpine.tar
# tag + push into your internal registry, then reference it in compose/Helm

Your compose file must reference the loaded image (image: ...), never a build: section — there is no source to build from, and you don't want a build toolchain or a registry pull on the high side.

Image distribution

AZgardTek does not expose the backend image on a public registry. If your procurement requires a specific delivery mechanism (e.g., physical media, your own artifact store, or a time-limited signed URL), contact AZgardTek and we will accommodate it.

License token​

A signed offline license (license.json) with offline_mode: true. Generated by AZgardTek and transferred as a file. It is Ed25519-signed and validated locally — no callback.

Snapshots of the CISA KEV catalog, EPSS scores, and NVD data, taken on the low side and carried in. See section 5.

UI​

Nothing to transfer: the web UI is built into the backend image and served by it on the same address and port as the API, so a single image is the whole product. (Set INFRACAST_UI_DIR=off to serve the API only.)

If you would rather host the UI on a separate internal web server, ask AZgardTek for the standalone UI bundle (scripts/build-ui-customer-bundle.sh). It carries no API address; set it at deploy time in env-config.js (window._env_ = { VITE_API_URL: 'https://infracast.internal.mil' };).


3. Deployment (Docker Compose Reference)​

A hardened, air-gap-ready compose file. Note: no build: directive, pinned image from the internal registry, secrets via env file, and the license + offline-feed volumes mounted in.

version: '3.8'
services:
infracast:
image: registry.internal/azgardtek/infracast:<release-tag> # internal registry, pinned
ports:
- "8443:8080"
environment:
- INFRACAST_DB_TYPE=postgres
- INFRACAST_DB_URL=postgres://infracast:${DB_PASSWORD}@postgres:5432/infracast
- INFRACAST_JWT_SECRET=${INFRACAST_JWT_SECRET}
- INFRACAST_CREDENTIAL_KEY=${INFRACAST_CREDENTIAL_KEY}
- INFRACAST_ENV=production
- INFRACAST_LOG_LEVEL=info
- INFRACAST_LICENSE_FILE=/etc/infracast/license.json
# Offline threat-intel: point feeds at local files instead of the internet
- INFRACAST_OFFLINE_MODE=true
- INFRACAST_KEV_FILE=/etc/infracast/feeds/known_exploited_vulnerabilities.json
- INFRACAST_EPSS_FILE=/etc/infracast/feeds/epss_scores-current.csv.gz
- INFRACAST_NVD_DIR=/etc/infracast/feeds/nvd/
# Artifact storage (export files today) — see "Artifact storage" below
- INFRACAST_ARTIFACTS_BACKEND=filesystem
- INFRACAST_ARTIFACTS_DIR=/var/lib/infracast/artifacts
- INFRACAST_ARTIFACTS_KEY_PROVIDER=local
- INFRACAST_ARTIFACTS_MASTER_KEY_FILE=/run/secrets/artifacts_master_key
volumes:
- ./plugins:/app/plugins
- ./license.json:/etc/infracast/license.json:ro
- ./feeds:/etc/infracast/feeds:ro
- artifacts_data:/var/lib/infracast/artifacts
- ./artifacts_master_key:/run/secrets/artifacts_master_key:ro
depends_on:
postgres:
condition: service_healthy

postgres:
image: registry.internal/postgres:16-alpine
environment:
POSTGRES_USER: infracast
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: infracast
healthcheck:
test: ["CMD-SHELL", "pg_isready -U infracast"]
interval: 5s
timeout: 5s
retries: 5
volumes:
- postgres_data:/var/lib/postgresql/data

volumes:
postgres_data:
artifacts_data:

Provide secrets via a local .env file (never the defaults):

# .env (chmod 600, do not commit)
DB_PASSWORD=<long-random>
INFRACAST_JWT_SECRET=<32+ byte random>
INFRACAST_CREDENTIAL_KEY=<32+ byte random, different from the JWT secret>

Artifact storage​

Infracast stores generated files (export files today; other generated documents and uploaded evidence are being moved onto the same store) outside the database, encrypted per tenant. Air-gapped installs use the local filesystem and an operator-supplied master key. Nothing falls back automatically: the backend and key provider must be set explicitly, and there is no default for INFRACAST_ARTIFACTS_KEY_PROVIDER.

INFRACAST_ARTIFACTS_BACKEND=filesystem
INFRACAST_ARTIFACTS_DIR=/var/lib/infracast/artifacts # shared storage if more than one replica
INFRACAST_ARTIFACTS_KEY_PROVIDER=local
INFRACAST_ARTIFACTS_MASTER_KEY_FILE=/run/secrets/artifacts_master_key
  • Mount a persistent volume at the artifacts directory. With more than one replica it must be shared storage that every replica can read and write.

  • Generate the master key once (32 random bytes; base64 is accepted):

    umask 077
    openssl rand -base64 32 > artifacts_master_key
    chmod 0600 artifacts_master_key

    The file must be readable only by the service account. Infracast refuses to start if the key file is readable by "other". Keep it out of .env, version control and your data backups.

  • Do not replace the key file once artifacts exist: a different key makes existing tenant keys unreadable. There is no online key-rotation tooling yet; contact support before changing it.

Losing the master key loses every stored artifact

There is no recovery path. Keep at least two copies on separate offline media under your normal key-custody procedure, separately from the artifacts directory and the database backup, and test that you can read the backup back.

S3-compatible storage on-prem. If you run an S3-compatible object store (for example Ceph or RustFS) you can use it instead of the filesystem:

INFRACAST_ARTIFACTS_BACKEND=s3
INFRACAST_ARTIFACTS_S3_BUCKET=infracast-artifacts
INFRACAST_ARTIFACTS_S3_REGION=us-east-1
INFRACAST_ARTIFACTS_S3_ENDPOINT=https://s3.internal.example:9000
INFRACAST_ARTIFACTS_S3_PATH_STYLE=true
INFRACAST_ARTIFACTS_KEY_PROVIDER=local
INFRACAST_ARTIFACTS_MASTER_KEY_FILE=/run/secrets/artifacts_master_key

The master key and the backup rules above apply in the same way.

Tenant deletion destroys the tenant's key and deletes its files; on the air-gapped provider there is no grace period, so take a backup first if you may need to undo it.

Bring it up:

docker compose --env-file .env up -d
docker compose logs -f infracast # confirm migrations ran and license validated

Database migrations run automatically on startup. First boot also creates an initial admin (system-admin) account; change its password immediately — see First Admin Account.

Replace every default

The stock docker-compose.yml ships eval defaults (infracast/infracast, change-me-in-production). These are unacceptable in a production air-gap. Rotate the DB password and JWT secret before first boot.


4. License: Offline Activation​

Infracast uses an offline, Ed25519-signed license. The validator embeds the AZgardTek public key at build time and verifies the token locally:

  1. AZgardTek issues a license.json with your node count, tier, feature flags, expiry, and offline_mode: true.
  2. Transfer the file into the enclave and mount it at INFRACAST_LICENSE_FILE.
  3. On startup the backend verifies the signature against the embedded public key. No network call is made.

If the signature, node count, or expiry don't validate, the backend logs the reason and starts in a restricted state. Node-count enforcement is the sum across all tenants/workspaces, checked locally against the signed limit.

To renew or resize: AZgardTek issues a new signed token; you swap the file and restart. No internet round-trip.


5. Threat Intelligence in an Air-Gapped Environment​

This is the one area that normally relies on the internet. Infracast pulls three feeds:

FeedSource (online)RefreshBehavior offline
CISA KEVcisa.gov KEV catalog JSON24 h TTLFalls back to embedded seed list; background fetch fails silently
EPSSFIRST.org / Cyentia daily CSV24 h TTLEmpty cache → EPSS scoring skipped, no crash
NVD enrichmentservices.nvd.nist.govon-demandUsed for CVE metadata enrichment only

Critical: none of these are on the startup or core-analysis critical path. Discovery, attack-path tracing, compliance, and reporting all work with zero feeds. A failed fetch is logged at WARN and the engine continues. So an air-gapped Infracast is functional out of the box — you just lose live "is this CVE actively exploited / how likely" enrichment until you feed it.

The easiest path is the AZgardTek feed bundle — a daily-packaged infracast-feeds-YYYYMMDD.tar.gz published on our CDN containing only freely-redistributable feeds. Download one bundle on the low side and carry it in:

# Latest bundle (updated daily ~07:30 UTC)
curl -fsSLO https://get.infracast.io/feeds/latest/infracast-feeds-latest.tar.gz
curl -fsSLO https://get.infracast.io/feeds/latest/infracast-feeds-latest.tar.gz.sha256
sha256sum -c <(echo "$(cat infracast-feeds-latest.tar.gz.sha256) infracast-feeds-latest.tar.gz")

# Extract into your mounted feeds dir, then point the env vars at the files:
tar -xzf infracast-feeds-latest.tar.gz
# INFRACAST_KEV_FILE -> known_exploited_vulnerabilities.json
# INFRACAST_EPSS_FILE -> epss_scores-current.csv.gz

Each bundle ships a manifest.json (per-feed checksums + the env var each maps to) and an ATTRIBUTION.txt. Dated bundles are retained for history; latest/ always points at the most recent build.

Feeds in the bundle (all redistributable):

FeedLicense
CISA KEVUS Gov / public domain
EPSS (FIRST.org)Free
NVD (NIST)Public domain
Feodo Tracker (abuse.ch)CC0
Not bundled — bring your own

URLhaus and ThreatFox (abuse.ch) are free under fair use but commercial redistribution may require a paid Spamhaus subscription, so we do not package them. If you want them in an air-gap, obtain them under your own abuse.ch/Spamhaus arrangement and drop them into the feeds directory. The paid/BYOK feeds (GreyNoise, Shodan, AbuseIPDB, VirusTotal, CrowdStrike) are query-time, API-key-gated, and not usable in a true air-gap.

Manual snapshot alternative (if not using the bundle):

  1. On a low-side, internet-connected host, download the current snapshots:
    curl -o known_exploited_vulnerabilities.json \
    https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json
    curl -o epss_scores-current.csv.gz \
    https://epss.cyentia.com/epss_scores-current.csv.gz
    # Optional: mirror the NVD CVE JSON 2.0 data feeds into nvd/
  2. Carry the bundle/files across the diode on your normal media-transfer cadence (daily/weekly).
  3. Drop them into the mounted ./feeds directory. The backend reads from INFRACAST_KEV_FILE / INFRACAST_EPSS_FILE / INFRACAST_NVD_DIR when INFRACAST_OFFLINE_MODE=true, so a file swap + the next 24 h refresh tick (or a backend restart) picks up the new data.
Engineering status

The offline file-source env vars (INFRACAST_OFFLINE_MODE, INFRACAST_KEV_FILE, INFRACAST_EPSS_FILE, INFRACAST_NVD_DIR) are the air-gap feed-loading path. If your build predates this, the feeds simply use their embedded seed/fallback data and skip the network fetch — still fully functional, just without periodic enrichment updates. Confirm your release tag includes the offline-feed loader.


6. Scanning Isolated Segments: The On-Prem Relay​

In many air-gapped programs the backend itself shouldn't have direct L3 reachability to every asset. The On-Prem Relay solves this without any internet:

  • Deploy the relay binary (Alpine container, port 8443) inside the segment to be scanned.
  • It performs discovery locally — VMware (govmomi), SSH, SNMP v2c/v3, WinRM — and tunnels results back to the backend over an authenticated internal WebSocket.
  • The relay generates its own self-signed TLS for its local API; rotate/replace with your PKI.
  • Register it in the UI under Settings → Connectors → Relay, issue a relay token, and run scans.

This keeps credentials and direct reachability scoped to the relay, with only an internal control channel back to the core. No external dependency.


7. Hardening Checklist (air-gap baseline)​

  • Images pulled from the internal registry only; no build: and no :latest.
  • DB password and INFRACAST_JWT_SECRET rotated off the eval defaults.
  • TLS terminated with your internal PKI (front the backend with your own reverse proxy or mount certs); do not expose plaintext 8080.
  • Admin password reset on first login; MFA enforced per tenant.
  • License file mounted read-only; verify offline_mode: true in the validated claims.
  • All optional outbound connectors (Stripe, AWS Marketplace, cloud discovery, GRC SaaS, Slack/Teams/PagerDuty) left unconfigured — they are no-ops when absent.
  • Egress firewall: default-deny outbound from the Infracast subnet (belt-and-suspenders; Infracast won't initiate egress in offline mode, but enforce it anyway).
  • PostgreSQL volume on encrypted storage; backups handled per your enclave's process.
  • Artifact master key stored with 0600 permissions and backed up separately from the artifacts directory.
  • Feed-refresh media-transfer cadence documented and assigned to an operator.

8. Backups & Upgrades Offline​

  • Backups: standard pg_dump of the infracast database plus the plugins/, license.json, and feeds/ volumes. See Backup & Restore.
  • Artifacts are not in the database backup. Back up the artifacts directory (or your S3-compatible bucket) separately, and back up the artifact master key separately from both the data and the database backup. The files are encrypted: without the master key a restored artifacts directory cannot be read.
  • Upgrades: repeat the pre-staging flow — docker save the new tag on the low side, transfer, docker load, push to the internal registry, bump the pinned tag, docker compose up -d. Migrations auto-apply on startup. Always snapshot the DB before an upgrade.

9. What Does Not Work Air-Gapped​

Be explicit with stakeholders about the trade-offs:

  • Live threat-intel auto-refresh — replaced by the offline media-transfer pattern above.
  • Cloud-provider discovery (AWS/Azure/GCP/M365) — only relevant if those clouds are reachable; in a true air-gap they aren't, so use the Relay for on-prem/VMware/network gear.
  • SaaS billing / AWS Marketplace metering — not used; licensing is the offline signed token.
  • Outbound alerting to internet SaaS (Slack/Teams cloud, PagerDuty, OpsGenie) — use on-prem alert destinations (internal SMTP, internal webhook, syslog) instead.

Everything else — the full analysis, compliance, documentation, attack-path, and reporting engine — runs unchanged.