Skip to content

Instantly share code, notes, and snippets.

Last active July 2, 2023 16:24
Show Gist options
  • Save Jaza/fcea493dd0ba6ebf09d3 to your computer and use it in GitHub Desktop.
Save Jaza/fcea493dd0ba6ebf09d3 to your computer and use it in GitHub Desktop.
Guide for how to create a (minimal) private PyPI repo, just using Apache with directory autoindex, and pip with an extra index URL.

How to set up and use a private PyPI repo

Accompanying blog post:

Splitting a Python codebase into dependencies for fun and profit

Based on:

Create a local PyPi repository using only mod_rewrite

See also:

Local PyPI Options

Setting up a private, team-wide PyPI repository

For a more advanced private PyPI, see:

devpi: PyPI server and packaging/testing/release tool

Create root directory for private PyPI


(On remote server)

mkdir /path/to/
mkdir /path/to/

Create self-signed SSL certificate

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/apache2/ssl/ \
-out /etc/apache2/ssl/

Set up Apache vhost

sudo htpasswd -c /etc/apache2/passwords_pypi pypi
sudo vi /etc/apache2/sites-available/

(Add these lines)

<VirtualHost *:80>

    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}

<VirtualHost *:443>
    DocumentRoot /data/www/

    SSLEngine On
    SSLCertificateFile /etc/apache2/ssl/
    SSLCertificateKeyFile /etc/apache2/ssl/

    <Directory /data/www/>
        AllowOverride None
        Options +Indexes
        IndexOptions SuppressColumnSorting
        IndexIgnore ..
        Order deny,allow
        Allow from all

        AuthType Basic
        AuthName "My Server"
        AuthBasicProvider file
        AuthUserFile /etc/apache2/passwords_pypi
        Require valid-user

    LogLevel warn
    ErrorLog /var/log/apache2/pypi-error.log
    CustomLog /var/log/apache2/pypi-access.log combined

cd /etc/apache2/sites-enabled
sudo ln -s ../sites-available/
sudo apache2ctl graceful

Create directory for new library in private PyPI

mkdir /path/to/

Update library's code

(On local machine)

cd /path/to/foobar-utils

(Add these lines)

__version__ = '0.1.0'

foobar = 'Hey foo, I am a bar!'


(Add these lines)

import os

import setuptools

module_path = os.path.join(os.path.dirname(__file__), '')
version_line = [line for line in open(module_path)
                if line.startswith('__version__')][0]

__version__ = version_line.split('__version__ = ')[-1][1:][:-2]


    author="Mister foo",

    description="Utils for handling Foo and Bar.",



        'Development Status :: 2 - Pre-Alpha',
        'Environment :: Web Environment',
        'Intended Audience :: Developers',
        'Operating System :: OS Independent',
        'Programming Language :: Python',
        'Programming Language :: Python :: 2',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.3',

vi README.rst

(Add these lines)


Utils for handling Foo and Bar.

Upload new version of library code to private PyPI

python bdist_wheel --universal
scp dist/foobar_utils-0.1.0-py2.py3-none-any.whl \

Configure pip to use private PyPI

vi ~/.pip/pip.conf

(Add these lines)

; Extra index to private pypi dependencies
extra-index-url =
trusted-host =

Use private library in a project's requirements.txt

cd /path/to/projectfoo
virtualenv .
source bin/activate
vi requirements.txt

(Add these lines)


pip install -r requirements.txt
Copy link

brettswift commented Aug 17, 2017

any idea how to use this without the user dot files? That kinda sucks when you want to have other people set up on your team right away. Obviously these packages will be sourced from the same spot. I'm new to python - just wondering if it can go into a config file in the project, just no luck finding this on google yet.

Also supplying them with environment variables, so we can do continuous delivery of our modules from a CI server, would be good info :)

Copy link

looks like this is a preferred way of deploying

Copy link

Could you post the equivalent nginx config?

Copy link

@brettswift Actually there are parameter to tell pip to use a different server and without TLS. example:
pip install requests --index-url=

Copy link

Nginx config I used today:

server {
    listen 443 ssl;
    server_name  {{ pypi_domain }};
    include snippets/letsencrypt-{{ pypi_domain }}.conf;
    location / {
        root /data/pypi/;
        autoindex on;
        try_files $uri $uri/ =404;
        client_body_temp_path /data/pypi.tmp/;
        dav_methods PUT;
        create_full_put_path on;
        dav_access group:rw all:r;
        limit_except GET {
                        allow {{ ci_public_ip }};
                        deny  all;
    index index.html;

We're uploading from our CI with a simple curl -XPUT https://pypi.redacted/simple/demo-0.0.2.tar.gz --data-binary @dist/demo-0.0.2.tar.gz from a whitelisted IP and it's done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment