How to set up DevPI, a PyPI-compatible Python development server

How to set up DevPI, a PyPI-compatible Python development server

Learn how to install and configure DevPI in this helpful tutorial.

Computer keyboard typing
Image credits : 

Get the newsletter

Join the 85,000 open source advocates who receive our giveaway alerts and article roundups.

The first time I used DevPI, I was getting ready for a camping trip with my wife and kids. By "getting ready" I do not mean practicing my s'mores-making skills. I mean that I knew my kids would be entertained by camp staff some of the time, and I planned to fix a few bugs in the Twisted package. I also knew I would not have internet on the campgrounds, so I needed to be able to develop without connecting to the internet.

A naive person would prepare virtual environments; however, virtual environments should be disposable, not precious. Many tools will discard and recreate virtual environments whenever the configuration changes. I needed to know that all my packages would be available. That was my introduction to DevPI.

DevPI is a PyPI-compatible server you can run locally. It will not, and does not try, to scale to PyPI-like levels. In return, running it locally is simple and no frills.

DevPi is made up of three parts. The most important one is devpi-server. For many uses, this is the only part that needs to run. The server serves, first and foremost, as a caching proxy to PyPI. It takes advantage of the fact that packages on PyPI are immutable: once you have a package, it can never change.

There is also a web server, which allows you to search in the local package directory. Because a lot of uses do not even involve searching on the PyPI website, this is optional. Finally, there is a client command-line tool that allows you to configure various parameters on the running instance. The client is most useful in more esoteric use cases.

Installing and running DevPI is straightforward. In a virtual environment, simply run:

(devpi)$ pip install devpi-server
(devpi)$ devpi-server --start --init

The pip tool, by default, goes to For some basic testing of DevPI, you can create a new virtual environment or playground and run:

(playground)$ pip install \
-i http://localhost:3141/root/pypi/+simple/ \
httpie glom
(playground)$ http --body | glom ’{"url":"url"}
"url": ""

Naturally, having to specify the -i … argument to pip every time would be annoying. After checking that everything works correctly, you can put the configuration in an environment variable:

$ export PIP_INDEX_URL=http://localhost:3141/root/pypi/+simple/

Or, to make things more permanent:

$ mkdir -p ~/.pip && cat > ~/.pip/pip.conf << EOF
index-url = http://localhost:3141/root/pypi/+simple/
index = http://localhost:3141/root/pypi/

The above file location works for Unix operating systems. On MacOS, the configuration file is $HOME/Library/Application Support/pip/pip.conf. On Windows, the configuration file is %APPDATA%\pip\pip.ini.

To "warm up" the DevPI cache (i.e., make sure it contains all needed packages), use pip to install them. The way I chose to do it, after configuring DevPI and pip, was to git clone the Twisted repository and run tox. Since tox goes through test environments, including the ones with a lot of packages, it would download all the needed packages.

A good practice also is to pre-install in a disposable virtual environment any requirements.txt files you have; however, DevPI's usefulness is not limited to disconnected operations. If you configure one inside your build cluster and point the build cluster at it, you completely avoid the risk of a "leftpad incident," where a package you rely on is removed from PyPI by the author. It might also make builds faster and will definitely cut out a lot of outgoing traffic.

Another use for DevPI is to test uploads before uploading them to PyPI. Assuming devpi-server is already running on the default port, you can run:

(devpi)$ pip install devpi-client twine
(devpi)$ devpi use http://localhost:3141
(devpi)$ devpi user -c testuser password=123
(devpi)$ devpi login testuser --password=123
(devpi)$ devpi index -c dev bases=root/pypi
(devpi)$ devpi use testuser/dev
(devpi)$ twine upload --repository http://localhost:3141/testuser/dev \
-u testuser -p 123 my-package-18.6.0.tar.gz
(devpi)$ pip install -i http://localhost:3141/testuser/dev my-package

Note that this allows uploading to an index that's used only explicitly, so you are not shadowing my-package for all environments that are not using it explicitly.

For an even more advanced use case, you can do:

(devpi)$ devpi index root/pypi mirror_url=https://ourdevpi.local

This will make the DevPI server a mirror of a local, "upstream" DevPI server. This allows uploading private packages to the "central" DevPI server to share them with your team. In those cases, the upstream DevPI server will often need to be run behind a proxy, and you need some tools to properly manage user access. Those details, however, are beyond the scope of this article.

About the author

Moshe sitting down, head slightly to the side. His t-shirt has Guardians of the Galaxy silhoutes against a background of sound visualization bars.
Moshe Zadka - Moshe has been involved in the Linux community since 1998, helping in Linux "installation parties". He has been programming Python since 1999, and has contributed to the core Python interpreter. Moshe has been a DevOps/SRE since before those terms existed, caring deeply about software reliability, build reproducibility and other such things. He has worked in companies as small as three people and as big as tens of thousands -- usually some place around where software meets system administration...