Air-gapped deployment
Mildport is built to run with zero egress. Licenses verify offline, your data stays in your stores, and the decode sidecars run beside the engine — so a network with no route to the internet is a supported deployment, not a workaround.
Three things will reach out if you let them. Close all three and nothing else phones home.
What crosses the boundary
Section titled “What crosses the boundary”An honest inventory of every outbound surface:
| Surface | Default | In an air-gap |
|---|---|---|
| License check | verified locally against your public key | ✅ no egress — nothing to configure |
| Database and blob storage | your stores | ✅ in your network |
| Decode sidecars (PDF/XLSX/OCR) | services beside the engine | ✅ no egress |
| Container images | pulled from our registry | ⚠️ mirror them (below) |
| PDF viewer worker | loaded from a public CDN by the widget | ⚠️ serve it yourself (below) |
| AI assist | off — the importer is deterministic | ⚠️ leave off, or point at a model inside your network |
| Reference datasets | off; fetched only from URLs you pass in | ⚠️ pass in-network URLs only |
| Metering and billing | off for self-hosted deployments | ✅ never contacted |
| Telemetry | none | ✅ the engine has none |
Mirror the images
Section titled “Mirror the images”Pull on a connected machine, push to your internal registry. The all-in-one image is the smallest footprint — engine and the three decode sidecars in one container. Semantic matching is a separate image; mirror it too if you want that pass:
docker pull ghcr.io/capitality-io/mildport-aio:<tag>docker tag ghcr.io/capitality-io/mildport-aio:<tag> registry.internal.example/mildport-aio:<tag>docker push registry.internal.example/mildport-aio:<tag># optional — semantic matchingdocker pull ghcr.io/capitality-io/mildport-embed:<tag>docker tag ghcr.io/capitality-io/mildport-embed:<tag> registry.internal.example/mildport-embed:<tag>docker push registry.internal.example/mildport-embed:<tag>Prefer to scale every sidecar independently? Mirror the five-image set instead:
for img in mildport mildport-pdf-probe mildport-xlsx-decode mildport-ocr mildport-embed; do docker pull ghcr.io/capitality-io/$img:<tag> docker tag ghcr.io/capitality-io/$img:<tag> registry.internal.example/$img:<tag> docker push registry.internal.example/$img:<tag>doneSkip the sidecars you won’t use — pdf-probe and ocr if you never import PDFs or scans,
xlsx-decode if you never import spreadsheets, embed if you don’t want semantic matching —
and turn them off in your values. MongoDB
and S3-compatible storage are yours; mirror or reuse what you already run.
If the estate is already Postgres, FerretDB is a verified experimental option.
Install with mirrored images
Section titled “Install with mirrored images”image: repository: registry.internal.example/mildport tag: '<tag>'imagePullSecrets: - name: internal-registrysidecars: pdfProbe: { image: { repository: registry.internal.example/mildport-pdf-probe, tag: '<tag>' } } xlsxDecode: { image: { repository: registry.internal.example/mildport-xlsx-decode, tag: '<tag>' } } ocr: { image: { repository: registry.internal.example/mildport-ocr, tag: '<tag>' } } embed: { image: { repository: registry.internal.example/mildport-embed, tag: '<tag>' } }existingSecret: mildport-secrets # IMPORT_LICENSE_PUBLIC_KEY, MONGO_CONNECTION_STRING, S3_*blob: backend: s3 s3: endpoint: 'https://minio.storage.svc:9000' bucket: mildport region: us-east-1helm install mildport oci://ghcr.io/capitality-io/charts/mildport \ -n mildport --create-namespace -f values.airgap.yamlhelm test mildport -n mildport # runs the preflight in-clusterMirror the chart itself the same way if your cluster cannot reach ghcr.io at install time.
Serve the PDF worker yourself
Section titled “Serve the PDF worker yourself”This is the one frontend gotcha. The widget loads the PDF viewer’s worker from a version-matched public CDN, which fails in an air-gap. Point it at your own copy before you define the element:
import { configurePdfWorker, defineImportSuiteElement } from '@capitality-io/mildport-widget';
configurePdfWorker('/assets/pdf.worker.min.mjs'); // same-origin copyawait defineImportSuiteElement();Copy pdf.worker.min.mjs from the pdfjs-dist package into your app’s static assets; its
version must match the one the widget ships with. If you only import CSV, XLSX or JSON and
never show the PDF preview, this does not apply to you.
Content Security Policy
Section titled “Content Security Policy”| Directive | Allow |
|---|---|
connect-src |
your Mildport API origin only |
worker-src |
'self' (or your asset origin) — not a CDN |
script-src |
your app origins — the widget ships in your bundle |
AI: off, or inside your network
Section titled “AI: off, or inside your network”The importer is deterministic by default and makes no model calls. If you want AI assist, point it at a model that runs inside your network — never a public API. AI settings live in the engine’s database rather than environment variables, so you configure them once at runtime from inside the cluster. Configure nothing and the importer stays fully deterministic, with zero egress.
Verify
Section titled “Verify”helm test mildport— the preflight passes with an offline license, your storage, and your event sink.- Add a
NetworkPolicythat denies egress except to your database, your object storage, and (if used) your in-network model endpoint. The pods should need nothing else. - Watch the browser’s network tab on the first PDF preview. If you see a CDN request, the worker above is not yet pointed at your own copy.
Upgrades
Section titled “Upgrades”Re-mirror the new tags, bump the image tags in your values, re-copy the PDF worker if the
widget’s version changed, then helm upgrade. Your license keeps working across upgrades —
it is verified locally, so nothing needs to be re-fetched or re-activated.
Next: Configuration reference · Licensing