Building a Multi-Architecture Air-Gapped Python Package Server

In high security environments, systems often operate without internet access. This creates a major challenge for Python development because package managers, like pip, normally retrieve packages and dependencies from the public Python Package Index (PyPI).

An air gapped Python package server solves this problem by creating an internal repository that hosts approved Python packages locally. Systems inside the secure network install packages from this internally vetted source instead of the internet. This also helps secure the development cycle by forcing developers to utilize approved packages.

This guide walks through creating a secure multi-architecture, multi-version offline PyPI mirror using a staging system and an internal package server.

Overview

The workflow follows a controlled supply chain model:

  1. Internet staging systems download packages and dependencies.
  2. Packages are scanned, vetted, and approved.
  3. Approved packages are transferred to the air gap server.
  4. Internet server hosts available packages via HTTP
  5. Offline systems (Windows or Linux) install packages using pip from the internal repository.

Step 1: Stage an Internet Connected System

The staging system downloads packages safely using python virtual environments, which isolates Python versions and prevents conflicts.

This system should:

  • Be isolated from production environments
  • Be regularly patched
  • Contain malware scanning tools
  • Never directly connect to the air gapped network

Install Required Versions of Python

dnf install python3.8 python3.10 python3-pip

Create a Virtual Environment:

Note: The below procedures requires that specific version of Python to be installed or available prior to use.

Linux Systems – For each Python Version:

#Example for Python 3.10 - Repeat for all desired others

#create Virtual environment
python3.10 -m venv /opt/venvs/py310

#Activate the environment
source /opt/venvs/py310/bin/activate

#Upgrade pip within the environment and deactivate the environment
pip install --upgrade pip setuptools wheel
deactivate

#Create package staging area
mkdir -p /opt/pypi-mirror/linux/py310

Windows Systems – For each Python Version:

# Example for Python 3.10 - Repeat for all desired others

# Create Virtual Environment
# Adjust path to where you want the venv
python3.10 -m venv C:\venvs\py310

# Activate the environment
# PowerShell

C:\venvs\py310\Scripts\Activate.ps1
# (If using cmd.exe, use C:\venvs\py310\Scripts\activate.bat)

# Upgrade pip, setuptools, wheel within the environment
pip install --upgrade pip setuptools wheel

# Deactivate the environment
deactivate

# Create package staging area
# PowerShell
New-Item -ItemType Directory -Force -Path C:\pypi-mirror\windows\py310

# (Or cmd.exe: mkdir C:\pypi-mirror\windows\py310)

Download Packages and Dependencies

Use pip download instead of pip install. This retrieves packages without installing them. Ensure the correct virtual environment is activated from above.

Linux Packages:

#Activate virtual environment
source /opt/venvs/py310/bin/activate

#Download packages to staging area using a requirements.txt file
pip download -r requirements.txt -d /opt/pypi-mirror/linux/py310

#Download packages to staging area calling out specific packages
pip download torch pytorch -d /opt/pypi-mirror/linux/py310

Windows Packages:

# Activate the virtual environment
# PowerShell
C:\venvs\py310\Scripts\Activate.ps1
# (If using cmd.exe, use C:\venvs\py310\Scripts\activate.bat)

# Download packages from requirements.txt to staging area
pip download -r requirements.txt -d C:\pypi-mirror\windows\py310

This package now contains target packages, all dependency wheels, and source archives if wheels are unavailable.

Step 2: Build the Air Gap Package Server (RedHat Example)

Inside the air gapped environment, create a simple internal PyPI repository.

Install Python

dnf install python3 python3-pip httpd

Create repository directories for each version of Python needed

mkdir -p /opt/pypi/packages/linux/py38
mkdir -p /opt/pypi/packages/windows/py310
chown -R apache:apache /opt/pypi

Install PyPI Server

Transfer the applicable pypiserver wheel package to the staging system

pip install pypiserver

Create a Systemd Service

# vim /etc/systemd/system/pypiserver.service

[Unit]
Description=Air Gap PyPI Server
After=network.target

[Service]
User=apache
Group=apache
ExecStart=/usr/local/bin/pypi-server run \
    -p 8080 \
    /opt/pypi/packages
Restart=always

[Install]
WantedBy=multi-user.target

Enable the service, create firewall rules, and set SELinux Contexts:

#Enable service and check status
systemctl daemon-reload
systemctl enable --now pypiserver
systemctl status pypiserver

#Add permanent firewall rules for TCP 8080 and reload firewall
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --reload

#Set SELinux contexts
semanage fcontext -a -t httpd_sys_content_t "/opt/pypi(/.*)?"
restorecon -Rv /opt/pypi
setsebool -P httpd_can_network_connect 1

Step 3: Scan and Vet Packages

Before crossing the air gap environment

  1. Conduct a malware scan of the package repo
  2. Conduct a dependency audit
  3. Review any known package Common Vulnerabilities and Exposures (CVEs)
  4. Verify package checksums
    • sha256 ~/pypi-mirror/* > SHA256SUMS.txt
  5. Gain approval for introduction if necessary

Step 4: Transfer Packages to Air-Gapped Server

Use secure transfer procedures to copy the new packages to the air-gapped server.

Copy Packages to applicable repos:

rsync -av ~/pypi-mirror/linux/py310 /opt/pypi/packages/linux/py310
rsync -av ~/pypi-mirror/windows/py38 /opt/pypi/packages/windows/py38

Verify Checksums

sha256sum -c SHA256SUMS.txt

Restart PyPI server to refresh the index:

systemctl restart pypiserver

Step 5: Install Packages from Air-Gap Server

Linux Client Example

pip install flask --index-url http://pypi-server:8080/simple/linux/py310/   --trusted-host pypi-server

Windows Client Example

pip install flask --index-url http://pypi-server:8080/simple/windows/py310/ --trusted-host pypi-server

Establish a permanent pip configuration (optional)

Use the below Linux and Windows configs for developers to hard-code their server defaults

Liunx:

# vim ~/.config/pip/pip.conf
[global]
index-url = http://pypi-server:8080/simple/linux/py310/
trusted-host = pypi-server

Windows:

# Edit %APPDATA%\pip\pip.ini
[global]
index-url = http://pypi-server:8080/simple/windows/py310/
trusted-host = pypi-server

Operational Recommendations

  • Maintain separate directories for each Python version and OS.
  • Use virtual environments to isolate downloads on the staging system(s).
  • Keep a pinned dependency list for reproducibility.
  • Document package versions and audit logs.
  • Rebuild and refresh repository regularly.
  • Separate staging from production to maintain security.

Security Benefits

  • Full supply chain control
  • Reduced malware exposure
  • Deterministic builds across multiple Python versions
  • Supports Linux and Windows clients in offline environments
  • Simplifies compliance for secure networks

Prev Next