Air gap installation with Hauler

This guide shows you how to install SUSE Security Admission Controller in air-gapped environments using Hauler. Hauler is a tool that helps users run workloads in air-gapped environments. It moves the required resources for apps into those air-gapped environments.

Hauler uses a manifest file that lists all the required resources to run Admission Controller in a private environment. This documentation describes how you can create and use it. Refer to the Hauler documentation to learn more about it. Hauler is also available in the SUSE Application Collection.

The basic workflow using the Hauler manifest is:

  1. Create it with the Admission Controller resources from the SUSE Application Collection

  2. Load all the resources required to run Admission Controller into a local store

  3. Export them into a file

  4. Move the file into the private environment

  5. Load the resources into Hauler running inside the private environment

  6. Copy everything into a registry, to be used in the installation process, in your air-gapped environment

Create the Hauler manifest

The Admission Controller Helm chart and container images are distributed through the SUSE Application Collection. Create a hauler_manifest.yaml file with the following content:

apiVersion: content.hauler.cattle.io/v1
kind: Charts
metadata:
  name: suse-security-admission-controller-chart
spec:
  charts:
    - name: suse-security-admission-controller
      repoURL: oci://dp.apps.rancher.io/charts
      version: 1.1.0
---
apiVersion: content.hauler.cattle.io/v1
kind: Images
metadata:
  name: suse-security-admission-controller-images
spec:
  images:
    - name: dp.apps.rancher.io/containers/kubewarden-controller:1.38.2-14.2
    - name: dp.apps.rancher.io/containers/kubewarden-audit-scanner:1.38.2-14.2
    - name: dp.apps.rancher.io/containers/kubewarden-policy-server:1.38.2-14.2
    - name: dp.apps.rancher.io/containers/kubectl:1.37.0-3.1
    - name: dp.apps.rancher.io/containers/policy-reporter:3.10.0-13.1
    - name: dp.apps.rancher.io/containers/policy-reporter-ui:2.8.1-13.1
---
apiVersion: content.hauler.cattle.io/v1
kind: Images
metadata:
  name: kubewarden-policies
  annotations:
    hauler.dev/certificate-oidc-issuer: https://token.actions.githubusercontent.com
    hauler.dev/certificate-identity-regexp: https://github.com/kubewarden/policies/.github/workflows/release.yml@refs/tags/.*
spec:
  images:
    - name: ghcr.io/kubewarden/policies/allow-privilege-escalation-psp:v1.0.14
    - name: ghcr.io/kubewarden/policies/capabilities-psp:v1.0.14
    - name: ghcr.io/kubewarden/policies/host-namespaces-psp:v1.1.12
    - name: ghcr.io/kubewarden/policies/hostpaths-psp:v1.1.11
    - name: ghcr.io/kubewarden/policies/pod-privileged:v1.0.14
    - name: ghcr.io/kubewarden/policies/user-group-psp:v1.1.5

The chart version and image tags in this example correspond to Admission Controller 1.38.2. For other versions, use the images listed in the images-lock.yaml file included in the chart. The policies are the ones that the chart deploys when you enable its recommended policies. Add any other policy that you want to use.

Log in to the SUSE Application Collection with Hauler, using your Application Collection credentials:

hauler login dp.apps.rancher.io \
  --username <your-username> \
  --password <your-token>

Synchronize the resources defined in the manifest to your Hauler store:

hauler store sync --filename hauler_manifest.yaml

Hauler downloads all the resources from the manifest to the local store. This process takes a few minutes. When complete, you can see the synchronized resources with the hauler store info command.

Generate the tarball file with all Admission Controller resources

Run the Hauler command to export all the resources previously loaded in the store to a file:

hauler store save --filename kubewarden-resources.tar.zst

Admission Controller container images support x86_64 and Arm architectures. Therefore, when you save the resource into the file, you may see warning messages like these:

2026-10-10 09:36:57 WRN compatibility warning... docker... multi-arch index [dp.apps.rancher.io/containers/kubewarden-policy-server:1.38.2-14.2] saved without --platform
2026-10-10 09:36:57 WRN 'docker load' keeps only one architecture per tag (last wins)... use --platform (i.e. linux/amd64) to select one... containerd and OCI imports include all architectures and are unaffected
2026-10-10 09:36:57 WRN compatibility warning... docker... multi-arch index [dp.apps.rancher.io/containers/kubewarden-controller:1.38.2-14.2] saved without --platform
2026-10-10 09:36:57 WRN 'docker load' keeps only one architecture per tag (last wins)... use --platform (i.e. linux/amd64) to select one... containerd and OCI imports include all architectures and are unaffected
2026-10-10 09:36:57 WRN compatibility warning... docker... multi-arch index [dp.apps.rancher.io/containers/kubectl:1.37.0-3.1] saved without --platform
2026-10-10 09:36:57 WRN 'docker load' keeps only one architecture per tag (last wins)... use --platform (i.e. linux/amd64) to select one... containerd and OCI imports include all architectures and are unaffected
2026-10-10 09:36:57 WRN compatibility warning... docker... multi-arch index [dp.apps.rancher.io/containers/policy-reporter:3.10.0-13.1] saved without --platform
2026-10-10 09:36:57 WRN 'docker load' keeps only one architecture per tag (last wins)... use --platform (i.e. linux/amd64) to select one... containerd and OCI imports include all architectures and are unaffected
2026-10-10 09:36:57 WRN compatibility warning... docker... multi-arch index [dp.apps.rancher.io/containers/policy-reporter-ui:2.8.1-13.1] saved without --platform
2026-10-10 09:36:57 WRN 'docker load' keeps only one architecture per tag (last wins)... use --platform (i.e. linux/amd64) to select one... containerd and OCI imports include all architectures and are unaffected
2026-10-10 09:36:57 WRN compatibility warning... docker... multi-arch index [dp.apps.rancher.io/containers/kubewarden-audit-scanner:1.38.2-14.2] saved without --platform
2026-10-10 09:36:57 WRN 'docker load' keeps only one architecture per tag (last wins)... use --platform (i.e. linux/amd64) to select one... containerd and OCI imports include all architectures and are unaffected

