πŸ“šBook Signing at KubeCon EU 2026Meet us at Booking.com HQ (Mon 18:30-21:00) & vCluster booth #521 (Tue 24 Mar, 12:30-1:30pm) β€” free book giveaway!RSVP Booking.com Event
Security advanced ⏱ 30 minutes K8s 1.28+

Sovereign Air-Gapped Kubernetes Clusters

Deploy sovereign and air-gapped Kubernetes clusters. Offline installation, private registry mirrors, disconnected GitOps, sovereign cloud.

By Luca Berton β€’ β€’ πŸ“– 6 min read

πŸ’‘ Quick Answer: Air-gapped Kubernetes runs completely disconnected from the internet β€” required for government, defense, and critical infrastructure. Install with pre-packaged binaries (kubeadm bundle or RKE2), mirror all images to a local registry (Harbor), use oc-mirror/skopeo for image sync, and run GitOps against internal Git servers. Sovereign = you control the entire stack, no external dependencies.

The Problem

Gartner’s 2026 trend of β€œgeopatriation” extends to sovereign tech stacks β€” countries and enterprises are building fully self-contained infrastructure with no external dependencies. Air-gapped Kubernetes is the extreme: no internet access, no cloud API calls, no external DNS, no package downloads. Every dependency must be pre-staged. This is mandatory for defense, intelligence, healthcare, critical infrastructure, and some financial systems.

flowchart TB
    subgraph EXTERNAL["🌐 External (Internet)"]
        REGISTRY["Container Registries<br/>(docker.io, quay.io, gcr.io)"]
        GIT["Git Repos<br/>(github.com)"]
        CHARTS["Helm Charts<br/>(artifacthub.io)"]
    end
    
    subgraph BRIDGE["πŸ”’ Transfer Station (one-way)"]
        MIRROR["oc-mirror / skopeo<br/>Image sync"]
        BUNDLE["Helm chart archive<br/>Git bundle"]
        DIODE["Data diode<br/>(one-way transfer)"]
    end
    
    subgraph AIRGAP["🏰 Air-Gapped Cluster"]
        HARBOR["Harbor Registry<br/>(local mirror)"]
        GITEA["Gitea<br/>(internal Git)"]
        K8S["Kubernetes<br/>(RKE2 / kubeadm)"]
        ARGOCD["ArgoCD<br/>(internal GitOps)"]
    end
    
    EXTERNAL --> BRIDGE
    BRIDGE -->|"Physical media<br/>or data diode"| AIRGAP
    HARBOR --> K8S
    GITEA --> ARGOCD --> K8S

The Solution

Phase 1: Mirror Images (Connected Side)

# Option A: oc-mirror (OpenShift/OKD)
oc-mirror --config imageset-config.yaml file://mirror-bundle

# imageset-config.yaml
kind: ImageSetConfiguration
mirror:
  platform:
    channels:
      - name: stable-4.15
  additionalImages:
    - name: registry.k8s.io/kube-apiserver:v1.30.0
    - name: registry.k8s.io/kube-controller-manager:v1.30.0
    - name: registry.k8s.io/kube-scheduler:v1.30.0
    - name: registry.k8s.io/etcd:3.5.15-0
    - name: registry.k8s.io/coredns/coredns:v1.11.1
    - name: registry.k8s.io/pause:3.9
    - name: docker.io/calico/cni:v3.28.0
    - name: docker.io/calico/node:v3.28.0
    - name: quay.io/argoproj/argocd:v2.12.0
    # GPU Operator
    - name: nvcr.io/nvidia/gpu-operator:v24.3.0
    - name: nvcr.io/nvidia/driver:550.90.07

# Option B: skopeo copy (image-by-image)
skopeo copy \
  docker://registry.k8s.io/kube-apiserver:v1.30.0 \
  dir:///transfer/kube-apiserver-v1.30.0

# Option C: crane (bulk)
crane pull registry.k8s.io/kube-apiserver:v1.30.0 \
  /transfer/kube-apiserver-v1.30.0.tar

Phase 2: Transfer to Air-Gapped Network

# Pack everything for physical transfer
tar czf k8s-airgap-bundle-$(date +%Y%m%d).tar.gz \
  /transfer/images/ \
  /transfer/charts/ \
  /transfer/binaries/ \
  /transfer/git-bundles/

# Transfer via:
# - USB drive / external SSD
# - Data diode (one-way network device)
# - Cross-domain solution (CDS)
# - Burnt DVD/Blu-ray (for audit trail)

# Verify integrity after transfer
sha256sum -c checksums.sha256

Phase 3: Load into Air-Gapped Registry

