How to Setup Custom PyPI Index URL in Different Environments
How to Setup Custom PyPI Index URL in Different Environments
When working with private Python packages or using a custom PyPI repository (like Azure Artifacts, AWS CodeArtifact, JFrog Artifactory, or a self-hosted PyPI server), you need to configure pip to use a custom index URL. This guide covers how to set up a private PyPI index URL across different environments.
Overview
The PyPI index URL tells pip where to download packages from. By default, pip uses https://pypi.org/simple, but you can configure it to use a private repository for proprietary packages or to mirror public packages.
1. Setup for GitHub Actions (PIP_INDEX_URL)
GitHub Actions supports environment variables that pip respects automatically. The PIP_INDEX_URL environment variable is the recommended approach.
Basic Setup
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
name: Python CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
env:
PIP_INDEX_URL: $
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
With Authentication
If your private PyPI server requires authentication, include credentials in the URL:
1
2
3
4
5
6
- name: Install dependencies with authentication
env:
# Format: https://username:password@your-pypi-server.com/simple
PIP_INDEX_URL: $
run: |
pip install -r requirements.txt
Using Extra Index URL
If you want to use both the public PyPI and a private repository:
1
2
3
4
5
6
- name: Install dependencies with extra index
env:
PIP_INDEX_URL: https://pypi.org/simple
PIP_EXTRA_INDEX_URL: $
run: |
pip install -r requirements.txt
Best Practices for GitHub Actions
- Store credentials in GitHub Secrets: Never hardcode URLs with credentials
- Use repository secrets: Go to Settings → Secrets and variables → Actions
- Format:
https://username:password@pypi.example.com/simple
2. Setup for Dockerfile with pip install
When building Docker images, you can pass the PyPI index URL as a build argument to keep credentials secure.
Using Build Arguments
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FROM python:3.11-slim
# Define build argument for PyPI index URL
ARG PIP_INDEX_URL=https://pypi.org/simple
# Set environment variable for pip
ENV PIP_INDEX_URL=${PIP_INDEX_URL}
WORKDIR /app
# Copy requirements file
COPY requirements.txt .
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
CMD ["python", "app.py"]
Building with Custom Index URL
1
2
3
4
# Build with custom PyPI index
docker build \
--build-arg PIP_INDEX_URL=https://username:password@pypi.example.com/simple \
-t myapp:latest .
Using Docker Secrets (Recommended for Docker Buildkit)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# syntax=docker/dockerfile:1
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
# Mount secret during build
RUN --mount=type=secret,id=pip_index_url \
PIP_INDEX_URL=$(cat /run/secrets/pip_index_url) \
pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
Build with secret:
1
2
3
4
5
6
7
8
9
10
# Store your URL in a file
echo "https://username:password@pypi.example.com/simple" > /tmp/pip_index_url
# Build with secret
DOCKER_BUILDKIT=1 docker build \
--secret id=pip_index_url,src=/tmp/pip_index_url \
-t myapp:latest .
# Clean up
rm /tmp/pip_index_url
Multi-stage Build Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Build stage
FROM python:3.11-slim as builder
ARG PIP_INDEX_URL
ENV PIP_INDEX_URL=${PIP_INDEX_URL}
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Runtime stage
FROM python:3.11-slim
WORKDIR /app
# Copy installed packages from builder
COPY --from=builder /root/.local /root/.local
COPY . .
# Update PATH
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
3. Setup for pyproject.toml
For projects using modern Python packaging with pyproject.toml, you can configure pip through several methods.
Using pip.conf (Recommended)
Create or edit pip.conf or pip.ini:
Linux/macOS: ~/.config/pip/pip.conf or ~/.pip/pip.conf
Windows: %APPDATA%\pip\pip.ini or %HOME%\pip\pip.ini
1
2
3
4
5
6
7
8
9
[global]
index-url = https://username:password@pypi.example.com/simple
# Or use extra-index-url to include public PyPI
# index-url = https://pypi.org/simple
# extra-index-url = https://username:password@pypi.example.com/simple
[install]
trusted-host = pypi.example.com
Using Environment Variables
1
2
export PIP_INDEX_URL=https://username:password@pypi.example.com/simple
pip install .
In pyproject.toml (for Poetry)
If you’re using Poetry, configure the private repository:
1
2
3
4
5
6
7
8
9
10
11
12
13
[tool.poetry]
name = "myproject"
version = "0.1.0"
description = "My project"
[[tool.poetry.source]]
name = "private-pypi"
url = "https://pypi.example.com/simple"
priority = "primary"
[[tool.poetry.source]]
name = "pypi"
priority = "supplemental"
Configure authentication separately:
1
2
poetry config repositories.private-pypi https://pypi.example.com/simple
poetry config http-basic.private-pypi username password
For PDM
1
2
3
4
[[tool.pdm.source]]
name = "private"
url = "https://pypi.example.com/simple"
verify_ssl = true
Configure credentials:
1
2
3
pdm config pypi.private.url https://pypi.example.com/simple
pdm config pypi.private.username myusername
pdm config pypi.private.password mypassword
4. Setup for Docker Compose
Docker Compose allows you to set environment variables and build arguments for services.
Using Environment Variables
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
args:
PIP_INDEX_URL: ${PIP_INDEX_URL}
environment:
- PIP_INDEX_URL=${PIP_INDEX_URL}
ports:
- "8000:8000"
volumes:
- .:/app
Using .env File
Create a .env file in the same directory as docker-compose.yml:
1
2
# .env file
PIP_INDEX_URL=https://username:password@pypi.example.com/simple
Important: Add .env to .gitignore to avoid committing credentials!
# .gitignore
.env
*.env
With Build Secrets
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
secrets:
- pip_index_url
ports:
- "8000:8000"
secrets:
pip_index_url:
file: ./secrets/pip_index_url.txt
Complete Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
version: '3.8'
services:
web:
build:
context: .
dockerfile: Dockerfile
args:
- PIP_INDEX_URL=${PIP_INDEX_URL:-https://pypi.org/simple}
environment:
- PYTHONUNBUFFERED=1
ports:
- "8000:8000"
volumes:
- .:/app
command: python manage.py runserver 0.0.0.0:8000
worker:
build:
context: .
dockerfile: Dockerfile
args:
- PIP_INDEX_URL=${PIP_INDEX_URL:-https://pypi.org/simple}
command: celery -A myapp worker -l info
environment:
- PIP_INDEX_URL=${PIP_INDEX_URL}
volumes:
- .:/app
Run with:
1
2
3
4
5
# Set environment variable
export PIP_INDEX_URL=https://username:password@pypi.example.com/simple
# Or use .env file
docker-compose up --build
5. Setup for Local Development Environment
For local development, you have several options to configure pip.
Option 1: Environment Variables (Temporary)
1
2
3
# For current session only
export PIP_INDEX_URL=https://username:password@pypi.example.com/simple
pip install -r requirements.txt
Option 2: pip Configuration File (Persistent)
Create a configuration file for pip:
Linux/macOS:
1
2
3
4
5
6
7
8
9
10
mkdir -p ~/.config/pip
cat > ~/.config/pip/pip.conf << EOF
[global]
index-url = https://username:password@pypi.example.com/simple
trusted-host = pypi.example.com
[install]
# Optional: add extra index for public packages
# extra-index-url = https://pypi.org/simple
EOF
Windows:
mkdir %APPDATA%\pip
echo [global] > %APPDATA%\pip\pip.ini
echo index-url = https://username:password@pypi.example.com/simple >> %APPDATA%\pip\pip.ini
echo trusted-host = pypi.example.com >> %APPDATA%\pip\pip.ini
Option 3: Project-specific Configuration
Create pip.conf in your project root:
1
2
3
4
5
6
[global]
index-url = https://pypi.example.com/simple
extra-index-url = https://pypi.org/simple
[install]
trusted-host = pypi.example.com
Use with:
1
pip install --config-file=pip.conf -r requirements.txt
Option 4: Using Command-line Arguments
1
2
3
4
5
6
7
8
# Single index
pip install -r requirements.txt \
--index-url https://username:password@pypi.example.com/simple
# With extra index (fallback to public PyPI)
pip install -r requirements.txt \
--index-url https://pypi.org/simple \
--extra-index-url https://username:password@pypi.example.com/simple
Option 5: Virtual Environment with direnv
Install direnv and create .envrc:
1
2
# .envrc
export PIP_INDEX_URL=https://username:password@pypi.example.com/simple
Allow the directory:
1
direnv allow .
Important: Add .envrc to .gitignore!
Option 6: Using keyring for Credential Management
For better security, use keyring to store credentials:
1
2
3
4
5
6
7
8
# Install keyring
pip install keyring
# Store credentials
keyring set https://pypi.example.com username
# Configure pip to use keyring
pip config set global.index-url https://pypi.example.com/simple
Create ~/.config/pip/pip.conf:
1
2
3
[global]
index-url = https://pypi.example.com/simple
keyring-provider = auto
Security Best Practices
- Never commit credentials: Always use environment variables or secrets
- Use
.gitignore: Add.env,*.env,pip.confwith credentials - Rotate credentials regularly: Change passwords periodically
- Use token authentication: Prefer tokens over username/password
- Limit token scope: Create tokens with minimum required permissions
- Use HTTPS: Always use secure connections to package repositories
- Verify SSL certificates: Don’t disable SSL verification unless absolutely necessary
Common Issues and Troubleshooting
Issue: SSL Certificate Verification Failed
1
2
3
4
5
6
# Temporary workaround (NOT recommended for production)
pip install --trusted-host pypi.example.com package-name
# Better: Add to pip.conf
[global]
trusted-host = pypi.example.com
Issue: Authentication Failed
Verify URL format:
1
https://username:password@pypi.example.com/simple
URL encode special characters in username/password:
1
2
3
4
from urllib.parse import quote
username = quote("user@example.com")
password = quote("p@ssw0rd!")
url = f"https://{username}:{password}@pypi.example.com/simple"
Issue: Package Not Found
Check index priority:
1
2
3
pip install --index-url https://pypi.example.com/simple \
--extra-index-url https://pypi.org/simple \
package-name -v
Summary
This guide covered setting up custom PyPI index URLs across different environments:
- ✅ GitHub Actions: Use
PIP_INDEX_URLenvironment variable - ✅ Dockerfile: Use build arguments and Docker secrets
- ✅ pyproject.toml: Configure via pip.conf or tool-specific configuration
- ✅ Docker Compose: Use environment variables and .env files
- ✅ Local Development: Multiple options including pip.conf, environment variables, and keyring
Choose the method that best fits your workflow and always prioritize security when handling credentials.