When you freeze a PyQt6 application with PyInstaller, your app gets bundled into a self-contained package. But what happens when your app needs to launch an external Python script or tool using QProcess? If you're not careful, the process will try to use whatever Python is installed on the user's system — or fail entirely if Python isn't installed at all.
This is a common issue. You've carefully packaged your application, but the QProcess call reaches outside your bundle and tries to use the system Python. Let's look at why this happens and how to solve it.
Why QProcess uses the system Python
When you call QProcess.start() with a command like "mitmproxy", Qt searches the system PATH to find that executable. After freezing, your bundled application lives in its own directory, but QProcess has no idea about that — it just asks the operating system to find and run mitmproxy the same way a terminal would.
If the user has Python 2.7 installed system-wide (and mitmproxy is installed there), that's the one that gets picked up. If the user has no Python at all, the command fails.
The fundamental problem is that QProcess launches a separate process outside your frozen bundle. It doesn't inherit the Python environment you used when building the app.
Approaches to solve this
There are a few strategies depending on what exactly you need to run.
Bundle the script into your application
If the script you're running is your own code, the best approach is usually to avoid launching it as a separate process entirely. Instead, you can run it within your application using Python's threading or multiprocessing modules, or use Qt's multithreading with QThreadPool.
For example, if meta_proxy.py contains a function you can call directly:
import threading
from script.addons import meta_proxy
def run_proxy():
meta_proxy.start(port=8888)
thread = threading.Thread(target=run_proxy, daemon=True)
thread.start()
This avoids the QProcess problem completely because the code runs inside your already-frozen application. PyInstaller will bundle meta_proxy.py and its dependencies along with everything else.
PyQt6 Crash Course by Martin Fitzpatrick — The important parts of PyQt6 in bite-size chunks
Bundle the external tool as a separate frozen executable
If you genuinely need to run a separate process — for example, because the tool manages its own event loop or needs process isolation — you can freeze the external script as its own standalone executable and ship it alongside your main app.
With PyInstaller, you can create a second frozen executable for the script:
pyinstaller --onefile script/addons/meta_proxy.py
This produces a standalone meta_proxy (or meta_proxy.exe on Windows) binary that doesn't need Python installed. You then include this binary in your application's distribution and launch it with QProcess using its known path.
Here's how you'd reference it from your frozen app:
import os
import sys
import shlex
from PyQt6 import QtCore
def get_resource_path(relative_path):
"""Get the absolute path to a bundled resource."""
if getattr(sys, "frozen", False):
# Running as a frozen executable
base_path = os.path.dirname(sys.executable)
else:
# Running in a normal Python environment
base_path = os.path.dirname(os.path.abspath(__file__))
return os.path.join(base_path, relative_path)
# Build the path to the bundled executable
proxy_path = get_resource_path("meta_proxy")
p = QtCore.QProcess()
p.start(proxy_path, ["-p", "8888", "-k"])
The get_resource_path helper figures out where your application is running from. When frozen, sys.executable points to your main .exe, so os.path.dirname(sys.executable) gives you the folder it lives in. You place the meta_proxy executable in that same folder (or a subfolder), and now QProcess can find it without relying on the system PATH.
Use the bundled Python interpreter
PyInstaller bundles a Python interpreter inside your frozen application. In some cases, you can use this interpreter to run scripts directly. The embedded interpreter is available at a path relative to your executable.
On macOS and Linux, the Python library is embedded in the bundle, but there isn't a standalone python executable you can easily call. On Windows with --onedir mode, you may find the Python DLL and supporting files in the output directory, but calling them directly is fragile and not recommended.
A more reliable variant of this approach is to use sys.executable itself. When your application is frozen with PyInstaller, sys.executable points to your frozen app's executable. You can make your frozen app accept command-line arguments that tell it to run in a "worker" mode instead of showing the GUI:
import sys
import os
from PyQt6 import QtWidgets, QtCore
def run_proxy(port):
"""Run the proxy service (called in subprocess mode)."""
# Import and start the proxy here
print(f"Starting proxy on port {port}")
# meta_proxy.start(port=port)
def main():
if len(sys.argv) > 1 and sys.argv[1] == "--run-proxy":
# We're being called as a subprocess — run the proxy
port = int(sys.argv[2]) if len(sys.argv) > 2 else 8888
run_proxy(port)
sys.exit(0)
# Normal GUI mode
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QMainWindow()
window.setWindowTitle("My Application")
button = QtWidgets.QPushButton("Start Proxy")
def start_proxy():
p = QtCore.QProcess()
# Launch ourselves with --run-proxy flag
p.start(sys.executable, ["--run-proxy", "8888"])
# Keep a reference so the process isn't garbage collected
window.process = p
button.clicked.connect(start_proxy)
window.setCentralWidget(button)
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
This pattern is quite elegant: your frozen .exe serves double duty. When launched normally, it shows the GUI. When launched with --run-proxy, it runs the proxy logic and exits. Since sys.executable always points to the frozen binary, QProcess launches another copy of your own application — no system Python needed.
Handling mitmproxy specifically
In the original question, the command being run is mitmproxy, which is a third-party tool. You have a few options here:
-
Bundle
mitmproxyas a frozen executable. Ifmitmproxyprovides a way to freeze it (or you can freeze its entry point), bundle it alongside your app and reference it by path as shown above. -
Use
mitmproxyas a library. Themitmproxypackage exposes a Python API. Instead of launching it as a separate process, you can import and run it directly inside your application (in a thread). This way PyInstaller bundles all themitmproxydependencies automatically. -
Ship a known Python environment. You could bundle a portable Python installation with your app and point
QProcessat that specific interpreter. This works but significantly increases your distribution size and complexity.
Option 2 is usually the cleanest. For example, you can use mitmproxy's programmatic API:
import threading
from mitmproxy.tools.main import mitmproxy
def start_mitmproxy():
mitmproxy(args=["-p", "8888", "-s", "addons/meta_proxy.py", "-k"])
thread = threading.Thread(target=start_mitmproxy, daemon=True)
thread.start()
Because this runs inside your frozen application's process, PyInstaller handles all the dependencies.
Summary of approaches
| Approach | When to use it |
|---|---|
| Run the code directly in a thread | When the script is your own code or a library with a Python API |
| Freeze the script as a second executable | When you need true process isolation |
Use sys.executable with a flag |
When you want one frozen binary that can act as both GUI and worker |
| Bundle a portable Python | Last resort — adds size and complexity |
The common thread across all these solutions is the same: after freezing, you can't rely on the user's system having Python or any Python packages installed. Everything your application needs must be included in what you ship. Whether that means importing a library directly, bundling a second executable, or re-launching your own frozen app in a different mode, the goal is to keep everything self-contained.
For more details on using QProcess to run external programs, see our complete guide to QProcess in PyQt6. If you're looking for a step-by-step walkthrough of packaging your PyQt6 app with PyInstaller on Windows, check out our PyQt6 PyInstaller packaging tutorial.
Create GUI Applications with Python & Qt6 by Martin Fitzpatrick — (PyQt6 Edition) The hands-on guide to making apps with Python — Save time and build better with this book. Over 15K copies sold.