Python

The Complete Guide to Private Python Package Repositories

Everything you need to know about private Python repositories: why teams need them, comparing options like PyPI server, Artifactory, Nexus, and CloudRepo, plus setup guides for pip, Poetry, and uv.

CloudRepo Team
14 min read

Every serious Python team eventually hits the same wall: you need to share code internally without publishing it to PyPI. Maybe it’s proprietary business logic, internal utilities, or packages that simply aren’t ready for public consumption. That’s when you need a private Python repository.

This guide covers everything: why you need one, how the options compare, and step-by-step configuration for pip, Poetry, and uv. By the end, you’ll know exactly which solution fits your team and how to set it up.

Why Teams Need Private Python Repositories

Before diving into solutions, let’s understand the problems private repositories solve.

1. Proprietary Code Protection

Your competitive advantage lives in your code. Publishing internal libraries to public PyPI exposes your intellectual property to competitors. A private repository keeps your algorithms, business logic, and internal tooling secure.

# This shouldn't be on public PyPI
from acme_ml_core import PropietaryPredictionModel
from acme_auth import InternalSSOClient
from acme_data import CompanyDataPipeline

2. Version Control Across Teams

When multiple teams depend on shared libraries, version chaos ensues without central management:

Terminal window
# Without a private repo, teams end up with:
team-a/requirements.txt: git+ssh://github.com/company/shared-lib.git@v1.2.3
team-b/requirements.txt: git+ssh://github.com/company/shared-lib.git@main
team-c/requirements.txt: /network/share/shared-lib-1.1.0.whl
# With a private repo, everyone uses:
requirements.txt: acme-shared-lib==1.2.3

3. CI/CD Pipeline Reliability

Git-based dependencies create fragile pipelines. Network issues, repository access changes, or branch deletions break builds unpredictably. A private repository provides stable, versioned artifacts that won’t disappear.

4. Compliance and Audit Requirements

Regulated industries need artifact traceability. Who published what, when, and which version is running in production? Private repositories provide audit logs, access controls, and retention policies that Git repositories lack.

5. Build Performance

Pulling from Git requires cloning entire repositories. A private PyPI repository serves pre-built wheels, dramatically reducing install times:

Terminal window
# Git dependency: Clone repo, install dependencies, build wheel
pip install git+https://github.com/company/big-repo.git
# Time: 45-120 seconds
# Private repository: Download pre-built wheel
pip install acme-big-package --index-url https://repo.company.com/simple
# Time: 2-5 seconds

Tip

Teams that switch from Git-based dependencies to private repositories often see CI/CD pipeline times drop by 50-70%, with the added benefit of reproducible builds.

Comparing Private Python Repository Options

Let’s examine the main options: self-hosted PyPI servers, enterprise solutions (Artifactory and Nexus), and managed services like CloudRepo.

Option 1: Self-Hosted PyPI Server

Tools like pypiserver or devpi let you run your own PyPI-compatible server.

Pros:

  • Full control over infrastructure
  • No external dependencies
  • Low direct costs (just server expenses)

Cons:

  • Significant setup and maintenance burden
  • Security patching is your responsibility
  • No built-in high availability
  • Limited enterprise features (RBAC, audit logs, SSO)

Best for: Teams with dedicated DevOps capacity who need air-gapped environments or have unusual customization requirements.

Option 2: JFrog Artifactory

Artifactory is the enterprise incumbent, supporting multiple package formats including Python.

Pros:

  • Mature enterprise features
  • Multi-format support (Maven, npm, Docker, PyPI)
  • Strong security scanning with Xray
  • Virtual repositories for caching public PyPI

Cons:

  • Consumption-based pricing creates unpredictable bills
  • Complexity often exceeds requirements
  • Expensive support contracts
  • Steep learning curve

Pricing reality: Artifactory’s $150/month base quickly escalates. With their consumption model (storage + transfer combined), a team with 100GB storage and moderate CI/CD usage can easily hit $600-800/month.

Option 3: Sonatype Nexus

Nexus Repository Manager is another enterprise option, originally focused on Maven but now supporting Python.

