> For the complete documentation index, see [llms.txt](https://docs.platform9.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.platform9.com/private-cloud-director/2025.10/getting-started/self-hosted/self-hosted-airgap-install.md).

# Airgap Install

Air-gapped mode is required when deployment nodes cannot directly access the Internet.

### In this mode, you will:

* Host a **private APT repository** for Ubuntu dependencies.
* Host a **private APT repository** for Ubuntu dependencies.
* Download **airctl artifacts** from an Internet-connected system like a jump host and transfer them to your air-gapped environment.

After preparing these components, proceed to the airctl install to configure and deploy PCD using `airctl`.

### Pre-requisites

For management plane host configuration, follow the [pre-requisite](/private-cloud-director/2025.10/getting-started/self-hosted/self-hosted-pre-requisites.md) section, except for package updates and OpenSSL installation. These steps will be covered later in this document.

### NTP Configuration (Client-Side)

{% hint style="info" %}
**Info**

An NTP server must already be available and reachable in the customer’s network.

All nodes must be configured as NTP clients to sync time with this server.
{% endhint %}

If NTP is not already configured on the client nodes, follow the steps below to point them to the NTP server.

Point node to the existing NTP server:

{% tabs %}
{% tab title="Bash" %}

```bash
sudo mkdir -p /etc/systemd/timesyncd.conf.d echo "[Time] NTP=<ntp-server-ip-or-fqdn>" | sudo tee /etc/systemd/timesyncd.conf.d/custom.conf
#Restart service:
sudo systemctl restart systemd-timesyncd sudo systemctl enable systemd-timesyncd
#Verify Sync
timedatectl status timedatectl show-timesync --all
```

{% endtab %}
{% endtabs %}

### Set Up Private APT Repository

Download sample scripts and package dependency list on the apt repo host.

{% tabs %}
{% tab title="Bash" %}

```bash
#Script to create a repo
curl --user-agent "<TOKEN>" -O https://pf9-airctl.s3.us-west-1.amazonaws.com/latest/sample_scripts/create_apt_repo.sh
#Script to download pacakge dependencies
curl --user-agent "<TOKEN>" -O https://pf9-airctl.s3.us-west-1.amazonaws.com/latest/sample_scripts/download_all_deps.sh
#Dependency list
curl --user-agent "<TOKEN>" -O https://pf9-airctl.s3.us-west-1.amazonaws.com/latest/dependency_list.txt
```

{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="Bash" %}

```bash
chmod +x create_apt_repo.sh download_all_deps.sh
```

{% endtab %}
{% endtabs %}

download required packages on your repo hosts which should have internet connectivity.

{% tabs %}
{% tab title="Bash" %}

```bash
./download_all_deps.sh <path-to-dependency-list.txt>
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
**Info**\
If using HTTPS with a custom CA, install the CA into /usr/local/share/ca-certificates and run update-ca-certificates.
{% endhint %}

#### Initialise the repository with the suitable options from the below.

#### Option 1 — HTTP (Insecure)

Initialise the Repository

{% tabs %}
{% tab title="Bash" %}

```bash
sudo ./create_apt_repo.sh init
sudo ./create_apt_repo.sh add-bulk ./deb_packages
```

{% endtab %}
{% endtabs %}

#### Option 2 — HTTPS (Secure, Recommended)

Initialise with Self-Signed Cert

{% tabs %}
{% tab title="Bash" %}

```bash
sudo ./scripts/create_apt_repo.sh init-https repo.local
sudo ./create_apt_repo.sh add-bulk ./deb_packages
```

{% endtab %}
{% endtabs %}

Or if you already have a cert/key:

{% tabs %}
{% tab title="Bash" %}

```bash
sudo ./scripts/create_apt_repo.sh init-https repo.example.com /path/to/cert.pem /path/to/key.pem
sudo ./create_apt_repo.sh add-bulk ./deb_packages
```

{% endtab %}
{% endtabs %}

### Configure each pcd nodes with apt repo:

If self-signed, distribute and trust the CA:

{% tabs %}
{% tab title="Bash" %}

```bash
# Update CA certificates 
sudo cp /path/to/apt_repo.crt /usr/local/share/ca-certificates/ 
sudo update-ca-certificates
```

{% endtab %}
{% endtabs %}

Add apt repo on each PCD hosts including compute hosts.

{% tabs %}
{% tab title="Bash" %}

```bash
# Backup existing sources list
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
sudo mkdir -p /etc/apt/sources.list.d.bak
sudo mv /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d.bak/ 2>/dev/null || true
sudo rm /etc/apt/sources.list
# Add private repo over HTTPS 
echo "deb [trusted=yes] http://<repo-host>/ stable main" | sudo tee /etc/apt/sources.list.d/private-repo.list
sudo apt update
```

{% endtab %}
{% endtabs %}

## Private Docker Registry Setup

Sample script to setup an Image Repository on a Node:

{% tabs %}
{% tab title="Bash" %}

```bash
curl --user-agent "<TOKEN>" -O https://pf9-airctl.s3-accelerate.amazonaws.com/latest/pcdv-images.txt
curl --user-agent "<TOKEN>" -O https://pf9-airctl.s3.us-west-1.amazonaws.com/latest/sample_scripts/setup_registry.sh
curl --user-agent "<TOKEN>" -O https://pf9-airctl.s3.us-west-1.amazonaws.com/latest/push-images.sh
```

{% endtab %}
{% endtabs %}

Run script setup\_registry.sh to create docker registry, provide credentials when prompted.

{% tabs %}
{% tab title="Bash" %}

```bash
chmod +x setup_registry.sh
./setup_registry.sh
```

{% endtab %}
{% endtabs %}

### Upload Images to Private Registry

{% hint style="info" %}
**NOTE**

Docker must be installed for this script to run.
{% endhint %}

#### Configure Docker to Trust the Private Registry

Prior to pushing container images, add the registry’s CA certificate to Docker’s trust store and restart the service:

{% tabs %}
{% tab title="Bash" %}

```bash
sudo mkdir -p /etc/docker/certs.d/<registry_url>:443
sudo cp /usr/local/share/ca-certificates/ca.crt /etc/docker/certs.d/<registry_url>:443/ca-cert.pem
sudo systemctl restart docker
```

{% endtab %}
{% endtabs %}

push images using the recommended script push-images.sh

{% tabs %}
{% tab title="Bash" %}

```bash
./push-images.sh --images-file-path <image_list_path> --private-registry-url <registry_url> --private-registry-username=<registry_username> --private-registry-password=<registry_password>
```

{% endtab %}
{% endtabs %}

## Install OpenSSL

On a **server** with internet access:

{% tabs %}
{% tab title="Bash" %}

```bash
curl --user-agent "<YOUR_USER_AGENT_KEY>" \ https://pf9-airctl.s3-accelerate.amazonaws.com/openssl-smcp-ubuntu/openssl_3.0.7-1_amd64.deb \ --output openssl_3.0.7-1_amd64.deb
```

{% endtab %}
{% endtabs %}

Copy to all nodes and install:

{% tabs %}
{% tab title="Bash" %}

```bash
# Verify the MD5 checksum
md5sum openssl_3.0.7-1_amd64.deb | grep 706caf \
  || { echo "MD5 checksum does not match, exiting."; exit 1; }

# Install the OpenSSL package
sudo dpkg -i openssl_3.0.7-1_amd64.deb \
  || { echo "Failed to install OpenSSL, exiting."; exit 1; }

# Add OpenSSL library path
echo "/usr/local/ssl/lib64" | sudo tee /etc/ld.so.conf.d/openssl-3.0.7.conf

# Refresh dynamic linker cache
sudo ldconfig -v

# Create a symbolic link to the new OpenSSL binary
sudo ln -sf /usr/local/ssl/bin/openssl /usr/bin/openssl

# Verify the OpenSSL version
openssl version | grep 3.0.7 \
  || { echo "OpenSSL version does not match, exiting."; exit 1; }
```

{% endtab %}
{% endtabs %}

Expected output:

`OpenSSL 3.0.7 1 Nov 2022 (Library: OpenSSL 3.0.7 1 Nov 2022)`

#### Package Updates

Install `cgroup-tools` on each cluster nodes

{% tabs %}
{% tab title="Bash" %}

```bash
apt-get update -y && apt-get install cgroup-tools -y
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
**Info**\
At this stage, the private registry and APT repository have been fully configured. Please review the pre-requisite documentation to ensure all required dependencies and conditions are satisfied before proceeding.
{% endhint %}

## Configure and Deploy PCD

{% hint style="info" %}
**DNS Prerequisite for Images and Repo**\
If the registry hostname is **not resolvable** in customer DNS, add it to /etc/hosts on each node.
{% endhint %}

Step 1: Download the Installer Script.

Run on a **server** with internet access:

{% tabs %}
{% tab title="Bash" %}

```bash
curl --user-agent "<YOUR_USER_AGENT_KEY>" \ https://pf9-airctl.s3-accelerate.amazonaws.com/latest/index.txt | \ awk '{print "curl -sS --user-agent \"<YOUR_USER_AGENT_KEY>\" \"https://pf9-airctl.s3-accelerate.amazonaws.com/latest/" $NF "\" -o ${HOME}/" $NF}' | bash
```

{% endtab %}
{% endtabs %}

Copy all fetched artifacts to one of the master nodes.

#### Step 2: Make the Installer Executable

Set the execute permissions on the installation script.

{% tabs %}
{% tab title="Bash" %}

```bash
chmod +x ./install-pcd.sh
```

{% endtab %}
{% endtabs %}

#### Step 3: Run the Installation Script

Execute the installer with the specified version. This runs the installer using the version number found in `version.txt`.

{% tabs %}
{% tab title="Bash" %}

```bash
./install-pcd.sh $(cat version.txt)
```

{% endtab %}
{% endtabs %}

#### Step 4: Add airctl to System Path

Add `airctl` to the system path to use it globally by creating a symlink in `/usr/bin` folder.

{% tabs %}
{% tab title="Bash" %}

```bash
sudo ln -s /opt/pf9/airctl/airctl /usr/bin/airctl
```

{% endtab %}
{% endtabs %}

### Configure airctl

{% hint style="info" %}
**NOTE**\
Copy the `ca.crt` file from the registry to the node where you will run the `airctl` CLI, and provide its path using the `--custom-registry-ca-cert-path` flag when running `airctl configure`.
{% endhint %}

Run the following command to generate a configuration file, which will be used to deploy the Self-hosted Private Cloud Director management cluster.

You can choose between a `single-master` or `multi-master` management cluster, depending on your installation type (POC or production).

{% tabs %}
{% tab title="Bash" %}

```bash
airctl configure \
  -4 \
  -f <fqdn> \
  -e <du-public-ip> \
  -i <comma-separated master node IPs> \
  --master-vip4 <vip-for-nodelet-cluster> \
  -v ens3 \
  -r <du-region-name> \
  -k <nfs-host-ip-for-hostpath-provisioner> \
  -n /mnt/gnocchi \
  -p hostpath-provisioner \
  --custom-registry-url https://registry.pf9.io \
  --custom-registry-username <registru_username> \
  --custom-registry-password "<registry_password>" \
  --custom-registry-ca-cert-path <path-to-registries-ca.crt> \
  --custom-registry-path-overrides \
  --enable-pcd-chart-bundle \
  --verbose
```

{% endtab %}
{% endtabs %}

You have now completed the airgap–specific configuration. From this point onward, follow the standard steps after `airctl configure` as documented in the [*Install*](/private-cloud-director/2025.10/getting-started/self-hosted/self-hosted-install.md) section.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.platform9.com/private-cloud-director/2025.10/getting-started/self-hosted/self-hosted-airgap-install.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