To avoid this warning message, you can set the --platform CLI flag to define which platform architecture you want to save into the file.

Transfer the tarball into your isolated environment

Now that you have all the Admission Controller resources in kubewarden-resources.tar.zst, copy it into your air-gapped environment and load it into the Hauler store there:

hauler store load --filename kubewarden-resources.tar.zst
# Check if the resources are loaded
hauler store info

Now all the resources required to install Admission Controller are in the Hauler store in your isolated environment.

Populate private registry

To use the resources from your Hauler store, it’s necessary to make them available in an internal registry. You can use Hauler commands to copy them into your private registry.

hauler store copy registry://localhost:5000

You can also run Hauler to start a registry with all the resources from the store. This registry is insecure, and you need to adapt the cluster configuration:

# Find IP address of your host
# hostname -I

# Update registries.yaml for k3s based cluster to allow insecure access
# mirrors:
#   "<HOST_IP>:5000":
#     endpoint:
#       - "http://<HOST_IP>:5000"
# configs:
#   "<HOST_IP>:5000":
#     tls:
#       insecure_skip_verify: true

# Configure policy-server to allow pulling policies from insecure sources
# helm install .. suse-security-admission-controller .. --set policyServer.insecureSources[0]=<HOST_IP:5000>

hauler store serve registry

This starts a registry at the localhost:5000 address. From this point, you can use other commands like Skopeo to copy all the container images, policy modules and Helm charts used by Admission Controller into your private registry.

Install Admission Controller

Now that your private registry has everything required, you can install Admission Controller. The difference from a standard Admission Controller installation is that you need to change the registry in the container images and policies to be the private registry. Additionally, you need to install the Helm charts from OCI artifacts.

Install the Admission Controller stack:

helm install --wait -n suse-sec-adm-controller suse-sec-adm-controller --create-namespace \
    --set "global.imageRegistry=<REGISTRY.YOURDOMAIN.COM:PORT>" \
    oci://<REGISTRY.YOURDOMAIN.COM:PORT>/hauler/suse-security-admission-controller \
    --version 1.1.0

The global.imageRegistry value sets the registry of all the container images of the chart, including the images of the Policy Reporter sub-chart. If your private registry requires authentication, create an image pull secret in the suse-sec-adm-controller namespace and set it with global.imagePullSecrets.

To use the PolicyReporter sub-chart available in the suse-security-admission-controller chart, enable it with the auditScanner.policyReporter value. See an example below:

helm install --wait -n suse-sec-adm-controller suse-sec-adm-controller --create-namespace \
    oci://<REGISTRY.YOURDOMAIN.COM:PORT>/hauler/suse-security-admission-controller \
    --version 1.1.0 \
    --set global.imageRegistry=<REGISTRY.YOURDOMAIN.COM:PORT> \
    --set auditScanner.policyReporter=true

For more information about the policy report sub-chart values, refer to Policy Reporter documentation.

Finally, configure each Policy Server to fetch policies from your private registry. See the using private registry section of the documentation.

Now create Admission Controller policies in your cluster. Policies must be available in your private registry.

kubectl apply -f - <<EOF
apiVersion: policies.kubewarden.io/v1
kind: ClusterAdmissionPolicy
metadata:
  name: privileged-pods
spec:
  module: registry://<REGISTRY.YOURDOMAIN.COM:PORT>/kubewarden/policies/pod-privileged:v1.0.14
  rules:
  - apiGroups: [""]
    apiVersions: ["v1"]
    resources: ["pods"]
    operations:
    - CREATE
  mutating: false
EOF

PolicyServer resources must use the image available in your private registry. For example:

apiVersion: policies.kubewarden.io/v1
kind: PolicyServer
metadata:
  name: reserved-instance-for-tenant-a
spec:
  image: <REGISTRY.YOURDOMAIN.COM:PORT>/containers/kubewarden-policy-server:1.38.2-14.2
  replicas: 2
  serviceAccountName: sa