Pros:

  • Self-hosted option available
  • Multi-format support
  • Proxy caching for public repositories

Cons:

  • Python support less mature than Maven
  • Similar consumption-based pricing to Artifactory
  • Complex administration
  • Enterprise features require expensive tiers

Pricing reality: Nexus cloud starts at $135/month but follows a similar consumption model. Heavy CI/CD usage triggers substantial overage charges.

Option 4: CloudRepo

CloudRepo is a managed repository service built for simplicity and predictable pricing.

Pros:

  • No egress fees - download as much as your CI/CD needs
  • Predictable monthly pricing
  • Support included (real engineers, not call centers)
  • Simple setup in minutes
  • Native support for pip, Poetry, and uv

Cons:

  • Fewer advanced features than Artifactory
  • No self-hosted option

Pricing: CloudRepo charges based on storage only. The Team plan at $399/month includes 250GB storage with unmetered data transfer and unlimited users. No consumption surprises.

Quick Comparison Table

FeatureSelf-HostedArtifactoryNexusCloudRepo
Setup TimeDaysHoursHoursMinutes
MaintenanceHighMediumMediumNone
pip SupportYesYesYesYes
Poetry SupportBasicGoodBasicNative
uv SupportBasicBasicBasicNative
Egress FeesN/A$0.75-1.25/GB$0.90-1.10/GBNone
SSO/SAMLDIYExtra costExtra costIncluded
SupportNoneExtra costExtra costIncluded
Starting Price$100+/month (infra)$150/month + usage$135/month + usage$199/month

Important

The “real” cost of Artifactory and Nexus often surprises teams. Consumption fees, support contracts, and enterprise feature add-ons can triple the advertised price. Always calculate your expected usage before committing.

Setting Up pip with a Private Repository

pip is the standard Python package installer. Here’s how to configure it for private repositories.

Basic pip Configuration

Create or edit your pip configuration file:

Linux/macOS: ~/.config/pip/pip.conf or ~/.pip/pip.conf

Windows: %APPDATA%\pip\pip.ini

pip.conf
[global]
# Your private repository as the primary index
index-url = https://username:password@myorg.mycloudrepo.io/repositories/python/simple
# Fall back to public PyPI for packages not in your private repo
extra-index-url = https://pypi.org/simple
# Trust your repository's SSL certificate
trusted-host = myorg.mycloudrepo.io

Environment Variables for CI/CD

For automated environments, use environment variables instead of config files:

Environment Setup
# Set your private repository as the default
export PIP_INDEX_URL=https://username:$CLOUDREPO_TOKEN@myorg.mycloudrepo.io/repositories/python/simple
# Add PyPI as a fallback
export PIP_EXTRA_INDEX_URL=https://pypi.org/simple
# Now pip commands use your private repo automatically
pip install your-private-package
pip install requests # Falls back to PyPI

Per-Project Configuration

For project-specific settings, create a pip.conf in your project directory or use requirements files:

requirements.txt
--index-url https://myorg.mycloudrepo.io/repositories/python/simple
--extra-index-url https://pypi.org/simple
your-private-package==1.2.0
requests>=2.28.0
pydantic>=2.0.0

Publishing Packages with twine

To upload packages to your private repository:

