How to Package a Python App as a Debian .deb

Blog / Python · December 22, 2015 · Updated June 10, 2026 · 7 min read
How to Package a Python App as a Debian .deb

The fastest way to turn a Python app into a Debian .deb in 2026 is fpm — it wraps an existing directory or virtualenv into an installable package with a single command, which is ideal for internal servers. If you need to ship a service with pinned dependencies, use dh-virtualenv; if your package is going into the official Debian or Ubuntu repositories, use native debhelper + dh-python (pybuild).

This guide replaces the old stdeb / py2dsc walkthrough that most tutorials still show. That approach is Python-2-era and effectively unmaintained — do not use it for new work. If you just want to share a library, you usually want PyPI instead of a .deb (see the FAQ below).

Key takeaways

  • Pick the tool by destination, not habit. Internal server? fpm. Service with bundled deps? dh-virtualenv. Official Debian/Ubuntu repo? native debhelper + dh-python.
  • stdeb/py2dsc is dead for modern work — it targets Python 2 and is unmaintained. Skip any tutorial built around it.
  • A .deb still earns its place on apt-managed fleets, for systemd services, and for OS-level integration (users, config in /etc, logrotate).
  • For cloud and Kubernetes, a container usually wins over a .deb. For a per-user CLI, pipx beats both.
  • Packaging to a .deb is a different target from PyPI — one ships to apt-managed machines, the other to pip users.

Should you even build a .deb in 2026?

Before writing a single debian/ file, be honest about whether .deb is the right format. Several modern options often beat it:

  • Docker / OCI images — the default for cloud, Kubernetes, and reproducible deploys. The whole runtime, interpreter, and dependencies travel together.
  • pipx — installs a Python CLI tool into its own isolated virtualenv per user, with the command on your PATH. Perfect for developer tools.
  • PEX / shiv / PyInstaller — bundle your app (and sometimes the interpreter) into a single executable file you can copy and run, no system packaging required.

A native .deb is still the right answer when you run an apt-managed fleet and want the OS package manager to own installs, upgrades, dependency resolution, and removal. It shines when the app is a long-running systemd service, or needs real OS integration — a dedicated system user, config under /etc, a logrotate snippet, or post-install hooks. If you maintain such servers, that lifecycle is exactly what our server maintenance services handle day to day.

Which Python-to-Debian packaging tool should you use?

Tool Best for Actively maintained?
fpm Quickly wrapping a directory or virtualenv into a .deb for internal servers Yes
dh-virtualenv Shipping a Python service with a bundled, pinned virtualenv Yes
debhelper + dh-python (pybuild) Packages destined for official Debian/Ubuntu repositories Yes
stdeb / py2dsc Legacy Python 2-era setup.py conversion No — avoid
Docker / OCI image Cloud, Kubernetes, full environment isolation Yes
pipx Installing per-user Python CLI tools Yes
PEX / shiv / PyInstaller Single-file, self-contained app bundles Yes

The rest of this guide shows a working example for the three .deb routes using a tiny CLI named mymood.

How do you build a .deb with fpm (the fast way)?

fpm ("Effing Package Management") is a Ruby tool that converts almost anything into a package. It is the quickest pragmatic path from Python to .deb and needs zero debian/ boilerplate.

First install it, then build. The cleanest reproducible pattern is to install your app into a directory (for example with pip install --target or into a relocatable virtualenv), then package that directory:

# Install fpm (it is a Ruby gem)
sudo apt-get install ruby ruby-dev build-essential
gem install fpm

# Option A: package straight from a published Python package + its deps
fpm -s python -t deb --python-bin python3 --python-pip pip3 mymood

# Option B: install into a dir, then wrap that dir into a .deb (most control)
pip3 install --target=/tmp/build/opt/mymood .
fpm -s dir -t deb -n mymood -v 1.0 \
    --depends python3 \
    --description "Simple mood checker" \
    -C /tmp/build opt/mymood

Option A reads your project metadata and turns the package (and dependencies) into .deb files. Option B is the most predictable for internal deployment: you control exactly what lands on disk and where. Either way you get an installable mymood_1.0_*.deb in seconds.

How do you ship a Python service with dh-virtualenv?

dh-virtualenv builds a complete virtualenv at package-build time and bundles it inside the .deb. Your exact pinned dependencies ship with the package, so the target server does not resolve Python deps at install time — great for services with reproducibility requirements.

You need a minimal debian/ directory. The debian/control file:

Source: mymood
Section: python
Priority: optional
Maintainer: Your Name <you@example.com>
Build-Depends: debhelper-compat (= 13),
               dh-virtualenv (>= 1.2),
               python3,
               python3-dev
Standards-Version: 4.6.2

Package: mymood
Architecture: any
Depends: ${python3:Depends}, ${misc:Depends}
Description: Simple mood checker
 A small CLI that analyzes your mood from a few questions.

