Air-Gap Mirroring
Purpose: For operators, shows how to mirror images for disconnected environments.
Task Summary
Air-gap mirroring packages every container image required by the openCenter platform into a single Zarf artifact. This artifact is transferred to a disconnected site where a bastion host serves images from a local registry.
Prerequisites
- A connected build host with internet access (Zone A)
opencenter-airgapCLI installed (see Air-Gap CLI Reference)- Docker or Podman running on the build host
- Sufficient disk space — a full platform package is typically 15–25 GB compressed
- Physical media or approved transfer mechanism for moving the artifact to the air-gapped site
Steps
1. Initialize the Air-Gap Configuration
opencenter-airgap init my-airgap-cluster
This creates a config/ directory with versions.env listing every image, Helm chart, and binary version.
2. Pin Versions
Edit config/versions.env to lock all component versions:
# config/versions.env
K8S_VERSION=1.29.4
FLUX_VERSION=2.3.0
CERT_MANAGER_VERSION=1.18.2
KYVERNO_VERSION=1.12.0
HARBOR_VERSION=2.11.0
LONGHORN_VERSION=1.7.0
3. Build the Zarf Package
opencenter-airgap build
The build process:
- Pulls every image listed in the platform manifest from upstream registries (Docker Hub, Quay.io, ghcr.io).
- Pulls Helm charts and Kubernetes binaries.
- Packages OS dependencies and Python packages required by Kubespray.
- Bundles everything into a signed Zarf artifact:
zarf-package-my-airgap-cluster-amd64.tar.zst. - Generates an SBOM for the entire package.
Build time depends on network speed — expect 30–60 minutes for a full platform package.
4. Transfer to the Air-Gapped Site
Move the .tar.zst file to the disconnected environment using your approved transfer mechanism (physical media, data diode, cross-domain solution).
5. Deploy on the Bastion Host
On the bastion host inside the air-gapped network:
zarf package deploy zarf-package-my-airgap-cluster-amd64.tar.zst --confirm
After deployment, the bastion serves:
| Service | Port | Content |
|---|---|---|
| Container registry | 5000 | All platform and application images |
| Package repository | 8080 | OS packages, Python dependencies |
| Helm chart repository | 8081 | All Helm charts from openCenter-gitops-base |
6. Configure Cluster Nodes to Use the Bastion Registry
Point containerd on each node to the bastion registry. In the Kubespray inventory:
# group_vars/all/containerd.yml
containerd_registries_mirrors:
- prefix: "docker.io"
mirrors:
- host: "http://bastion.local:5000"
capabilities: ["pull", "resolve"]
- prefix: "ghcr.io"
mirrors:
- host: "http://bastion.local:5000"
capabilities: ["pull", "resolve"]
- prefix: "quay.io"
mirrors:
- host: "http://bastion.local:5000"
capabilities: ["pull", "resolve"]
Verification
Confirm the bastion registry contains the expected images:
# List repositories in the bastion registry
curl -s http://bastion.local:5000/v2/_catalog | jq .
# Verify a specific image is available
skopeo inspect docker://bastion.local:5000/platform-security/cert-manager-controller:1.18.2
From a cluster node, verify image pull works:
crictl pull bastion.local:5000/platform-core/fluxcd/source-controller:2.3.0
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
zarf package deploy fails with checksum error | Corrupted transfer | Re-transfer the artifact; verify SHA256 before deploy |
| Node can't pull from bastion | containerd mirror not configured | Check containerd_registries_mirrors in Kubespray inventory |
| Missing image in bastion registry | Image not in versions.env | Add the image, rebuild, and redeploy the Zarf package |
Further Reading
- Image Lifecycle Overview — Where mirroring fits in the pipeline
- Image Catalog — Full list of images included in the package
- Publish & Promote — How images reach production before mirroring