Packaging Python projects for Debian / Raspbian with dh-virtualenv

This article aims to explain some things to developers which don’t use Python a lot, and might struggle with some of the concepts otherwise.

I highly recommend the lecture of the following article as an introduction to the concepts discussed here: is an official repository of software for the Python programming language. It includes libraries we want to use in our projects, which are not shipped by default with Python.

The problem

Our goal is to package an application written in Python (targetting Python3) as a .deb package for Raspbian (which is, all things considered, really equivalent to Debian).

Our application has dependencies on several Python libraries.

For instance, we would like to use the requests library. The requests library itself depends on other libraries, e.g. chardet, urllib3, etc.

Manually managing these dependencies and manually shipping the required library code is inherently impractical.


Python has it’s own package / dependency management system, called pip

pip allows you to install python libraries and applications, so that your own code can use these libraries.

Here there are two problems:

  • we want to ship the package as a Debian package, for end users to install easily. We can safely assume that on a significant number of target systems the appropriate python libraries will not have been installed by the user
  • if we try to install these libraries system-wide on behalf of the user, we might break compatibility with some other packages (e.g. because of API changes in between versions of the libraries)

Therefore we need a solution.


virtualenv will allow us to create virtual environments for Python.

In these virtual environments we have:

  • our own Python interpreter
  • copies of the required standard libraries
  • our own pip
  • our own “system-wide” library

Practical part: Trying out virtualenv

Install virtualenv (as part of dh-virtualenv, which we will get to in a little while) by doing:

sudo apt install dh-virtualenv

As requests is already installed system-wide on my test box, we will use a different package as an example. randstr will allow you to generate a random string.

Check if randstr is installed system-wide:

pip3 show randstr

This should return nothing. Indicating that this package is not installed systemwide:


Now we will create a virtual environment. The virtual environment folder does NOT have to live inside your code folder, but it can. You should ignore it in GIT.

let’s say we have a directory /home/pi/tutorial

Change into this directory, and create a new environment folder env:

cd /home/pi/tutorial

which python3

virtualenv -p /usr/bin/python3 env

Note: you should specify the Python interpreter if you want Python 3, as otherwise Python 2 will be used. I am using Python 3. (Running virtualenv with interpreter /usr/bin/python2)

which python3 will tell you where the python3 binary resides, adjust the path for virtualenv accordingly.


activate the environment:

source env/bin/activate

(This command path assumes that you stayed in the tutorial directory). Please also note, that this is not an executable script – you have to use source to use it with your bash terminal.

Notice, how (env) is prepended before your command prompt:


This means that the environment is active. Now you can run pip3 again, but this time pip3 will perform it’s work on the virtual environment:

pip3 show randstr

pip3 show requests

should still yield nothing. The second line will also show nothing – showing that (if requests is installed system-wide) we are indeed in a virtual environment.

Now install randstr:

pip3 install randstr

and check again, if it is installed:

pip3 show randstr


This time it shows that randstr is installed. It has been installed into env/lib/python3.5/site-packages:


While the environment ist still activated, let’s write a small Python sample application:


paste the following code, generating a random string using the new library we just installed:

from randstr import randstr
print(“hello world”)

and save.

Run the code using python3:


while the environment is still active, this will work:


Now it’s time to exit the environment, to see whether the code will still run:


Your prompt will return back to normal. And


will throw an error message, about no module ‘randstr’ existing on your system. This is exactly the behaviour we want!


You can have a look at the env folder, seeing how it ships all the necessary things to run your Python code, including libraries, the python executable, pip, and more.

Debian virtualenv packaging with dh-virtualenv

OK, now with the basics out of the way, what we want is to package a virtualenv alongside with our code, so that our application will run reliably and without disturbing other applications on our user’s computers.

This is where dh-virtualenv enters the picture. dh-virtualenv is an addition to the Debian building scripts, which allows you to package virtual environments.

We need to create a basic package structure for our new package. This is where cookiecutter will help.


cookiecutter creates new projects for you from templates, thus reducing the overhead and time you spend on developing and getting your head around the many files which are required.

sudo apt install cookiecutter

Note: the package python3-cookiecutter will provide a Python module for Python 3. What we want is the application itself, which is installed with the cookiecutter package as seen above. This might pull in Python 2, but – oh well.

cookiecutter is not necessary for your end users, it is only used to create several files for your package.

create an example project directory, into which we will apply the template (I am still in the tutorial directory):

mkdir sampleproject

cd sampleproject

Using a special mold for dh-virtualenv we can set up many of the necessary files:


This will ask you several questions.


Note, that the folder should be named debian, even when packaging for Raspbian – therefore keep the name as debian.

This will install the following files:


Now you need to run the following commands, as per the instructions under the dh-virtualenv-mold:

sudo apt-get install build-essential debhelper devscripts equivs
sudo mk-build-deps --install debian/control
To build the package later on, you will run the follwoing command from your project’s top level directory:
dpkg-buildpackage -uc -us –b
Right now, though, the command will fail because is missing. We will get to and requirements.txt in a brief while:
/usr/bin/python3: can't open file '': [Errno 2] No such file or directory

Information about the individual files


This file will contain your release and version information for the package. You can update it using a special tool, dch.

This file is not a changelog for your application, but just a changelog for the packing of your application.


This file includes a special magic number “9”. (for compatibility issues, it’s fine to leave it as it is)


This is the main file to set package settings, and dependencies on a Debian level. If your application depends, for example, on the omxplayer being installed, it will need to go in here as a dependency:

Source: sampleproject
Section: contrib/python
Priority: extra
Maintainer: Maximilian Batz <???>
Build-Depends: debhelper (>= 9), python, python-dev, dh-virtualenv (>= 0.10), tar
Standards-Version: 3.9.5