Terminal window
# Install twine if you haven't already
pip install twine
# Build your package
python -m build
# Upload to CloudRepo
twine upload \
--repository-url https://myorg.mycloudrepo.io/repositories/python/ \
--username your-username \
--password $CLOUDREPO_TOKEN \
dist/*

Or configure twine in ~/.pypirc:

.pypirc
[distutils]
index-servers =
cloudrepo
[cloudrepo]
repository = https://myorg.mycloudrepo.io/repositories/python/
username = your-username
password = your-api-token

Then upload with:

Terminal window
twine upload --repository cloudrepo dist/*

Setting Up Poetry with a Private Repository

Poetry is the modern standard for Python dependency management. Its private repository support is excellent.

Configuring Poetry Sources

Add your private repository to pyproject.toml:

pyproject.toml
[tool.poetry]
name = "my-project"
version = "1.0.0"
description = "A project using private packages"
authors = ["Your Team <team@company.com>"]
[tool.poetry.dependencies]
python = "^3.10"
# Public packages - Poetry fetches from PyPI by default
requests = "^2.31.0"
pydantic = "^2.5.0"
# Private packages - Poetry fetches from CloudRepo
acme-shared-lib = "^1.2.0"
acme-auth = "^2.0.0"
# Configure your private repository
[[tool.poetry.source]]
name = "cloudrepo"
url = "https://myorg.mycloudrepo.io/repositories/python/simple"
priority = "supplemental"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

Poetry Source Priorities

Poetry 1.5+ uses priority settings to control package resolution:

pyproject.toml
# Primary: Check this source first for all packages
[[tool.poetry.source]]
name = "cloudrepo-primary"
url = "https://myorg.mycloudrepo.io/repositories/python-internal/simple"
priority = "primary"
# Supplemental: Only check for packages not found in default (PyPI)
[[tool.poetry.source]]
name = "cloudrepo-shared"
url = "https://myorg.mycloudrepo.io/repositories/python-shared/simple"
priority = "supplemental"
# Explicit: Only use when explicitly specified in dependency
[[tool.poetry.source]]
name = "cloudrepo-legacy"
url = "https://myorg.mycloudrepo.io/repositories/python-legacy/simple"
priority = "explicit"

Authentication Setup

Configure credentials for your private repository:

Terminal window
# Interactive configuration (stored securely)
poetry config http-basic.cloudrepo username $CLOUDREPO_TOKEN
# Or use environment variables (ideal for CI/CD)
export POETRY_HTTP_BASIC_CLOUDREPO_USERNAME=your-username
export POETRY_HTTP_BASIC_CLOUDREPO_PASSWORD=$CLOUDREPO_TOKEN

Publishing with Poetry

Configure a publishing repository:

pyproject.toml
[tool.poetry.repositories.cloudrepo]
url = "https://myorg.mycloudrepo.io/repositories/python/"

Then build and publish:

Terminal window
# Build wheel and sdist
poetry build
# Publish to CloudRepo
poetry publish -r cloudrepo
# Or build and publish in one command
poetry publish --build -r cloudrepo

Complete Poetry Example

Here’s a production-ready pyproject.toml for a team using CloudRepo:

pyproject.toml
[tool.poetry]
name = "order-service"
version = "2.1.0"
description = "Order processing microservice"
authors = ["Platform Team <platform@company.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
# Public dependencies
fastapi = "^0.109.0"
uvicorn = {extras = ["standard"], version = "^0.27.0"}
sqlalchemy = "^2.0.25"
# Private dependencies from CloudRepo
acme-auth = "^3.1.0"
acme-events = "^2.0.0"
acme-observability = "^1.5.0"
[tool.poetry.group.dev.dependencies]
pytest = "^7.4.4"
pytest-asyncio = "^0.23.3"
black = "^24.1.0"
ruff = "^0.1.14"
mypy = "^1.8.0"
[[tool.poetry.source]]
name = "cloudrepo"
url = "https://acme.mycloudrepo.io/repositories/python/simple"
priority = "supplemental"
[tool.poetry.repositories.cloudrepo]
url = "https://acme.mycloudrepo.io/repositories/python/"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

Setting Up uv with a Private Repository

uv is the blazing-fast Python package manager from the creators of Ruff. It’s gaining rapid adoption for its speed and pip compatibility.

Basic uv Configuration

uv reads pip configuration by default, so your existing pip.conf works. For uv-specific configuration:

Environment Variables
# Primary index (your private repository)
export UV_INDEX_URL=https://username:$CLOUDREPO_TOKEN@myorg.mycloudrepo.io/repositories/python/simple
# Additional indexes (PyPI as fallback)
export UV_EXTRA_INDEX_URL=https://pypi.org/simple
# Install packages
uv pip install your-private-package
uv pip install -r requirements.txt

uv Configuration File

Create a uv.toml or .uv/uv.toml file for project configuration:

uv.toml
[pip]
index-url = "https://myorg.mycloudrepo.io/repositories/python/simple"
extra-index-url = ["https://pypi.org/simple"]
[pip.auth]
myorg.mycloudrepo.io = { username = "your-username", password = "${CLOUDREPO_TOKEN}" }

uv with pyproject.toml

uv works seamlessly with PEP 621 pyproject.toml files:

pyproject.toml
[project]
name = "my-service"
version = "1.0.0"
requires-python = ">=3.10"
dependencies = [
"requests>=2.28.0",
"pydantic>=2.0.0",
"acme-shared-lib>=1.2.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"black>=24.0.0",
]
[tool.uv]
index-url = "https://myorg.mycloudrepo.io/repositories/python/simple"
extra-index-url = ["https://pypi.org/simple"]

uv in CI/CD

uv’s speed makes it ideal for CI/CD pipelines:

.github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v1
- name: Install dependencies
env:
UV_INDEX_URL: https://user:${{ secrets.CLOUDREPO_TOKEN }}@myorg.mycloudrepo.io/repositories/python/simple
UV_EXTRA_INDEX_URL: https://pypi.org/simple
run: |
uv venv
uv pip install -e ".[dev]"
- name: Run tests
run: |
source .venv/bin/activate
pytest

Performance Comparison

uv’s performance advantage is dramatic:

Terminal window
# Installing a project with 50 dependencies
# pip (traditional)
time pip install -r requirements.txt
# real 45.2s
# uv (with CloudRepo CDN)
time uv pip install -r requirements.txt
# real 3.8s
# That's 12x faster

Success

uv combined with CloudRepo’s CDN-backed infrastructure delivers the fastest possible Python package installation. Teams report CI/CD pipeline speedups of 80-90% after switching.

Best Practices for Package Versioning and Security

Semantic Versioning

Adopt semantic versioning (semver) for predictable upgrades:

MAJOR.MINOR.PATCH
1.0.0 - Initial release
1.1.0 - New feature (backward compatible)
1.1.1 - Bug fix
2.0.0 - Breaking change

Configure your dependencies to match:

pyproject.toml
[tool.poetry.dependencies]
# Allow patches only (safest for production)
critical-lib = "~1.2.0" # Allows 1.2.x
# Allow minor updates (good for active development)
utility-lib = "^1.2.0" # Allows 1.x.x
# Pin exactly (for reproducibility)
legacy-lib = "1.2.3" # Exactly 1.2.3

Security Best Practices

1. Use API tokens, not passwords:

Terminal window
# Generate read-only tokens for CI/CD
# Generate publish tokens only for release pipelines
# Rotate tokens regularly

2. Separate repositories by access level:

production-releases/ - Only tested, approved packages
staging/ - Pre-release versions for testing
development/ - Work-in-progress packages

3. Audit your dependencies:

Terminal window
# Check for known vulnerabilities
pip-audit
# Or with Safety
safety check -r requirements.txt

4. Lock your dependencies:

Always commit your lock files (poetry.lock, requirements.txt with pinned versions) to ensure reproducible builds across environments.

Package Naming Conventions

Avoid naming collisions with public packages:

# Good: Company prefix prevents confusion
acme-auth
acme-data-pipeline
acme-ml-models
# Bad: Could conflict with public packages
auth
data-pipeline
models

Cost Comparison: The Real Numbers

Let’s calculate actual costs for a typical development team.

Scenario: 15 Developers, Active CI/CD

Usage profile:

  • 100 GB stored artifacts
  • 400 GB monthly downloads (CI/CD + developers)
  • 50 GB monthly uploads

JFrog Artifactory Cost

Base price (Pro): $150/month
Consumption: 100 + 400 + 50 = 550 GB
First 25 GB: Included
Next 525 GB at $1.25/GB: $656.25/month
─────────────
Monthly total: $806.25
Annual total: $9,675

Sonatype Nexus Cost

Base price (Pro): $135/month
Consumption: 100 + 400 + 50 = 550 GB
First 20 GB: Included
Next 530 GB at $1.10/GB: $583/month
─────────────
Monthly total: $718
Annual total: $8,616

CloudRepo Cost

Team plan (250 GB storage): $399/month
Data transfer: Included
Users: Unlimited
Support: Included
─────────────
Monthly total: $399
Annual total: $4,788

Annual Savings

ProviderAnnual CostSavings vs CloudRepo
Artifactory$9,675-
Nexus$8,616-
CloudRepo$4,788$4,887 vs Artifactory

Important

CloudRepo’s Team plan saves this team nearly $5,000 annually compared to Artifactory. For teams with heavier CI/CD usage, savings can exceed $10,000/year. And unlike consumption-based pricing, CloudRepo’s bills are predictable every month.

Hidden Costs Not Included Above

Artifactory/Nexus additional expenses:

  • Enterprise support contracts: $5,000-15,000/year
  • SSO/SAML integration: Enterprise tier required
  • Advanced security features: Higher tier required
  • Training and expertise: Internal DevOps time

CloudRepo includes:

  • All features on every plan
  • Human support from engineers
  • SSO/SAML (coming mid-January 2026)
  • No surprise bills

Migration Guide: Moving to a Private Repository

From Git Dependencies

If you’re currently using Git URLs in requirements:

Before: requirements.txt
git+ssh://git@github.com/company/shared-lib.git@v1.2.3
git+https://github.com/company/auth-lib.git@main
  1. Build and publish packages to CloudRepo
  2. Update your requirements:
After: requirements.txt
--index-url https://myorg.mycloudrepo.io/repositories/python/simple
--extra-index-url https://pypi.org/simple
acme-shared-lib==1.2.3
acme-auth-lib==2.0.0

From Self-Hosted PyPI

Export your existing packages and upload to CloudRepo:

migrate.py
import os
import requests
from pathlib import Path
CLOUDREPO_URL = "https://myorg.mycloudrepo.io/repositories/python/"
CLOUDREPO_TOKEN = os.environ["CLOUDREPO_TOKEN"]
def upload_package(wheel_path: Path):
with open(wheel_path, 'rb') as f:
response = requests.post(
CLOUDREPO_URL,
files={'content': (wheel_path.name, f)},
auth=('token', CLOUDREPO_TOKEN)
)
return response.status_code == 201
# Upload all wheels from your old repository
for wheel in Path("/var/pypi/packages").glob("**/*.whl"):
if upload_package(wheel):
print(f"Uploaded: {wheel.name}")
else:
print(f"Failed: {wheel.name}")

