Python zipapp creates executable ZIP archives containing a Python application. These files commonly use the .pyz extension and can be launched with python application.pyz. On POSIX systems, they may also contain a shebang and executable permission so they behave like ordinary commands.
The format works well for internal utilities, command-line tools, administrative scripts, and pure-Python applications that benefit from single-file delivery. It does not compile the project into a native binary and it does not include the Python interpreter. The target machine still needs a compatible Python installation, and bundled dependencies must support execution from a ZIP archive.
How a .pyz application works
A Python Zip Application is a regular ZIP file with a root-level __main__.py. When executed, Python places the archive on sys.path and runs that module as the entry point.
my_app/
├── __main__.py
├── commands.py
└── data/
└── config.jsonCreate the archive with:
python -m zipapp my_appThen run it:
python my_app.pyzImports work through Python’s normal ZIP import machinery. For packaged data, use the patterns described in Python importlib.resources.
Generate an entry point
If the source directory does not contain __main__.py, the -m option can generate one that imports and calls a no-argument function.
python -m zipapp my_app \
-m "my_app.cli:main" \
-o tool.pyzThe callable must be included in the archive:
# my_app/cli.py
def main():
print('Application started')The required format is package.module:callable. Do not use -m when the source already has __main__.py.
Add a shebang
The -p option prepends an interpreter line. On POSIX, zipapp also sets the executable bit.
python -m zipapp my_app \
-p "/usr/bin/env python3" \
-o tool.pyz
./tool.pyzThe selected interpreter must be portable for the intended audience. /usr/bin/env python3 is often more flexible than an absolute path, but it still assumes that a compatible python3 command exists.
Compression trade-offs
Files are stored uncompressed by default. Use --compress to enable deflate:
python -m zipapp my_app --compress -o tool.pyzCompression lowers distribution size but may increase build and startup work. Source code usually compresses well; JPEG, PNG, and other already-compressed formats may barely shrink.
Automate with create_archive()
The Python API provides the same functionality.
import zipapp
zipapp.create_archive(
source='my_app',
target='dist/tool.pyz',
interpreter='/usr/bin/env python3',
main='my_app.cli:main',
compressed=True,
)source can be a directory, an existing archive, or a binary input stream. target can be a path or binary output stream. The caller must close any streams it supplies.
Filter build content
The filter callback receives each relative Path and decides whether it enters the archive.
from pathlib import Path
import zipapp
IGNORED = {'__pycache__', '.git', '.pytest_cache'}
def include(path: Path) -> bool:
if any(part in IGNORED for part in path.parts):
return False
return path.suffix not in {'.pyc', '.log', '.env'}
zipapp.create_archive(
'my_app',
'dist/my_app.pyz',
filter=include,
compressed=True,
)Always exclude secrets, local environment files, logs, caches, and development artifacts. Build from a clean staging directory and inspect the final ZIP contents.
Bundle pure-Python dependencies
Install dependencies into the build tree before packaging:
python -m pip install \
--requirement requirements.txt \
--target build/my_app
python -m zipapp build/my_app \
-m "my_app.cli:main" \
-o dist/my_app.pyzPin versions and hashes for reproducible builds. Do not install dependencies directly into the source tree; use a disposable build directory.
C extensions cannot load from inside the ZIP
Native modules such as .so and .pyd files generally cannot be loaded directly from an archive because the operating-system loader requires real filesystem objects.
Packages such as NumPy, cryptographic libraries, database drivers, and image processors may include native components. Require them externally, ship compatible binaries next to the archive, or use another packaging method. Remember architecture and operating-system compatibility.
Read packaged resources correctly
Do not assume that __file__ points to a normal directory. A resource inside the archive may not exist as a persistent path.
from importlib.resources import files
text = (
files('my_app.data')
.joinpath('config.json')
.read_text(encoding='utf-8')
)If an API requires a filesystem path, use importlib.resources.as_file() within a context manager. The full workflow is covered in Python importlib.resources.
Inspect the embedded interpreter
zipapp.get_interpreter() reads the shebang.
import zipapp
interpreter = zipapp.get_interpreter('dist/my_app.pyz')
print(interpreter)The CLI equivalent is python -m zipapp archive.pyz --info. Use this in release checks to confirm the expected launcher.
Copy and modify an existing archive
create_archive() can copy an existing .pyz and replace its interpreter line.
zipapp.create_archive(
'old.pyz',
'new.pyz',
interpreter='/usr/bin/env python3',
)The input and output path cannot be identical. Write to a new file, validate it, synchronize if necessary, and then replace the old artifact atomically.
Avoid unsafe in-place overwrites
The documentation demonstrates modifying an archive through BytesIO, but warns that an error during overwrite may destroy the original. A safer production pattern uses a temporary sibling file and os.replace().
import os
import zipapp
new_path = 'app.pyz.new'
zipapp.create_archive('app.pyz', new_path, '/usr/bin/env python3')
validate(new_path)
os.replace(new_path, 'app.pyz')Reproducible artifacts
A repeatable build requires controlled Python versions, dependencies, source files, permissions, timestamps, and archive order. Record the final checksum.
import hashlib
from pathlib import Path
data = Path('dist/app.pyz').read_bytes()
print(hashlib.sha256(data).hexdigest())Python compileall and py_compile can verify syntax, but precompiled bytecode does not remove interpreter-version constraints.
Distribution security
A .pyz is executable code. Sign it or publish cryptographic hashes through a trusted channel, restrict release permissions, and never execute unknown archives.
ZIP packaging does not hide content. Anyone can inspect the archive. Do not embed API keys, passwords, private certificates, or production configuration.
Python version compatibility
The archive must match the interpreter used at runtime. New syntax, recent APIs, and dependency requirements can fail on older installations. Declare a minimum supported version and test a matrix of clean environments.
import sys
if sys.version_info < (3, 11):
raise SystemExit('Python 3.11 or newer is required')A shebang cannot express “version X.Y or later.” It points to a command; runtime checks remain useful.
When zipapp is a strong choice
Use zipapp when the application is mostly pure Python, users already have a compatible interpreter, and one-file delivery simplifies operations. Internal automation and portable command-line utilities are excellent candidates.
Avoid it when you must bundle Python itself, depend heavily on native extensions, require persistent filesystem paths for resources, or need a native installer. Wheels, containers, or executable bundlers may fit better.
Test the generated archive
Do not test only the source directory. Execute the final artifact in a clean environment:
python dist/my_app.pyz --version
python dist/my_app.pyz diagnoseCheck that no global packages are required. Test encoding, resources, command-line arguments, error handling, exit codes, and supported Python versions. Documentation generated with Python pydoc can help verify public APIs.
Common mistakes
- Missing both
__main__.pyand themainoption. - Packaging secrets and development files.
- Assuming native extensions run inside the ZIP.
- Using
__file__for every resource. - Selecting a non-portable shebang.
- Overwriting the original without an atomic replacement.
- Testing only on the build machine.
Best practices
- Build in a clean disposable directory.
- Pin dependencies and record hashes.
- Filter caches, logs, and secrets.
- Use importlib.resources for data.
- Test the actual
.pyzin clean environments. - Document the minimum Python version.
- Distribute through a trusted channel.
Conclusion
Python zipapp turns a source tree into a transparent, executable single-file application. Entry points, shebangs, filtering, and pure-Python dependency bundling make it practical for many internal tools.
It does not bundle the interpreter and it does not solve native binary dependencies. Plan compatibility, resources, security, and artifact testing. Consult the official zipapp documentation and the zipimport documentation.







