Skip to main content

Artifact Collection (Hauler)

Hauler is the RGS airgap transport tool. It collects OCI images, Helm charts, and binary files into a self-contained store, packages the store into a tarball for sneakernet transfer, and serves the contents as an OCI-compatible registry and file server on the airgap side.

Status: Complete ✅ — 145 GB store (full Carbide product bundle) saved to /srv/www/htdocs/hauler/.


References

ResourceURL
Hauler docshttps://hauler.dev
Hauler GitHubhttps://github.com/hauler-dev/hauler
RGS Carbide docshttps://ranchercarbide.dev/docs
RGS airgap bloghttps://ranchergovernment.com/blog/airgapping-made-easy-with-rke2-and-rancher

How Hauler works

Hauler organizes everything around a store — a local OCI-layout directory (default /var/lib/hauler/). The store is content-addressed, so re-running sync only downloads what has changed.

Manifests describe what to collect using three Kubernetes-style resource kinds:

KindWhat it collects
ImagesOCI container images (multi-arch via platforms:)
ChartsHelm charts from any Helm repo URL
FilesArbitrary files or tarballs from any HTTPS URL

A single manifest file can contain multiple resources of any kind. In this enclave, manifests are generated at runtime from version variables in scripts/env.d/carbide-enclave.sh — bumping a version there automatically flows into the next sync.


Prerequisites

Disk space

The full enclave store (all components below) is approximately 9–12 GB uncompressed. Plan for at least 25 GB free on the bastion host to accommodate the store, the compressed tarball, and working space during sync.

PathPurposeNotes
/var/lib/hauler/Hauler storeContent-addressed; safe to re-sync
/srv/www/htdocs/hauler/Tarball output + manifest filesWeb-accessible from nuc-00

Network access (pre-airgap only)

sync must run on a host with outbound HTTPS to the following:

Registry / URLUsed for
registry.ranchercarbide.devCarbide-signed Rancher + RKE2 images
registry.ranchercarbide.devCarbide registry — Carbide-signed images and --products bundle
docker.ioHarbor images (rate-limited without auth)
quay.iocert-manager + Keycloak images
nvcr.ioNVIDIA GPU Operator image
charts.jetstack.iocert-manager Helm chart
charts.rancher.comRancher Helm chart
helm.goharbor.ioHarbor Helm chart
charts.bitnami.comKeycloak Helm chart
helm.ngc.nvidia.comGPU Operator Helm chart
github.comRKE2 binary tarballs + checksums
releases.rancher.comHarvester community ISO + iPXE assets
get.hauler.devHauler install script (if not already installed)
get.rke2.ioRKE2 install script

Credentials

Set in ~/.config/RGS/creds before running any hauler.sh command:

# Carbide registry (both registries use the same credentials)
export CARBIDE_USERNAME="..."
export CARBIDE_PASSWORD="..."

# Docker Hub — read-only Personal Access Token to avoid anonymous rate limits
export DOCKER_USERNAME="..."
export DOCKER_PASSWORD="..."

# Harbor admin — needed for the push phase (after Harbor is up)
export HARBOR_ADMIN_PASSWORD="..."

~/.config/RGS/creds is outside any git directory and is never committed. See scripts/env.d/creds.example for the expected variable names.

Operator privilege

CommandRequired user
sync, save, pushmansible (non-root)
serve, serve-filesroot (binds to low ports; manages store dir)
loadroot (writes to /var/lib/hauler/)

The Hauler lifecycle

┌─────────────────────────────────────────────────────────────────┐
│ Internet-connected side (nuc-00, pre-airgap) │
│ │
│ 1. hauler.sh sync ← pulls images/charts/files into store │
│ 2. hauler.sh save ← packages store → .tar.zst tarball │
└───────────────────────────┬─────────────────────────────────────┘
│ USB / sneakernet
┌───────────────────────────▼─────────────────────────────────────┐
│ Airgap side (nuc-00 after boundary crossing) │
│ │
│ 3. hauler.sh load ← unpacks tarball back into store │
│ 4. hauler.sh serve ← OCI registry :5000 (Harvester + RKE2) │
│ 5. hauler.sh push ← migrates store → Harbor (permanent) │
└─────────────────────────────────────────────────────────────────┘

Phase 1 — sync: Hauler reads each manifest, authenticates to upstream registries, and pulls all content into the local store. The --products rke2=<version> flag pulls the full RKE2 component image set (pause, etcd, CNI plugins, etc.) that isn't enumerated in the manual manifest.