Package: sampleproject
Architecture: any
Pre-Depends: dpkg (>= 1.16.1), python2.7 | python3, ${misc:Pre-Depends}
Depends: ${python:Depends}, ${misc:Depends}
Description: An example package to demonstrate virtualenv Debian packaging with dh-virtualenv
     This is a distribution of “sampleproject” as a self-contained
     Python virtualenv wrapped into a Debian package (“omnibus” package,
     all passengers on board). The packaged virtualenv is kept in sync with
     the host’s interpreter automatically.
     See for more details.

You will also want to edit the long Description.


Includes your initial settings. Not required for packaging.


Your copyright file, with your license (e.g. MIT). It’s pre-filled with your name, year, e-Mail and “some rights reserved”.


This is a Makefile, to actually build your package. It is prefilled by the cookiecutter template.


This file allows you to create links during installation . The filename will include your project name instead of sampleproject.



This file allows you to run additional setup steps after installation (e.g. activating your python script as a service). The filename will include your project name instead of sampleproject.


As far as I understand it this is for dh-virtualenv to install a newer python interpreter into the virtual environment to ship, if the system’s (your development box) one is updated. The filename will include your project name instead of sampleproject.

This might or might not be the case, I have not tested it.

As mentioned above, we need a file. This is the standard way applications are packaged under Python (they ship with a, which is executed for various tasks related to packaging).

A simple can be seen on this page:

Not all entries in this files are necessary. For instance, you can leave out the classifiers. I have the following


Note: the version number and other information inside here are for your Python package (which will be created in the course of building a Debian package with your virtualenv).

Structure for your code

Your code will now live in a subdirectory of the main sampleproject directory:


The subdirectory has the same name as the main directory in this case.

Note the

The is the one we used above, but a bit reworked to look like this:


Additionally, I set up a virtual environment for testing while developing inside the main (top level) sampleproject directory:

virtualenv -p /usr/bin/python3 sp_env

called sp_env for sample project environment

Entering the environment:

source sp_env/bin/activate

Install the randstr library into this environment:

pip3 install randstr

and now we can run the code:

python3 sampleproject/


OK. Exit the environment (! important !), and try to build the package again:


dpkg-buildpackage -us –uc

By default (because of the cookie cutter), your package will be created for installation in /opt/venvs/:

New python executable in /home/pi/tutorial/sampleproject/debian/sampleproject/opt/venvs/sampleproject/bin/python3

This can be changed in the debian/rules file.

Now the package has been built, and deposited outside of the sampleproject top directory (inside the tutorial directory):


You can install the .deb file using dpkg:

sudo dpkg -i sampleproject_0.2.1+nmu1_armhf.deb

See the installation results with

tree /opt/venvs

127 directories and 945 files were installed.

Now you can try to execute the sampleproject using


requirements / dependencies

The application will not run as (maybe) expected, but throw an error about randstr not being present. What gives?


The reason is that dh-virtualenv does not know about the requirement we have, to bundle the package randstr during the build process. This is the final piece of the puzzle.

The requirements need to be added to the, as a way to let pip know which additional packages need to be installed in the virtual environment which is being created.

add the following to




The whole file then will look like this:


In my tests the requirements.txt placed at top-level did not influence the behaviour of dh-virtualenv and the Python packages actually included in the Debian package. If you still want it, here’s how:

pip offers you a way to put out the exact dependencies you used for developing in the virtual environment. Enter the virtual environment:

source sp_env/bin/activate

And create the requirements.txt file:

pip3 freeze > requirements.txt

Now you can have a look at that file:

cat requirements.txt


Even though this file seems not to be used by dh-virtualenv (maybe it needs to go in a different subdirectory), it is a good reference for yourself, to see which packages you should put as dependencies into the install_requires part of

Note that the file sampleproject.links offered us a convenient way to link this into the /usr/bin folder, I will therefore remove the comment sign for the next build:


With that out of the way, it’s time to purge the sampleproject, and build it again, then install it. Don’t forget to deactivate first!


sudo apt purge sampleproject

dpkg-buildpackage –us -uc

cd ..

sudo dpkg –i sampleproject_0.2.1+nmu1_armhf.deb

This time, the package should be accessible using sampleproject:

which sampleproject




Bonus tip: changing the installation location

/opt/venvs is not a very Debian “place” to put a package.

The specifications are here:

  • /usr/local is not for packages
  • /opt is not for Debian packages proper (it’s for user-installed third-party software)

Applications may use a single subdirectory under /usr/lib. If an application uses a subdirectory, all architecture-dependent data exclusively used by the application must be placed within that subdirectory.

“The FHS requirement that architecture-independent application-specific static files be located in /usr/share is relaxed to a suggestion. In particular, a subdirectory of /usr/lib may be used by a package (or a collection of packages) to hold a mixture of architecture-independent and architecture-dependent files. However, when a directory is entirely composed of architecture-independent files, it should be located in /usr/share.”

The best location to me seems to be /usr/share

We need to edit two files:

in debian/rules DH_VIRTUALENV_INSTALL_ROOT must be changed to /usr/share, like this:


and debian/sampleproject.links must be changed:


Also, we will make a new entry into the changelog:

dch –i


simply edit the text, to reflect a new version number, and an entry for the changelog.

Then the project can be build again:

dpkg-buildpackage -us –uc

After installation, you can verify that indeed the new location is used:


The package will take up about 16 MB on your target system, thanks to the virtual environment and everything which is shipped with it. This probably could be reduced by some parameters, but that exercise is left to the reader.

Have fun packaging!