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 served by the backend or any web serverUI bundle (built 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

Do not leave build: . in the air-gapped compose file — you don't want to require a build toolchain or any 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 bundle

If you serve the UI separately, build it with the in-enclave API URL baked in:

VITE_API_URL=https://infracast.internal.mil npm run build
# deploy dist/ to your internal web server or S3-compatible store

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:
vulcan:
image: registry.internal/azgardtek/vulcan:<release-tag> # internal registry, pinned
ports:
- "8443:8080"
environment:
- VULCAN_DB_TYPE=postgres
- VULCAN_DB_URL=postgres://vulcan:${DB_PASSWORD}@postgres:5432/vulcan
- VULCAN_JWT_SECRET=${JWT_SECRET}
- VULCAN_LOG_LEVEL=info
- VULCAN_LICENSE_FILE=/etc/infracast/license.json
# Offline threat-intel: point feeds at local files instead of the internet
- VULCAN_OFFLINE_MODE=true
- VULCAN_KEV_FILE=/etc/infracast/feeds/known_exploited_vulnerabilities.json
- VULCAN_EPSS_FILE=/etc/infracast/feeds/epss_scores-current.csv.gz
- VULCAN_NVD_DIR=/etc/infracast/feeds/nvd/
volumes:
- ./plugins:/app/plugins
- ./license.json:/etc/infracast/license.json:ro
- ./feeds:/etc/infracast/feeds:ro
depends_on:
postgres:
condition: service_healthy

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

volumes:
postgres_data:

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

# .env  (chmod 600, do not commit)
DB_PASSWORD=<long-random>
JWT_SECRET=<32+ byte random>

Bring it up:

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

Database migrations run automatically on startup. First-boot seeds the admin account; set a strong password immediately.

Replace every default

The stock docker-compose.yml ships eval defaults (vulcan/vulcan, 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 VULCAN_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
# VULCAN_KEV_FILE -> known_exploited_vulnerabilities.json
# VULCAN_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 VULCAN_KEV_FILE / VULCAN_EPSS_FILE / VULCAN_NVD_DIR when VULCAN_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 (VULCAN_OFFLINE_MODE, VULCAN_KEV_FILE, VULCAN_EPSS_FILE, VULCAN_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 Relay Connectors, 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 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.
  • Feed-refresh media-transfer cadence documented and assigned to an operator.

8. Backups & Upgrades Offline

  • Backups: standard pg_dump of the vulcan database plus the plugins/, license.json, and feeds/ volumes. See Backup & Restore.
  • 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.