> ## Content Index
> Fetch the complete content index at: https://linuxhandbook.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Packaging a Python App to Executable .deb Binary
- URL: https://linuxhandbook.com/package-python-app-deb/
- Published: 2025-03-18T05:28:47.000Z
- Updated: 2025-03-18T05:28:47.000Z
- Description: I am sharing how I packaged my python application into an executable .deb package in this tutorial.
- Author: Abhishek Kumar
- Tags: Tutorial

You know that moment when you dive into a project, thinking, "*This should be easy*," and then hours later, you're buried under obscure errors, outdated forum posts, and conflicting advice? 

Yeah, that was me while trying to package my Python app into a `.deb` file.

It all started with my attempt to revive an old project, which some of our long-time readers might remember - [**Compress PDF**.](https://github.com/itsfoss/compress-pdf?ref=linuxhandbook.com) 

![pdf compressor tool by its foss](https://linuxhandbook.com/content/images/2025/03/compress-pdf-by-itsfoss.png)

PDF Compressor Tool v1.0 by [Its FOSS](https://itsfoss.com/?ref=linuxhandbook.com)

Since I’ve been learning Python these days, I thought, *why not customize the UI, tweak the logic, and give it a fresh start?* 

The python app was working great when running inside a virtual environment but I was more interested in shipping this app as a .deb binary, making installation as simple as `dpkg -i app.deb`.

Every tutorial I read online, covered bits and pieces, but none walked me through the entire process. So here I am, documenting my experience while packaging my script into a `.deb` file.

## Choosing the right packaging tool

For turning a Python script into an executable, I am using [**PyInstaller**](https://pyinstaller.org/en/stable/?ref=linuxhandbook.com). Initially, I tried using [py2deb](https://py2deb.readthedocs.io/en/latest/?ref=linuxhandbook.com), a tool specifically meant for creating `.deb` packages. 

**Bad idea.** Turns out, `py2deb` hasn’t been maintained in years and doesn’t support newer Python versions.

[PyInstaller](https://pyinstaller.org/en/stable/?ref=linuxhandbook.com) takes a Python script and bundles it along with its dependencies into a single standalone executable. This means users don’t need to install Python separately, it just works out of the box.

## Step 1: Install PyInstaller

First, make sure you have PyInstaller installed. If not, install it using pip:

```bash
pip install pyinstaller

```

Check if it's installed correctly:

```bash
pyinstaller --version

```

## Step 2: Create the .deb package structure

To keep things clean and structured, `.deb` packages follows a specific folder structure. 

```bash
compressor/
├── pdf-compressor/                
│   ├── DEBIAN/                     
│   │   ├── control
│   │   ├── postinst
│   ├── usr/                         
│   │   ├── bin/
│   │   ├── share/
│   │   │   ├── applications/
│   │   │   ├── icons/
│   │   │   ├── pdf-compressor/
```

Let’s create it:

```bash
mkdir -p pdf-compressor/DEBIAN
mkdir -p pdf-compressor/usr/bin
mkdir -p pdf-compressor/usr/share/applications
mkdir -p pdf-compressor/usr/share/icons/
mkdir -p pdf-compressor/usr/share/pdf-compressor/
```

![creating empty directories](https://linuxhandbook.com/content/images/2025/03/creating-empty-dirs.png)

#### What each directory is for?

- `usr/bin/`: Stores the executable file.
- `usr/share/applications/`: Contains the `.desktop` file (so the app appears in the system menu).
- `usr/share/icons/`: Stores the app icon.
- `DEBIAN/`: Contains metadata like package info and dependencies.

#### Optional: Packaging dependencies

Before packaging the app, I wanted to ensure it loads assets and dependencies correctly whether it's run as a script or a standalone binary.

Initially, I ran into two major problems:

1. The in-app logo wasn’t displaying properly because asset paths were incorrect when running as a packaged executable.
2. Dependency errors occurred when running the app as an executable.

To keep everything self-contained and avoid conflicts with system packages, I created a virtual environment inside: `pdf-compressor/usr/share/pdf-compressor` 

```bash
python3 -m venv venv
source venv/bin/activate
```

Then, I installed all the dependencies inside it:

```bash
pip install -r requirements.txt
deactivate

```

This ensures that dependencies are bundled properly and won’t interfere with system packages.

Now to ensure that the app correctly loads assets and dependencies, I modified the script as follows:

```bash
import sys
import os

# Ensure the virtual environment is used
venv_path = "/usr/share/pdf-compressor/venv"
if os.path.exists(venv_path):
    sys.path.insert(0, os.path.join(venv_path, "lib", "python3.10", "site-packages"))

# Detect if running as a standalone binary
if getattr(sys, 'frozen', False):
    app_dir = sys._MEIPASS  # PyInstaller's temp folder
else:
    app_dir = os.path.dirname(os.path.abspath(__file__))

# Set correct paths for assets
icon_path = os.path.join(app_dir, "assets", "icon.png")
logo_path = os.path.join(app_dir, "assets", "itsfoss-logo.webp")
pdf_icon_path = os.path.join(app_dir, "assets", "pdf.png")

print("PDF Compressor is running...")

```

**What’s happening here?**

- `sys._MEIPASS` → When the app is packaged with PyInstaller, assets are extracted to a temporary folder. This ensures they are accessible.
- Virtual environment path (`/usr/share/pdf-compressor/venv`) → If it exists, it is added to `sys.path`, so installed dependencies can be found.
- Assets paths → Dynamically assigned so they work in both script and standalone modes.

After making these changes, my issue was mostly resolved. 

📋

I know there are other ways to handle this, but since I'm still learning, this approach worked well for me. If I find a better solution in the future, I’ll definitely improve it!

## Step 3: Compiling python script into executable binary

Now comes the exciting part, turning the Python script into a standalone executable. Navigate to the root directory where the main Python script is located. Then run:

```bash
pyinstaller --name=pdf-compressor --onefile --windowed --add-data "assets:assets" pdf-compressor.py

```

- `--onefile`: Packages everything into a single executable file
- `--windowed`: Hides the terminal (useful for GUI apps)
- `--name=compress-pdf`: Sets the output filename
- `--add-data "assets:assets"` → Ensures images/icons are included.

![creating the standalone executable file from a python script](https://linuxhandbook.com/content/images/2025/03/creating-executable-binary-1.png)

After this, PyInstaller will create a `dist/` , inside, you'll find `compress-pdf` . This is the standalone app!

Try running it:

```bash
./dist/pdf-compressor

```

If everything works as expected, you’re ready to package it into a `.deb` file.

## Step 4: Move the executable to the correct location

Now, move the standalone executable into the bin directory:

```bash
mv dist/compress-pdf pdf-compressor/usr/bin/pdf-compressor

```

![moving the executable to the bin directory](https://linuxhandbook.com/content/images/2025/03/moving-script-to-bin.png)

## Step 5: Add an application icon

I don't know about you but to me, an app without an icon or just generic gear icons feels incomplete. Icon gives the vibe to your app.

Let’s place the `assets` directory which contains the icon and logo files inside the right directory:

```bash
cp assets/ pdf-compressor/usr/share/pdf-compressor
```

![copying the assets directory to the correct directory used by packaged binary](https://linuxhandbook.com/content/images/2025/03/copying-assets-dir.png)

## Step 6: Create a desktop file

To make the app appear in the system menu, we need a `.desktop` file. Open a new file:

```bash
nano pdf-compressor/usr/share/applications/pdf-compressor.desktop
```

Paste this content:

```bash
[Desktop Entry]
Name=PDF Compressor
Comment=Compress PDF files easily
Exec=/usr/bin/pdf-compressor
Icon=/usr/share/icons/pdf-compressor.png
Terminal=false
Type=Application
Categories=Utility
```

- **`Exec`** → Path to the executable.
- **`Icon`** → App icon location.
- **`Terminal=false`** → Ensures it runs as a GUI application.

![creating desktop file for pdf compressor](https://linuxhandbook.com/content/images/2025/03/creating-desktop-file.png)

Save and exit (`CTRL+X`, then `Y`, then `Enter`).

## Step 7: Create the control file

At the heart of every `.deb` package is a metadata file called `control`. 

This file is what tells the Debian package manager (dpkg) what the package is, who maintains it, what dependencies it has, and a brief description of what it does.

That’s why defining them here ensures a smooth experience for users. 

Inside the `DEBIAN/` directory, create a `control` file:

```bash
nano pdf-compressor/DEBIAN/control
```

then I added the following content in it:

```bash
Package: pdf-compressor
Version: 1.0
Section: utility
Priority: optional
Architecture: amd64
Depends: python3, ghostscript
Recommends: python3-pip, python3-venv
Maintainer: Your Name <your@email.com>
Description: A simple PDF compression tool.
 Compress PDF files easily using Ghostscript.
```

![](https://linuxhandbook.com/content/images/2025/03/creating-control-file-1.png)

## Step 8: Create the postinst script

The post-installation (`postinst`) script as the name suggests is executed after the package is installed. It ensures all dependencies are correctly set up.

```bash
nano pdf-compressor/DEBIAN/postinst
```

Add this content:

```bash
#!/bin/bash
set -e  # Exit if any command fails

echo "Setting up PDF Compressor..."
chmod +x /usr/bin/pdf-compressor

# Install dependencies inside a virtual environment
python3 -m venv /usr/share/pdf-compressor/venv
source /usr/share/pdf-compressor/venv/bin/activate
pip install --no-cache-dir pyqt6 humanize

echo "Installation complete!"
update-desktop-database

```

**What’s happening here?**

- **`set -e`** → Ensures the script stops on failure.
- Creates a virtual environment → This allows dependencies to be installed in an isolated way.
- `chmod +x /usr/bin/pdf-compressor` → Ensures the binary is executable.
- `update-desktop-database` → Updates the system’s application database.

![creating postinst file](https://linuxhandbook.com/content/images/2025/03/new-postinst-file.png)

Setting up the correct permission for `postinst` is important:

```bash
chmod 755 pdf-compressor/DEBIAN/postinst
```

![postinst permission error](https://linuxhandbook.com/content/images/2025/03/postinst-permission-error.png)

## Step 9: Build & Install the deb package

After all the hard work, it's finally time to bring everything together. To build the package, we’ll use `dpkg-deb --build`, a built-in Debian packaging tool.

This command takes our structured `pdf-compressor` directory and turns it into a `.deb` package that can be installed on any Debian-based system.

```bash
dpkg-deb --build pdf-compressor
```

If everything goes well, you should see output like:

```bash
dpkg-deb: building package 'pdf-compressor' in 'pdf-compressor.deb'.
```

![building the deb file from the executable binary](https://linuxhandbook.com/content/images/2025/03/building-deb-package.png)

Now, let’s install it and see our application in action!

```bash
sudo dpkg -i pdf-compressor.deb
```

![installing the pdf-compressor.deb file ](https://linuxhandbook.com/content/images/2025/03/dpkg-installed-application.png)

💡

If installation fails due to missing dependencies, fix them using: `sudo apt install -f`

This installs `pdf-compressor` onto your system just like any other Debian package. To verify, you can either launch it from the Applications menu or directly via terminal:

```bash
pdf-compressor
```

![pdf compressor v2.0 running on lubuntu vm](https://linuxhandbook.com/content/images/2025/03/pdf-compressor-v2.png)

PDF Compressor v2.0 running inside [Lubuntu](https://lubuntu.me/?ref=linuxhandbook.com) | P.S. I know the color scheme could have been better 😅

## Final thoughts

Packaging a Python application isn’t as straightforward as I initially thought. During my research, I couldn't find any solid guide that walks you through the entire process from start to finish.

So, I had to experiment, fail, and learn, and that’s exactly what I’ve shared in this guide. Looking back, I realize that a lot of what I struggled with could have been simplified had I known better. But that’s what learning is all about, right?

I believe that this write-up will serve as a good starting point for new Python developers like me who are still struggling to package their projects.

That said, I know this isn’t the only way to package Python applications, there are probably better and more efficient approaches out there. So, I’d love to hear from you!

Also, if you found this guide helpful, be sure to check out our [PDF Compressor project on GitHub](https://github.com/itsfoss/compress-pdf?ref=linuxhandbook.com). Your feedback, contributions, and suggestions are always welcome!

Happy coding! 😊