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 PyPIfrom acme_ml_core import PropietaryPredictionModelfrom acme_auth import InternalSSOClientfrom acme_data import CompanyDataPipeline2. Version Control Across Teams
When multiple teams depend on shared libraries, version chaos ensues without central management:
# Without a private repo, teams end up with:team-a/requirements.txt: git+ssh://github.com/company/shared-lib.git@v1.2.3team-b/requirements.txt: git+ssh://github.com/company/shared-lib.git@mainteam-c/requirements.txt: /network/share/shared-lib-1.1.0.whl
# With a private repo, everyone uses:requirements.txt: acme-shared-lib==1.2.33. 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:
# Git dependency: Clone repo, install dependencies, build wheelpip install git+https://github.com/company/big-repo.git# Time: 45-120 seconds
# Private repository: Download pre-built wheelpip install acme-big-package --index-url https://repo.company.com/simple# Time: 2-5 secondsTip
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
| Feature | Self-Hosted | Artifactory | Nexus | CloudRepo |
|---|---|---|---|---|
| Setup Time | Days | Hours | Hours | Minutes |
| Maintenance | High | Medium | Medium | None |
| pip Support | Yes | Yes | Yes | Yes |
| Poetry Support | Basic | Good | Basic | Native |
| uv Support | Basic | Basic | Basic | Native |
| Egress Fees | N/A | $0.75-1.25/GB | $0.90-1.10/GB | None |
| SSO/SAML | DIY | Extra cost | Extra cost | Included |
| Support | None | Extra cost | Extra cost | Included |
| 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
[global]# Your private repository as the primary indexindex-url = https://username:password@myorg.mycloudrepo.io/repositories/python/simple
# Fall back to public PyPI for packages not in your private repoextra-index-url = https://pypi.org/simple
# Trust your repository's SSL certificatetrusted-host = myorg.mycloudrepo.ioEnvironment Variables for CI/CD
For automated environments, use environment variables instead of config files:
# Set your private repository as the defaultexport PIP_INDEX_URL=https://username:$CLOUDREPO_TOKEN@myorg.mycloudrepo.io/repositories/python/simple
# Add PyPI as a fallbackexport PIP_EXTRA_INDEX_URL=https://pypi.org/simple
# Now pip commands use your private repo automaticallypip install your-private-packagepip install requests # Falls back to PyPIPer-Project Configuration
For project-specific settings, create a pip.conf in your project directory or use requirements files:
--index-url https://myorg.mycloudrepo.io/repositories/python/simple--extra-index-url https://pypi.org/simple
your-private-package==1.2.0requests>=2.28.0pydantic>=2.0.0Publishing Packages with twine
To upload packages to your private repository:
# Install twine if you haven't alreadypip install twine
# Build your packagepython -m build
# Upload to CloudRepotwine upload \ --repository-url https://myorg.mycloudrepo.io/repositories/python/ \ --username your-username \ --password $CLOUDREPO_TOKEN \ dist/*Or configure twine in ~/.pypirc:
[distutils]index-servers = cloudrepo
[cloudrepo]repository = https://myorg.mycloudrepo.io/repositories/python/username = your-usernamepassword = your-api-tokenThen upload with:
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:
[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 defaultrequests = "^2.31.0"pydantic = "^2.5.0"# Private packages - Poetry fetches from CloudRepoacme-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:
# 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:
# 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-usernameexport POETRY_HTTP_BASIC_CLOUDREPO_PASSWORD=$CLOUDREPO_TOKENPublishing with Poetry
Configure a publishing repository:
[tool.poetry.repositories.cloudrepo]url = "https://myorg.mycloudrepo.io/repositories/python/"Then build and publish:
# Build wheel and sdistpoetry build
# Publish to CloudRepopoetry publish -r cloudrepo
# Or build and publish in one commandpoetry publish --build -r cloudrepoComplete Poetry Example
Here’s a production-ready pyproject.toml for a team using CloudRepo:
[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 dependenciesfastapi = "^0.109.0"uvicorn = {extras = ["standard"], version = "^0.27.0"}sqlalchemy = "^2.0.25"# Private dependencies from CloudRepoacme-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:
# 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 packagesuv pip install your-private-packageuv pip install -r requirements.txtuv Configuration File
Create a uv.toml or .uv/uv.toml file for project configuration:
[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:
[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:
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 pytestPerformance Comparison
uv’s performance advantage is dramatic:
# 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 fasterSuccess
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 release1.1.0 - New feature (backward compatible)1.1.1 - Bug fix2.0.0 - Breaking changeConfigure your dependencies to match:
[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.3Security Best Practices
1. Use API tokens, not passwords:
# Generate read-only tokens for CI/CD# Generate publish tokens only for release pipelines# Rotate tokens regularly2. Separate repositories by access level:
production-releases/ - Only tested, approved packagesstaging/ - Pre-release versions for testingdevelopment/ - Work-in-progress packages3. Audit your dependencies:
# Check for known vulnerabilitiespip-audit
# Or with Safetysafety check -r requirements.txt4. 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 confusionacme-authacme-data-pipelineacme-ml-models
# Bad: Could conflict with public packagesauthdata-pipelinemodelsCost 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/monthConsumption: 100 + 400 + 50 = 550 GBFirst 25 GB: IncludedNext 525 GB at $1.25/GB: $656.25/month ─────────────Monthly total: $806.25Annual total: $9,675Sonatype Nexus Cost
Base price (Pro): $135/monthConsumption: 100 + 400 + 50 = 550 GBFirst 20 GB: IncludedNext 530 GB at $1.10/GB: $583/month ─────────────Monthly total: $718Annual total: $8,616CloudRepo Cost
Team plan (250 GB storage): $399/monthData transfer: IncludedUsers: UnlimitedSupport: Included ─────────────Monthly total: $399Annual total: $4,788Annual Savings
| Provider | Annual Cost | Savings 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:
git+ssh://git@github.com/company/shared-lib.git@v1.2.3git+https://github.com/company/auth-lib.git@main- Build and publish packages to CloudRepo
- Update your requirements:
--index-url https://myorg.mycloudrepo.io/repositories/python/simple--extra-index-url https://pypi.org/simple
acme-shared-lib==1.2.3acme-auth-lib==2.0.0From Self-Hosted PyPI
Export your existing packages and upload to CloudRepo:
import osimport requestsfrom 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 repositoryfor 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
# 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:
pip config set global.index-url https://yourorg.mycloudrepo.io/repositories/python/simplePoetry:
poetry source add cloudrepo https://yourorg.mycloudrepo.io/repositories/python/simpleuv:
export UV_INDEX_URL=https://yourorg.mycloudrepo.io/repositories/python/simple4. Upload Your First Package
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.