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:
- Internet staging systems download packages and dependencies.
- Packages are scanned, vetted, and approved.
- Approved packages are transferred to the air gap server.
- Internet server hosts available packages via HTTP
- 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
- Conduct a malware scan of the package repo
- Conduct a dependency audit
- Review any known package Common Vulnerabilities and Exposures (CVEs)
- Verify package checksums
sha256 ~/pypi-mirror/* > SHA256SUMS.txt
- 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