First Deployment
Purpose: For platform engineers and field operators, walks through building an air-gap Zarf package on a connected build host, transferring it, and using it to install a 3-node Kubernetes cluster on a disconnected bastion plus three cluster nodes.
You will end with:
-
A signed Zarf package on the build host (~25 GB compressed).
-
A bastion serving a local container registry, an apt mirror, and a Python wheel index.
-
A 3-node Kubernetes v1.34.3 cluster pulling exclusively from the bastion.
Plan on 60–90 minutes the first time, mostly waiting for downloads.
Prerequisites
Build host (Zone A — connected):
-
Ubuntu 24.04 (other Linux distros work but are not what the build is tested on).
-
Python 3.12 or later (
pyproject.tomlpinsrequires-python = ">=3.12"). -
Git 2.30+.
-
100 GB free disk.
-
Internet access to GitHub, container registries, Ubuntu apt mirrors, and PyPI.
-
Optional: Zarf CLI and Cosign, if you want the build to produce a signed
.tar.zstpackage. Without Zarf the build still produceszarf.yamland the artifact manifest, but not the compressed package.
Field environment (Zone C — disconnected):
-
1 bastion host on Ubuntu 24.04, 100 GB free disk, Zarf CLI installed.
-
3 cluster nodes on Ubuntu 24.04, each ≥ 4 vCPU and ≥ 8 GB RAM.
-
Bastion can SSH to all cluster nodes; cluster nodes can reach the bastion on TCP 5000 (registry), 80 (nginx), and 3000 (Gitea, optional).
Step 1 — Install the CLI on the build host
git clone <repo-url> opencenter-airgap
cd opencenter-airgap
pip install -e .
opencenter-airgap version
pip install -e . installs the opencenter-airgap script defined in pyproject.toml under [project.scripts]. The version subcommand prints the package version plus key dependency versions.
Step 2 — Initialize a project
opencenter-airgap init
This creates config/, build/, dist/, assets/, and a default config/versions.env. It does not create config/components.yaml — the build step will generate that for you on first run.
Step 3 — Pin versions
Open config/versions.env and review the pinned versions. The defaults match the matrix the project ships with:
KUBERNETES_VERSION="v1.34.3"
UBUNTU_VERSION="24.04"
CONTAINERD_VERSION="2.1.5"
CALICO_VERSION="v3.31.3"
FLUXCD_VERSION="v2.7.5"
…
For your first deployment, keep the defaults. The build will fail closed if any value is missing or unparseable.
Step 4 — Build the package
opencenter-airgap build
The build:
-
Loads
config/versions.env. -
Generates (or merges into)
config/components.yamlfrom those versions. -
Clones the source repos listed in
versions.env(Kubespray andopenCenter-gitops-base) intobuild/. -
Scans the cloned repos for container images and Helm charts.
-
Writes
config/all-images.txt,config/helm-charts.txt, andconfig/helm-repos.txt. -
Generates
zarf.yamlfromzarf.yaml.template. -
If Zarf CLI is installed, builds
dist/zarf-package-opencenter-airgap-amd64-<version>.tar.zstplus…-sbom.jsonand…sha256sidecars. -
Writes
dist/artifact-manifest.jsonrecording every artifact with its checksum.
Build state is checkpointed to build/state.json. If the build dies — usually a flaky download — re-run opencenter-airgap build and it will resume from the last completed step. To start fresh use --clean.
Step 5 — Verify the package
Before transferring the package, run the verification helper:
hack/scripts/verify-package.sh dist/zarf-package-*.tar.zst
This checks the .sha256 sidecar, optionally the Cosign signature if you pass a .pub key, and rejects the package if the SBOM contains latest image tags or HIGH/CRITICAL CVEs. See ../operations/verify-package.md[Verify a Built Package] for the full procedure.
Step 6 — Transfer to the field
The package and its sidecars need to travel together:
ls dist/
# zarf-package-opencenter-airgap-amd64-1.0.0-rc2.tar.zst
# zarf-package-opencenter-airgap-amd64-1.0.0-rc2.tar.zst.sha256
# zarf-package-opencenter-airgap-amd64-1.0.0-rc2-sbom.json
# artifact-manifest.json
cp dist/zarf-package-* /media/usb/
cp dist/artifact-manifest.json /media/usb/
Move the media to the disconnected site by whatever process your environment requires.
Step 7 — Deploy on the bastion
On the bastion:
sudo zarf package deploy zarf-package-*.tar.zst --confirm
This unpacks the package under /opt/opencenter/, starts a container registry on TCP 5000, brings up an nginx file server, and stages the Kubespray playbooks and binaries.
If you have the CLI installed on the bastion as well, opencenter-airgap serve <package> does the deploy and an explicit health check on every service.
Step 8 — Provision the cluster
On the bastion, write the inventory for your three nodes. Replace the IPs with the addresses of your cluster hosts:
# /opt/opencenter/kubespray/inventory/mycluster/inventory.yml
all:
hosts:
node1: { ansible_host: 192.168.1.101, ip: 192.168.1.101 }
node2: { ansible_host: 192.168.1.102, ip: 192.168.1.102 }
node3: { ansible_host: 192.168.1.103, ip: 192.168.1.103 }
children:
kube_control_plane: { hosts: { node1: {} } }
kube_node: { hosts: { node1: {}, node2: {}, node3: {} } }
etcd: { hosts: { node1: {} } }
k8s_cluster:
children:
kube_control_plane: {}
kube_node: {}
Point the cluster nodes at the bastion’s apt and pip mirrors:
cd /opt/opencenter/playbook
ansible-playbook -i ../kubespray/inventory/mycluster/inventory.yml offline-repo.yml
Then run Kubespray:
cd /opt/opencenter/kubespray
ansible-playbook -i inventory/mycluster/inventory.yml cluster.yml
Expect 20–30 minutes.
Step 9 — Check your work
On the bastion:
export KUBECONFIG=/opt/opencenter/kubespray/inventory/mycluster/artifacts/admin.conf
# All nodes Ready
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# node1 Ready control-plane 5m v1.34.3
# node2 Ready <none> 4m v1.34.3
# node3 Ready <none> 4m v1.34.3
# Core pods running
kubectl get pods -A | grep -v Running | grep -v Completed
# (only the header should remain)
# Bastion registry is the only image source
kubectl get pods -A -o jsonpath='{.items[*].spec.containers[*].image}' \
| tr ' ' '\n' | sort -u | head
# Every line should start with <bastion-ip>:5000/
If anything fails the ../reference/troubleshooting.md[Troubleshooting reference] covers the common cases.
Next steps
-
../operations/add-custom-component.md[Add a Custom Component] — extend the manifest with a tool, image, or chart.
-
../reference/component-manifest-schema.md[Component Manifest Schema] — full reference for
config/components.yaml. -
../concepts/architecture-overview.md[Architecture Overview] — why the build is split into three zones.