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


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.
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 byuv
, 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
fileManual 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
: TellsPyInstaller
to bundle everything into a single.exe
file--name
: Specifies the desired name for both the output.exe
and the generated.spec
filemain.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:
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.
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:
🎉 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.
Subscribe to my newsletter
Read articles from ARHAM RUMI directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
