Build a One-File EXE with PyInstaller (Including Binaries & Resources)

ARHAM RUMIARHAM RUMI
5 min read

Packaging Python projects into standalone executables can be incredibly useful for sharing your work with non-developers. In this guide, we’ll walk through how to convert your Python script — even one that depends on additional binaries like ffmpeg or modules like easygui — into a single .exe file using PyInstaller. Let’s have a look at the project structure.

Project structure

Let’s have a quick overview of this project.

  • .venv: The virtual environment that keeps your project isolated and independent.

  • ffmpeg_build: Contains the ffmpeg binaries used in the project. You might not need this specific folder if you’re using other binaries, but the concept of including external binaries remains the same.

  • favicon.ico: The icon file for your application.

  • main.py: The main script containing all the core logic of your application.

  • pyproject.toml: Project configuration file created by uv, specifying dependencies and metadata.

In this guide, we’re using uv as the package and project manager. It automatically generates some boilerplate files during initialization. Don’t worry if you’re using a different virtual environment or package manager — the steps in this guide still apply.

Now, let’s move on to the core of the guide. Here’s what we’ll need:

  • PyInstaller

  • .spec file

  • Manual updates to the .spec file to correctly handle paths and packages

We’ll begin by generating a basic .spec file using PyInstaller. Then, we’ll manually edit it to suit our project’s structure and rebuild the package using the updated .spec file. That’s the overall process.

Step 1: Install PyInstaller

Run the following command in your terminal or command prompt:

pip install pyinstaller

To confirm that PyInstaller was installed successfully, run the following command in a new command prompt:

pyinstaller -v

If it returns the version number, congratulations — PyInstaller is installed correctly. If not, review the error message and resolve the issue accordingly. You can comment the issue and I will respond to it.

Step 2: Creating .spec file

Navigate to the root folder of your project (where your main script is located), and run the following command:

pyinstaller --onefile --name "unify" main.py

Let’s break down the command:

  • --onefile: Tells PyInstaller to bundle everything into a single .exe file

  • --name: Specifies the desired name for both the output .exe and the generated .spec file

  • main.py: This is the entry point of your application—the script containing your project’s logic

After running the command, PyInstaller will execute and display logs ending with something like:

Command logs

Updated project structure

You should now see new folders like build/, dist/, and a unify.spec file in your project directory. These are generated automatically by PyInstaller after the initial build.

Among these, the unify.spec file is the most important for us—it defines how PyInstaller bundles your application. At this point, it contains the default configuration, which we will now customize to properly include paths, external binaries, and required packages.

The unify.spec file contains this:

# -*- mode: python ; coding: utf-8 -*-


a = Analysis(
    ['main.py'],
    pathex=[],
    binaries=[],
    datas=[],
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
    optimize=0,
)
pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.datas,
    [],
    name='unify',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)

This is the basic .spec file and we will update it.

Step 3: Updating the .spec File for Paths and Packages

Let’s edit the .spec file to tell PyInstaller exactly what it needs to include for your application to work correctly in a single .exe file.

# -*- mode: python ; coding: utf-8 -*-


a = Analysis(
    ['main.py'],
    pathex=["E:\\unify\\.venv\\Lib\\site-packages"],
    binaries=[('E:\\unify\\ffmpeg_build\\bin\\ffmpeg.exe','bin'), ('E:\\unify\\ffmpeg_build\\bin\\ffprobe.exe','bin')],
    datas=[],
    hiddenimports=["easygui","tqdm"],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
    optimize=0,
)
pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.datas,
    [],
    name='unify',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    icon=['favicon.ico'],
)

Here, you’ll notice we’ve updated several key variables in the .spec file:

  • pathex: Specifies additional paths to search for imports.

  • binaries: Points to external binary files (e.g., ffmpeg.exe, ffprobe.exe) that need to be bundled with your application.

  • hiddenimports: Lists any modules that are dynamically imported in your code but may not be automatically detected by PyInstaller. Adding them here helps avoid ModuleNotFoundError at runtime.

  • icon: Path to your .ico file. In many cases, a relative path will work fine. However, if you encounter an error or your .exe file doesn’t show the icon as expected, try using the full absolute path. On Windows, note that icon updates may not be immediately visible due to icon caching behavior.

Note: All these options can also be provided as flags when running the pyinstaller command. However, we kept the initial command simple because you’ll likely need to tweak the .spec file manually anyway—even if you pass all options up front.

Now, delete the build/ and dist/ directories to clean up the old build, and run this command:

pyinstaller unify.spec

The build process will take some time to complete. During this, you’ll see logs detailing the packaging steps.

Command logs

Once finished, navigate to the dist/ directory — you’ll find your one-file .exe there, complete with the custom icon (if everything was set correctly). It should look something like this:

One file EXE

🎉 That’s it! Your project is now bundled into a single executable.

If you run into any issues, feel free to drop a comment — I’ll do my best to help you troubleshoot and resolve them.

0
Subscribe to my newsletter

Read articles from ARHAM RUMI directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

ARHAM RUMI
ARHAM RUMI