Phase 2 — save: The store is packaged into a timestamped, zstd-compressed tarball and written to /srv/www/htdocs/hauler/. The tarball is immediately web-accessible from nuc-00 at http://10.0.0.10/hauler/.

Phase 3 — load: On the airgap side, the tarball is extracted back into the Hauler store. This is the mirror of save.

Phase 4 — serve: Hauler starts an OCI-compatible registry on port 5000. During Harvester node install and RKE2 bootstrap, nodes pull images from this registry before Harbor exists.

Phase 5 — push: Once Harbor is running, all content is migrated from the ephemeral Hauler registry into Harbor's permanent registry. After this point, Harbor is the authoritative OCI source for the enclave.


Manifest format

:::caution Hauler v2 breaking changes Hauler v2.0.0 (June 2025) introduced two breaking manifest changes from v1.x:

  1. apiVersion: content.hauler.cattle.io/v1alpha1content.hauler.cattle.io/v1
  2. platforms: (list per image) → platform: (singular string per entry) — list the same image twice for multi-arch :::

Manifests use Kubernetes-style YAML with Hauler's custom API kinds. Here is an annotated excerpt from the generated cert-manager.yaml:

# Charts — fetched from a Helm repo URL and stored verbatim
apiVersion: content.hauler.cattle.io/v1
kind: Charts
metadata:
name: cert-manager-chart
spec:
charts:
- name: cert-manager
repoURL: https://charts.jetstack.io
version: "v1.14.5"
---
# Images — pulled by digest, stored in OCI layout
# v2: platform is a singular string per entry; list the image twice for multi-arch
apiVersion: content.hauler.cattle.io/v1
kind: Images
metadata:
name: cert-manager-images
spec:
images:
- name: quay.io/jetstack/cert-manager-controller:v1.14.5
platform: linux/amd64
- name: quay.io/jetstack/cert-manager-controller:v1.14.5
platform: linux/arm64 # required — cert-manager runs on DGX Spark nodes

And a Files example from rke2.yaml:

apiVersion: content.hauler.cattle.io/v1
kind: Files
metadata:
name: rke2-binaries
spec:
files:
- path: https://github.com/rancher/rke2/releases/download/v1.32.13%2Brke2r1/rke2.linux-amd64.tar.gz
- path: https://github.com/rancher/rke2/releases/download/v1.32.13%2Brke2r1/rke2.linux-arm64.tar.gz

Manifests are written to /srv/www/htdocs/hauler/manifests/ and are browseable at http://10.0.0.10/hauler/manifests/ for reference after each sync.


What gets collected

ManifestContent typesarm64?
rke2.yamlRuntime image + amd64/arm64 install tarballs + install scriptYes
cert-manager.yamlHelm chart + 5 controller imagesYes
rancher.yamlHelm chart + Rancher + shell images (Carbide registry)Yes
harvester.yamlCommunity ISO + iPXE vmlinuz/initrd/rootfs/squashfsamd64 only
harbor.yamlHelm chart + 10 Harbor component imagesamd64 only
keycloak.yamlHelm chart + Keycloak imageYes
gpu-operator.yamlHelm chart + GPU Operator imageYes

Additionally, hauler store sync --products rke2=<version> pulls the full RKE2 component image set (pause, etcd, CoreDNS, Canal CNI, etc.) that is not enumerated in the manifest.


Commands

# Pull all artifacts from upstream into the Hauler store (needs internet)
bash scripts/hauler.sh sync

# Package store → timestamped tarball in web root
bash scripts/hauler.sh save

# sync + save in one shot (pre-airgap convenience)
bash scripts/hauler.sh all

# Load a tarball into the store (airgap side, run as root)
sudo bash scripts/hauler.sh load /path/to/carbide-enclave-<timestamp>.tar.zst

# Start Hauler OCI registry on :5000 (used during Harvester + RKE2 install)
sudo bash scripts/hauler.sh serve

# Start Hauler file server on :8080 (alternative binary distribution)
sudo bash scripts/hauler.sh serve-files

# Push store contents → Harbor (after Harbor is up)
bash scripts/hauler.sh push

Hauler is auto-installed by hauler.sh sync if not already present. Non-root installs go to ~/.local/bin/; root installs go to /usr/local/bin/.


Airgap transfer

After save, the tarball is immediately web-accessible:

http://10.0.0.10/hauler/carbide-enclave-<timestamp>.tar.zst