And debian/rules — note the recipe line must be indented with a real tab, not spaces:

#!/usr/bin/make -f
export DH_VIRTUALENV_INSTALL_ROOT=/opt/venvs

%:
	dh $@ --with python-virtualenv

Build it the usual way (dpkg-buildpackage -us -uc -b). The resulting .deb drops a self-contained virtualenv under /opt/venvs/mymood, dependencies and all.

How do you do native Debian packaging with debhelper + dh-python?

This is the "proper" route for anything headed to the official Debian or Ubuntu archives, or an internal mirror that follows Debian policy. Modern packaging uses debhelper-compat level 13 and dh-python's pybuild build system, and reads metadata from a standard pyproject.toml (the setup.py-only era is over). Install the toolchain first:

sudo apt-get install build-essential debhelper dh-python \
    python3-all pybuild-plugin-pyproject devscripts

Create debian/changelog with dch --create (do not hand-edit the format), then add debian/control:

Source: mymood
Section: python
Priority: optional
Maintainer: Your Name <you@example.com>
Build-Depends: debhelper-compat (= 13),
               dh-python,
               python3-all,
               pybuild-plugin-pyproject
Standards-Version: 4.6.2
Homepage: https://example.com/mymood

Package: python3-mymood
Architecture: all
Depends: ${python3:Depends}, ${misc:Depends}
Description: Simple mood checker
 A small CLI that analyzes your mood from a few questions.

A minimal debian/rules (again, recipe lines are tab-indented):

#!/usr/bin/make -f
export PYBUILD_NAME=mymood

%:
	dh $@ --with python3 --buildsystem=pybuild

A correctly formatted debian/changelog entry looks like this:

mymood (1.0-1) unstable; urgency=medium

  * Initial release.

 -- Your Name <you@example.com>  Wed, 10 Jun 2026 12:00:00 +0530

Then build from the project root:

dpkg-buildpackage -us -uc -b
# -> ../python3-mymood_1.0-1_all.deb

Install with sudo apt install ./python3-mymood_1.0-1_all.deb (use apt, not dpkg -i, so dependencies resolve automatically). If you are wiring this into a CI pipeline or untangling legacy packaging, our Python development services team does exactly this kind of build-and-release work.

When should you use a container instead of a .deb?

Use the table as a quick decision guide:

Situation Better choice
Apt-managed bare-metal or VM fleet .deb
Long-running systemd service with OS integration .deb
Kubernetes / cloud-native / ephemeral workloads Docker / OCI image
Per-developer CLI tool pipx
Single drop-in executable for mixed hosts PEX / shiv / PyInstaller
Sharing a reusable library with other Python devs PyPI (pip)

A .deb and a container are not rivals so much as different deployment models: .deb makes the host OS the source of truth; a container makes the image the source of truth. Pick the one that matches how the rest of your fleet is already managed.

Frequently Asked Questions

Is stdeb (py2dsc) still usable for Python packaging?

Not for new work. stdeb/py2dsc was built around Python 2 and setup.py, and it is effectively unmaintained. Most older "Python to deb" tutorials lean on it, which is why their steps no longer apply cleanly. Use fpm for speed, dh-virtualenv for bundled services, or native debhelper + dh-python for repo-quality packages.

Should I ship a .deb or a Docker image?

Ship a .deb when you run an apt-managed fleet and want the OS to own installs, upgrades, and removal — especially for systemd services or apps needing OS integration. Ship a Docker/OCI image for cloud, Kubernetes, or any environment where you want the whole runtime bundled and the image to be the source of truth.

What is the difference between dh-virtualenv and native debhelper packaging?

dh-virtualenv builds a full virtualenv at package-build time and bundles your pinned dependencies inside the .deb, so the target host never resolves Python deps. Native debhelper + dh-python produces a policy-compliant package that depends on system Python packages — the right choice if it is going into Debian/Ubuntu repositories.

How do I run a Python app as a systemd service from a .deb?

Add a unit file (for example debian/mymood.service) and let debhelper install and enable it; dh_installsystemd handles enabling and starting it on install. This OS-level lifecycle — services, users, config in /etc — is the main reason to choose .deb over a single-file bundle.

Can I publish my Python package to a private apt repository?

Yes. Once you have built a .deb, host it in a private apt repo with a tool such as aptly or reprepro, sign it with GPG, and point your servers' sources.list at it. Then apt install and apt upgrade work exactly as they do for distro packages, which is the whole appeal for managed fleets.

Is building a .deb the same as publishing to PyPI?

No — they are different distribution targets. A .deb installs onto apt-managed Debian/Ubuntu machines via apt, while publishing to PyPI ships your package to anyone using pip. Libraries meant for other Python developers belong on PyPI; deployable apps and services for your own servers are good .deb candidates.

Share this article