Python zipapp: Build Executable .pyz Files

Published on: August 13, 2026
Reading time: 5 minutes
Organized binders representing applications packaged as executable .pyz files with Python zipapp

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.json

Create the archive with:

python -m zipapp my_app

Then run it:

python my_app.pyz

Imports 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.pyz

The 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.pyz

The 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.pyz

Compression 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.pyz

Pin 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 diagnose

Check 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__.py and the main option.
  • 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 .pyz in 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.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Code editor representing REPL completion with Python rlcompleter
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python rlcompleter: REPL Completion

    Learn Python rlcompleter to add completion to REPLs, consoles, and editors, control namespaces, filter results, and avoid side effects.

    Ler mais

    Tempo de leitura: 5 minutos
    13/08/2026
    Terminal window representing an interactive console built with Python cmd
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python cmd: Build Interactive Consoles

    Learn Python cmd to build interactive consoles with commands, help, history, completion, testing, streams, and secure action control.

    Ler mais

    Tempo de leitura: 4 minutos
    12/08/2026
    Interactive terminal representing a custom REPL built with the Python code module
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python code: Build a Custom REPL

    Learn the Python code module to build custom REPLs, control namespaces, prompts, output, incomplete blocks, errors, and local exit behavior.

    Ler mais

    Tempo de leitura: 5 minutos
    12/08/2026
    Web application code representing WSGI with Python wsgiref
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python wsgiref: WSGI Applications

    Learn Python wsgiref to build and validate WSGI applications, test environ and headers, route requests, and run a local reference

    Ler mais

    Tempo de leitura: 4 minutos
    12/08/2026
    Secure Internet protocol representing Unicode preparation with Python stringprep
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python stringprep: Prepare Unicode

    Learn Python stringprep to apply RFC 3454 tables, map Unicode, reject prohibited characters, and validate bidirectional protocol rules.

    Ler mais

    Tempo de leitura: 5 minutos
    12/08/2026
    Network connections representing non-blocking I/O with Python selectors
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python selectors: Non-Blocking I/O

    Learn Python selectors to monitor many sockets, read and write readiness, timeouts, partial messages, and non-blocking connections safely.

    Ler mais

    Tempo de leitura: 5 minutos
    11/08/2026