Transfer to the airgap side via USB, external drive, or any out-of-band method. Then load:

sudo bash scripts/hauler.sh load /path/to/carbide-enclave-<timestamp>.tar.zst

Version management

All component versions are set in scripts/env.d/carbide-enclave.sh:

RKE2_VERSION="v1.32.13+rke2r1"
RANCHER_VERSION="v2.13.6"
CERT_MANAGER_VERSION="v1.14.5"
HARBOR_VERSION="2.11.0"
HARBOR_CHART_VERSION="1.14.0" # Helm chart version — differs from app version
KEYCLOAK_VERSION="24.0.4"
HAULER_VERSION="v2.0.1"
GPU_OPERATOR_VERSION="v24.3.0"

Bumping a version in that file and re-running hauler.sh sync is the complete update workflow — manifests are regenerated automatically before each sync.

:::caution Harbor chart vs app version Harbor's Helm chart version (HARBOR_CHART_VERSION) and its application image tag (HARBOR_VERSION) are not the same. Both must be set correctly. Mismatching them produces images that don't match what the chart expects. :::


Things to consider

Docker Hub rate limiting

Anonymous pulls from docker.io are rate-limited to ~100 pulls per 6 hours per IP. Harbor images (which are only available on Docker Hub) will fail sync without authentication. A free Docker Hub account with a read-only Personal Access Token is sufficient and required.

Carbide registry consolidation (v2 change)

As of mid-2025, registry.ranchercarbide.dev is the single Carbide registry for both images and --products bundle syncs. The old rgcrprod.azurecr.io / rgcrprod.azurecr.us URLs return UNAUTHORIZED and should not be used. hauler.sh sync authenticates to registry.ranchercarbide.dev once and uses --product-registry registry.ranchercarbide.dev for the --products pull.

RKE2 version string normalization

The RKE2 version v1.32.13+rke2r2 has two forms used in different contexts:

ContextFormExample
OCI image tags+-v1.32.13-rke2r2
GitHub download URLs+%2Bv1.32.13%2Brke2r2

hauler.sh handles both transformations automatically. If writing custom manifests, be aware of which form each context requires.

RKE2 image tarballs are not on GitHub for v1.30+

For RKE2 v1.30 and later, the rke2-images.linux-amd64.tar.gz bundle is no longer published to GitHub releases. Use hauler store sync --products rke2=<version> instead — it pulls the component images directly from the Carbide registry and is the supported path for Carbide customers.

arm64 parity

Any image or binary that runs on or is managed by the DGX Spark (arm64) must have an explicit linux/arm64 platform entry in its manifest. Omitting it means the image will not be in the store and the DGX Spark workload will fail to pull at runtime — with no error at sync time.

Components currently amd64-only (Harbor, Harvester) do not run on the DGX Spark directly. Everything else — cert-manager, Rancher, GPU Operator, RKE2 runtime — must be multi-arch.

Hauler registry is temporary

The hauler store serve registry on port 5000 is an ephemeral registry used only during the Harvester install and RKE2 bootstrap phases, before Harbor is available. Once Harbor is running and hauler.sh push has completed, Harbor is the authoritative registry. Stop the Hauler serve process after the push is confirmed.

The step CLI binary needs to be in the store

step (the Smallstep CLI) is required on SL-Micro RKE2 nodes and the DGX Spark to bootstrap CA trust, but it is not available in any package repository for those OSes. In an airgap, it must come from Hauler or be served from nuc-00's Apache web root.

Add both platform builds to a Files entry in hauler.sh (or copy them manually to /srv/www/htdocs/step/ on nuc-00 pre-airgap):

apiVersion: content.hauler.cattle.io/v1
kind: Files
metadata:
name: step-cli
spec:
files:
- path: https://github.com/smallstep/cli/releases/download/v0.27.4/step_linux_0.27.4_amd64.tar.gz
- path: https://github.com/smallstep/cli/releases/download/v0.27.4/step_linux_0.27.4_arm64.tar.gz

After loading the store on the airgap side, extract the binaries from the tarballs and place them at http://10.0.0.10/step/step-linux-amd64 and http://10.0.0.10/step/step-linux-arm64 so nodes can fetch them without a package manager.

Harvester government edition is not in the store

The Harvester government edition (v1.7.1-govt.2) is placed manually on nuc-00's Apache web root and is not collected by Hauler. The harvester.yaml manifest references community build URLs for reference only. Government edition assets are not publicly downloadable and must be transferred separately.