The zipapp module lets you package a Python application as a single executable file, usually with the .pyz extension. The format is based on a regular ZIP archive containing a __main__.py entry point. It is useful for command-line tools, internal utilities, educational projects, and controlled environments where Python is already installed.
Because zipapp belongs to the standard library, you do not need an external packager for the basic workflow. It is not a complete replacement for wheels, containers, or tools that bundle the interpreter, but it offers an elegant middle ground when the project is mostly pure Python and easy distribution matters.
How a pyz file works
A .pyz file is a ZIP archive that Python can execute directly. The interpreter opens the archive, finds __main__.py, and runs it as the application entry point. The archive can contain packages, modules, and resource files that are compatible with ZIP imports.
my_app/
├── __main__.py
├── cli.py
└── utils.pyKeep __main__.py small. It should usually import a real main function and delegate execution to it. This design improves testability and keeps command-line concerns separate from business logic.
from cli import main
if __name__ == "__main__":
raise SystemExit(main())Create your first zipapp
Once the source directory is ready, run:
python -m zipapp my_app -o my_app.pyzThe -o option defines the output file. You can execute the result with:
python my_app.pyzOn Unix-like systems, add an interpreter line and mark the file as executable:
python -m zipapp my_app -o my_app.pyz -p "/usr/bin/env python3"
chmod +x my_app.pyz
./my_app.pyzThe -p argument writes a shebang. On Windows, execution generally remains python app.pyz unless file associations are configured.
Choose an entry point
If the source directory does not already contain __main__.py, use -m with a module:function target:
python -m zipapp my_app -m "cli:main" -o tool.pyzThe selected function must be importable and should not require positional arguments. The generated entry point calls it automatically.
Use the Python API
For build scripts and continuous integration, use zipapp.create_archive():
from zipapp import create_archive
create_archive(
"my_app",
target="dist/my_app.pyz",
interpreter="/usr/bin/env python3",
main="cli:main",
compressed=True,
)The API makes it easier to generate reproducible artifacts, place them in a distribution directory, and combine packaging with validation steps.
Handle dependencies
Pure Python dependencies can be installed into the staging directory before archive creation. A common approach uses pip --target:
python -m pip install -r requirements.txt --target build/app
cp -r src/* build/app/
python -m zipapp build/app -o dist/app.pyz -m "cli:main"This technique does not work reliably for every package with native extensions. Shared libraries such as .so or .pyd files may need a real filesystem location and platform-specific installation. For those projects, choose a wheel, container, or another packaging method.
Python version compatibility
A zipapp does not include Python itself. The target machine must provide a compatible interpreter and any system-level requirements. If your source uses new syntax or standard-library features, publish the minimum supported version clearly.
Portable Python code can still depend on operating-system behavior. Paths, permissions, external commands, encodings, and process signals should be tested on every supported platform.
Package resource files correctly
Do not assume that every internal resource has a normal filesystem path. Prefer importlib.resources to read package data:
from importlib.resources import files
template = files("my_package").joinpath("template.txt").read_text(
encoding="utf-8"
)This approach works better for modules loaded from ZIP archives. Directly using Path(__file__).parent may fail when a library expects to open a physical path.
Keep configuration outside
Do not embed passwords, tokens, or environment-specific secrets in the archive. A .pyz file is easy to inspect because it is still a ZIP file. Use environment variables, command-line options, secret stores, or external configuration files with appropriate permissions.
Compression choices
Compression can reduce artifact size, especially when the application contains many text files. However, startup may require additional decompression work. For small command-line tools the difference is usually minor. Measure both size and startup time when they matter.
Integrity and distribution
A single artifact simplifies deployment, rollback, and checksum verification. Generate a SHA-256 digest and publish it through a trusted channel:
python -c "import hashlib, pathlib; p=pathlib.Path('app.pyz'); print(hashlib.sha256(p.read_bytes()).hexdigest())"For higher-assurance distribution, sign the artifact and verify the signature before deployment.
Test the final archive
Unit tests against the source tree are not enough. Build the archive in CI and execute real smoke tests. Resource access, dynamic imports, package metadata, and libraries that depend on physical files can behave differently inside a ZIP.
Test help output, invalid options, exit codes, supported Python versions, and representative operating systems. A simple build-and-run matrix can prevent broken releases.
Use cases
- Internal command-line utilities.
- Administrative scripts with several modules.
- Pure Python applications for controlled servers.
- Educational tools distributed as one download.
- Build artifacts used in automation pipelines.
When to use another format
Choose wheels when users should install the package into an environment. Choose containers when operating-system dependencies and services must be controlled. Choose a standalone executable builder when users do not have Python. Zipapp is strongest when the interpreter already exists and the application remains mostly pure Python.
Reproducible builds
Pin dependency versions, build in a clean directory, exclude caches and temporary files, and document the command that generated the archive. Reproducibility makes debugging and rollback much easier.
Security considerations
Packaging does not make code secret or trusted. Treat downloaded archives like any executable code. Verify origin, checksums, signatures, and dependency sources. Do not run untrusted zipapps with elevated privileges.
Maintainable project structure
Keep business logic in importable modules, use a small CLI layer, validate input, return meaningful exit codes, and log failures without exposing secrets. This structure works well both before and after packaging.
For related Academify guides, read Python importlib.resources, Python virtual environments, Python argparse, and Python subprocess.
Conclusion
zipapp provides a lightweight way to turn a Python application into one executable archive. It works best for pure Python projects and environments with a compatible interpreter. With a clear entry point, correct resource handling, pinned dependencies, external configuration, integrity checks, and tests against the final artifact, a zipapp can become a simple and dependable distribution format.