# Push images to internal Harbor
for image_dir in /transfer/images/*/; do
  IMAGE_NAME=$(basename $image_dir)
  skopeo copy \
    dir://$image_dir \
    docker://harbor.internal.local/$IMAGE_NAME
done

# Or use oc-mirror to load
oc-mirror --from /transfer/mirror-bundle \
  docker://harbor.internal.local

Harbor Registry (Air-Gapped)

# Harbor deployed inside air-gapped cluster
apiVersion: apps/v1
kind: Deployment
metadata:
  name: harbor-core
  namespace: registry
spec:
  template:
    spec:
      containers:
        - name: harbor
          image: harbor.internal.local/goharbor/harbor-core:v2.11.0
          env:
            - name: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY
              value: "/storage"
          volumeMounts:
            - name: storage
              mountPath: /storage
      volumes:
        - name: storage
          persistentVolumeClaim:
            claimName: harbor-storage   # Large PVC for all mirrored images

RKE2 Offline Installation

# On connected machine: download RKE2 bundle
curl -sfL https://get.rke2.io --output install.sh
# Download images tarball
curl -sfL https://github.com/rancher/rke2/releases/download/v1.30.0+rke2r1/rke2-images.linux-amd64.tar.zst \
  -o rke2-images.tar.zst

# Transfer to air-gapped node, then:
# 1. Place images
mkdir -p /var/lib/rancher/rke2/agent/images/
cp rke2-images.tar.zst /var/lib/rancher/rke2/agent/images/

# 2. Install RKE2
INSTALL_RKE2_ARTIFACT_PATH=/transfer/binaries \
  sh install.sh

# 3. Configure private registry
cat > /etc/rancher/rke2/registries.yaml <<EOF
mirrors:
  "docker.io":
    endpoint:
      - "https://harbor.internal.local"
  "registry.k8s.io":
    endpoint:
      - "https://harbor.internal.local"
  "quay.io":
    endpoint:
      - "https://harbor.internal.local"
configs:
  "harbor.internal.local":
    tls:
      ca_file: /etc/pki/ca-trust/source/anchors/harbor-ca.crt
EOF

# 4. Start RKE2
systemctl enable rke2-server
systemctl start rke2-server

Internal GitOps (ArgoCD + Gitea)

# Gitea β€” lightweight internal Git server
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gitea
  namespace: git
spec:
  template:
    spec:
      containers:
        - name: gitea
          image: harbor.internal.local/gitea/gitea:1.22
          ports:
            - containerPort: 3000
            - containerPort: 22
          volumeMounts:
            - name: data
              mountPath: /data
---
# ArgoCD pointed at internal Gitea
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: platform-apps
  namespace: argocd
spec:
  source:
    repoURL: https://gitea.internal.local/platform/manifests.git
    path: apps/
    targetRevision: main
  destination:
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      selfHeal: true

ImageContentSourcePolicy (IDMS)

Redirect all image pulls to internal registry:

# For OpenShift
apiVersion: config.openshift.io/v1
kind: ImageDigestMirrorSet
metadata:
  name: airgap-mirrors
spec:
  imageDigestMirrors:
    - source: docker.io
      mirrors:
        - harbor.internal.local/dockerhub
    - source: registry.k8s.io
      mirrors:
        - harbor.internal.local/k8s
    - source: quay.io
      mirrors:
        - harbor.internal.local/quay
    - source: nvcr.io
      mirrors:
        - harbor.internal.local/nvidia

Offline Helm Charts

# Connected side: package charts
helm pull argo/argo-cd --version 7.3.0
helm pull prometheus-community/kube-prometheus-stack --version 61.0.0
helm pull nvidia/gpu-operator --version v24.3.0

# Transfer .tgz files to air-gapped cluster
# Install from local files
helm install argocd /charts/argo-cd-7.3.0.tgz \
  --set global.image.repository=harbor.internal.local/argoproj/argocd \
  -n argocd --create-namespace

Update Process

# Monthly sync procedure:
# 1. (Connected) Pull new images + charts + patches
./sync-images.sh --since-last-sync

# 2. (Connected) Create transfer bundle
./create-bundle.sh --output /media/usb/bundle-$(date +%Y%m).tar.gz

# 3. (Transfer) Physical media through security checkpoint
# Scan bundle for malware/anomalies

# 4. (Air-gapped) Load bundle
./load-bundle.sh --input /media/usb/bundle-202604.tar.gz

# 5. (Air-gapped) Update cluster
helm upgrade --reuse-values argocd /charts/argo-cd-7.4.0.tgz
kubectl rollout restart deployment -n monitoring

Common Issues

IssueCauseFix
`ImagePullBackOff`Image not in local registryMirror the missing image
Certificate errorsHarbor CA not trusted by nodesDistribute CA cert to all nodes
Helm chart needs internetChart has external dependenciesPre-pull all sub-charts
DNS resolution failsNo external DNSUse CoreDNS with internal-only zones
Time sync driftNo NTP serversDeploy internal NTP (chrony) or GPS clock
CVE updates delayedManual sync processEstablish regular sync cadence (weekly/monthly)

Best Practices

  • Automate the mirror process β€” scripted, reproducible, auditable
  • Verify checksums after transfer β€” detect corruption from physical media
  • Use data diodes β€” hardware-enforced one-way data flow for highest security
  • Mirror by digest, not tag β€” tags can be reassigned; digests are immutable
  • Test updates in staging β€” air-gapped staging cluster before production
  • Document every transfer β€” compliance requires chain of custody

Key Takeaways

  • Air-gapped K8s = zero internet access β€” every dependency must be pre-staged
  • Mirror images with oc-mirror/skopeo, transfer via physical media or data diode
  • Harbor provides the internal container registry; Gitea provides internal Git
  • RKE2 and kubeadm support offline installation with pre-loaded images
  • ImageDigestMirrorSet redirects all pulls to internal registry transparently
  • 2026 trend: sovereign tech stacks with no external dependencies
#air-gapped #sovereign #offline #private-registry #compliance
Luca Berton
Written by Luca Berton

Principal Solutions Architect specializing in Kubernetes, AI/GPU infrastructure, and cloud-native platforms. Author of Kubernetes Recipes and creator of CopyPasteLearn courses.

Kubernetes Recipes book cover

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.

Luca Berton Ansible Pilot Ansible by Example Open Empower K8s Recipes Terraform Pilot CopyPasteLearn ProteinLens