Skip to content
Geek is the Way!
Menu
  • Forums
  • Sobre o blog
  • Contato
  • English
Menu

Private PyPI with Docker Compose on RHEL 8.10

Posted on April 16, 2026April 16, 2026 by Thiago Crepaldi

Last Updated on April 16, 2026 by Thiago Crepaldi

For developers managing internal libraries or AI infrastructure, a private PyPI server is essential for hosting nightly builds without polluting public registries. devpi is the gold standard for this, specifically for its “volatile” index feature which allows for rapid iteration.

In this guide, we’ll deploy it on RHEL 8.10 using Docker Compose, rooting everything in /opt/local for clean service isolation.

Prerequisites

  • RHEL 8.10 with rootful Docker installed.
  • Docker Compose V2.
  • Sudo access for filesystem and firewall management.

Prepare the Host Filesystem

We will store both our configuration (docker-compose.yml) and the actual package data under /opt/local/devpi.

# Create the project structure
sudo mkdir -p /opt/local/devpi/data
sudo chmod 700 /opt/local/devpi/data
cd /opt/local/devpi

Configure the RHEL Firewall

By default, RHEL will block external access. Open 3141 persistently:

sudo firewall-cmd --permanent --add-port=3141/tcp
sudo firewall-cmd --reload

Create the Docker Compose Configuration

Create a docker-compose.yml file in /opt/local/devpi.

Critical RHEL Note: We use the :Z flag on the volume mount. This tells Docker to automatically relabel the host directory for SELinux compliance, preventing “Permission Denied” errors.

services:
  devpi:
    image: jonasal/devpi-server:latest
    container_name: devpi-server
    restart: always
    ports:
      - "3141:3141"
    volumes:
      - ./data:/data:Z
    environment:
      - DEVPI_PASSWORD=SetYourAdminPassword

Deploy the Service

sudo docker compose up -d

Configuring the “Nightly” Index

Standard PyPI indexes don’t allow version overwrites. For nightlies, we want a volatile index.

From your workstation (with devpi-client installed):

# 1. Connect and login
devpi use http://<your-server-ip>:3141
devpi login root --password=SetYourAdminPassword

# 2. Create a CI/CD user
devpi user -c build_bot password=bot-secret-pass

# 3. Create the volatile index
devpi index -c build_bot/nightly volatile=True

Enabling/Disabling PyPI.org Caching

One of devpi’s best features is its ability to act as a caching proxy for the official PyPI.

To Enable Caching (Inherit from PyPI):

If you want your index to find and cache public packages when they aren’t found locally:

devpi index build_bot/nightly bases=root/pypi

To Disable Caching (Private Only):

If you want to isolate your index so it only contains your internal packages (improving security by preventing dependency confusion attacks):

devpi index build_bot/nightly bases=""

Create a Dummy Package for Testing

Create a small internal utility library to verify the setup.

pyproject.toml:

[build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "geek-utils"
version = "0.1.0.dev1"
description = "Internal test library for GeekIsTheWay"
requires-python = ">=3.9"
dependencies = ["requests>=2.28"]

src/geek_utils/core.py:

import secrets

def get_geek_token():
    return f"GEEK-{secrets.token_hex(16)}"

Build and Publish Dummy Package for testing

Build the Package

pip install build
python -m build

Uploading via Twine

Option A: Explicit Command

twine upload --repository-url http://<your-server-ip>:3141/build_bot/nightly/ \
  -u build_bot -p bot-secret-pass dist/*

Option B: Using .pypirc (Recommended) Add this to your ~/.pypirc:

[nightly]
repository = http://<your-server-ip>:3141/build_bot/nightly/
username = build_bot
password = bot-secret-pass

Then run: twine upload -r nightly dist/*


Client-Side Configuration (pip & requirements.txt)

Set Global Configuration

pip config set global.index-url http://<your-server-ip>:3141/build_bot/nightly/+simple/
pip config set global.trusted-host <your-server-ip>

Using Requirements Files

requirements.txt (Mixed Mode)

--extra-index-url http://<your-server-ip>:3141/build_bot/nightly/+simple/
--trusted-host <your-server-ip>

geek-utils==0.1.0.dev1
requests>=2.28.0

Install Command Examples

Mixed Mode (devpi + PyPI.org): pip install --extra-index-url http://<your-server-ip>:3141/build_bot/nightly/+simple/ --trusted-host <your-server-ip> geek-utils

Strict Mode (Only use devpi): pip install --index-url http://<your-server-ip>:3141/build_bot/nightly/+simple/ --trusted-host <your-server-ip> geek-utils

Deleting Packages from the Registry

If a nightly build is corrupted or no longer needed, use the following commands from your workstation.

Delete a Specific Version

This is the most common task for cleaning up specific failed builds:

# Connect and login if not already
devpi use http://<your-server-ip>:3141/build_bot/nightly
devpi login build_bot --password bot-secret-pass

# Delete version 0.1.0.dev1 of geek-utils
devpi remove geek-utils==0.1.0.dev1

Delete an Entire Project

To remove every version of a project from the index:

devpi remove geek-utils

Bulk Cleanup (Pro-Tip)

While devpi doesn’t have a built-in “TTL” (Time To Live) for packages, you can use a simple bash loop with the client:

# Example: List versions and remove them (logic can be expanded for date filtering)
devpi list geek-utils

Troubleshooting & Maintenance

  • Docker Pull Limits: If you hit rate limits, run sudo docker login on the RHEL host.
  • SELinux: Verify labels with ls -Zd /opt/local/devpi/data.
  • Backup: sudo tar -czvf devpi-backup.tar.gz /opt/local/devpi.
  • Preventing Dependency Confusion
    • If a package exists in both your private index and root/pypi, devpi will prefer the local version if the versions are identical. However, pip will always try to download the highest version number it can find across all available indexes.
    • To fix this for your nightly builds:
      • Always use a higher version/suffix: Tag your nightlies with a suffix that PyPI.org wouldn’t have (e.g., 1.2.0.dev20260416).
      • Explicit Bases: If you are worried about a specific project, you can create an index with no bases specifically for that project, ensuring 0% chance of leaking to or from the public internet.

Share this:

  • Tweet

Related

Leave a ReplyCancel reply

LIKED? SUPPORT IT :)

Buy Me a Coffee


Search


Categories

  • Cooking (1)
  • Homelab (82)
    • APC UPS (6)
    • pfSense (41)
    • Plex (1)
    • Prometheus & Grafana (1)
    • Proxmox (21)
    • Shopping (1)
    • Supermicro (2)
    • Synology NAS (8)
    • Ubiquiti (6)
    • UDM-Pro (4)
  • Random (3)
  • Wordpress (1)

Tags

Agentless monitoring (3) AP9631 (3) Apache2 (3) APC UPS (6) Bind9 (3) certificates (5) DDNS (5) debian (3) DNS (7) DNSBL (2) DSM (6) Dynamic DNS (4) Firewall (9) gmail (3) Let's Encrypt Certificates (7) monitoring (19) networking (21) NMC (2) PBS (3) pfsense (43) port forwarding (3) privacy (2) proxmox (17) proxmox backup server (3) proxmox virtual environment (16) pve (5) rev202207eng (76) security (28) SNMP (4) SNMPv1 (3) ssh (4) SSL (6) Supermicro (2) Synology (7) udm-pro (5) unifi (6) unifi controller (3) unifi switch (2) UPS (5) VLAN (4) vpn (9) vpn server (2) wifi (4) Zabbix (18) Zabbix Agent2 (11)

See also

Privacy policy

Sitemap

©2026 Geek is the Way! | Design by Superb