From Artifactory/Nexus

Both Artifactory and Nexus support package export. Download your Python packages and upload to CloudRepo using the migration script above, or contact CloudRepo support for assisted migration.

Getting Started with CloudRepo

Ready to simplify your Python package management? Here’s how to get started:

1. Sign Up

Create a free account at cloudrepo.io/signup. No credit card required for the trial.

2. Create a Python Repository

Terminal window
# Via the web UI: Click "New Repository" > Select "Python"
# Or via API:
curl -X POST https://api.cloudrepo.io/v1/repositories \
-H "Authorization: Bearer $CLOUDREPO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "python", "type": "python"}'

3. Configure Your Tools

pip:

Terminal window
pip config set global.index-url https://yourorg.mycloudrepo.io/repositories/python/simple

Poetry:

Terminal window
poetry source add cloudrepo https://yourorg.mycloudrepo.io/repositories/python/simple

uv:

Terminal window
export UV_INDEX_URL=https://yourorg.mycloudrepo.io/repositories/python/simple

4. Upload Your First Package

Terminal window
twine upload \
--repository-url https://yourorg.mycloudrepo.io/repositories/python/ \
dist/*

That’s it. Your private Python repository is ready.


Questions about setting up your private Python repository? Our engineering team provides hands-on support for every customer. Start your 14-day free trial or contact us for a personalized walkthrough. We’ll help you get set up in minutes, not days.

Ready to save 90% on your repository hosting?

Join thousands of teams who've switched to CloudRepo for better pricing and features.

Related Articles

Python

CloudRepo vs PyPI Server: Python Repo Comparison

Comparing self-hosted PyPI server with CloudRepo for Python package management. Learn why teams choose CloudRepo as their PyPI server alternative for private Python repositories.

9 min read Read more →