Python zipapp: Build Executable .pyz Files

Published on: August 27, 2026
Reading time: 6 minutes
High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.

The zipapp module packages a Python application into one executable ZIP archive, commonly using the .pyz extension. Python adds the archive to sys.path and runs its __main__.py. This is useful for internal tools, automation scripts, command-line utilities, prototypes, and distribution to systems that already have a compatible Python runtime.

A zipapp is not a standalone native executable. It does not automatically include Python, solve every dependency, or make compiled extensions importable from inside a ZIP file. For public desktop distribution or machines without Python, a full application packager may be more appropriate.

Minimal structure

An application archive needs a __main__.py file at its root.

my_app/
├── __main__.py
└── package/
    ├── __init__.py
    └── cli.py

The entry file starts the application.

from package.cli import main

if __name__ == "__main__":
    raise SystemExit(main())

This keeps main() independently testable and propagates an exit code.

Create an archive from the command line

python -m zipapp my_app -o my_app.pyz

Run it with:

python my_app.pyz --help

Python treats the archive as an importable directory and executes its entry point.

Create an archive with the API

zipapp.create_archive() automates builds from Python code.

from zipapp import create_archive

create_archive(
    "my_app",
    target="dist/my_app.pyz",
)

Create the destination directory first and use pathlib to organize the build pipeline.

Generate an entry point

When the source tree has no __main__.py, specify a main function as package.module:function.

python -m zipapp my_app \
  -m "package.cli:main" \
  -o my_app.pyz

The tool generates a small __main__.py that imports and calls the function.

Main function design

The entry function should be importable and callable without positional arguments unless it deliberately reads sys.argv.

def main():
    args = parser.parse_args()
    run(args)
    return 0

Do not execute the entire program at module import time. Imports should establish definitions, not perform the command.

Shebang and interpreter

The --python option or interpreter parameter adds an interpreter line to the archive.

python -m zipapp my_app \
  --python "/usr/bin/env python3" \
  -o my_app.pyz
chmod +x my_app.pyz
./my_app.pyz

This works on systems that support shebang execution. On Windows, run python my_app.pyz or configure an association.

Runtime version checks

A generic python3 shebang can resolve to different releases. Validate minimum requirements at startup.

import sys

if sys.version_info < (3, 12):
    raise SystemExit("Python 3.12 or newer is required")

Document the versions tested by the project.

Compression

The --compress option or compressed=True compresses entries.

create_archive(
    "my_app",
    target="dist/my_app.pyz",
    compressed=True,
)

Compression reduces file size but adds CPU work during build and reading. Small applications may see little benefit.

Rebuilding from clean sources

The API can process existing archives in selected workflows, but it should not be treated as a generic ZIP editor.

Rebuild from a clean source tree to improve traceability and reproducibility.

Pure Python dependencies

Packages written entirely in Python can be installed into the staging directory before the build.

python -m pip install \
  --target build/app \
  --requirement requirements.txt
cp -r src/my_package build/app/
python -m zipapp build/app -o dist/app.pyz

Use an isolated build environment and pinned versions.

Native extensions

Compiled modules such as .so and .pyd files generally cannot be imported directly from inside a ZIP because the operating-system loader needs a real file.

Keep native dependencies installed outside the archive, extract them through a controlled process, or choose another packaging tool.

Detect native dependencies

Do not rely only on a package's name. Inspect wheels and test in a clean environment.

A transitive dependency can introduce native code even when the top-level package appears pure.

Package resources

Code running from a ZIP should not assume __file__ names a normal filesystem file.

Use importlib.resources to read templates and packaged data.

from importlib.resources import files

text = (
    files("my_package")
    .joinpath("data/default.json")
    .read_text(encoding="utf-8")
)

Resources that require a real path

Some libraries require an actual pathname. importlib.resources.as_file() can temporarily materialize a resource where supported.

Use it as a context manager and never keep the path after exit.

Writable files

Treat archive contents as read-only. Do not write configuration, caches, or databases beside internal modules.

Use user-data, cache, or temporary directories and allow explicit configuration.

Current working directory

Do not assume the process starts in the archive's directory. Path.cwd() depends on where the user launched the command.

Use configured absolute paths or packaged resources.

Imports

Organize the code as a package and prefer absolute imports. Fragile relative imports and modules named after standard-library packages can cause conflicts.

Test the final artifact, not only the source tree.

Namespace packages

Namespace packages can work, but combinations of internal and external portions require careful testing.

A normal package is usually more predictable for a compact tool.

sys.path behavior

During execution, the archive appears on the import path. Its position can interact with externally installed packages.

Use unique package names and never depend on accidental shadowing.

External dependency conflicts

If a dependency is not bundled, the application may import an arbitrary installed version from the user's environment.

Validate versions or distribute instructions for a dedicated virtual environment.

Use with a virtual environment

An internal deployment can create a venv containing Python and native dependencies, then execute the .pyz with that interpreter.

The archive contains application code while the environment provides runtime libraries.

zipapp versus pipx

Tools such as pipx install command-line applications into isolated environments. A conventional package with an entry point can be easier to update than a manually distributed archive.

Choose zipapp when a single file materially simplifies operations.

Version metadata

Expose application version through --version and optionally include a manifest with commit, build time, and dependency lock information.

__version__ = "1.4.0"

Reproducible builds

ZIP entries contain timestamps and ordering information, which can produce different hashes. Control timestamps, sort inputs, and pin dependencies for reproducibility.

Basic zipapp usage may need an additional normalization step for strict deterministic builds.

File filters

The API accepts a filter that decides which paths are included.

def include(path):
    parts = set(path.parts)
    return not parts.intersection({"__pycache__", ".git", "tests"})

create_archive("build/app", "dist/app.pyz", filter=include)

Do not accidentally exclude required resources. Run the final archive in a clean directory.

Do not embed secrets

A zipapp is an ordinary ZIP and can be opened easily. Never include passwords, API tokens, private keys, or credentials.

Load secrets from a manager, protected environment, or external configuration.

Checksums and signatures

Distribute a hash or signature to verify integrity. Python does not automatically validate a zipapp signature before execution.

The deployment process must verify the file before starting the interpreter.

Untrusted archives

Running a .pyz executes Python code with the user's permissions. Never download and execute unknown archives.

Use HTTPS, signatures, trusted origins, and least privilege.

Atomic updates

A single file is easy to replace, but update atomically. Download under a temporary name, validate, and call os.replace().

Do not overwrite the active file before verification completes.

Rollback

Keep the previous version until a smoke test passes. A launcher or symlink can select the active artifact.

Do not combine irreversible data migrations with an update that has no rollback plan.

Containers

A zipapp can reduce application files inside a container image, but it still needs Python and dependencies.

Containers already provide layers and isolation, so measure whether the archive adds operational value.

Arguments and exit codes

Use argparse and consistent exit statuses. raise SystemExit(main()) propagates the return code.

Write normal output to stdout and errors to stderr.

Logging

Do not write logs inside the archive. Configure stderr, an external file, or structured logging.

Include archive version at process startup for diagnostics.

Testing

Build the archive in CI and execute real commands in a clean environment. Test help, version, missing dependencies, resources, exit codes, paths with spaces, Windows, and POSIX.

Compare source-tree and .pyz behavior.

Common mistakes

Common failures include omitting __main__.py, bundling native extensions inside the ZIP, treating __file__ as a normal path, writing into the archive, relying on an unknown external dependency version, embedding secrets, skipping artifact tests, and confusing zipapp with a self-contained executable.

Conclusion

zipapp packages pure Python applications into a single archive executed by Python. Use a clean entry point, importlib.resources, pinned dependencies, read-only archive assumptions, and end-to-end artifact tests.

Consult the official zipapp documentation and Python sysconfig for details about the destination runtime.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python tokenize: Read Source Code Tokens

    Learn Python tokenize to read tokens, comments, encodings, indentation, and positions, then transform and rebuild source safely.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python sysconfig: Paths and Build Info

    Learn Python sysconfig to inspect paths, schemes, headers, compiler flags, ABI details, native extensions, and virtual environments.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    A person in a hoodie coding on dual monitors, depicting cybersecurity and hacking themes.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python marshal: Internal Binary Format

    Learn Python marshal for internal objects and bytecode, including versions, allow_code, disposable caches, limits, and untrusted-input risks.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    A close-up view of fresh, green cucumbers ready for pickling and preservation in Estonia.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python copyreg: Customize Pickle Types

    Learn Python copyreg to customize pickle reducers, version serialized state, avoid global conflicts, and test safely across processes.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    Close-up of a python snake coiled in darkness, showcasing its scales and eyes.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python reprlib: Safe Object Summaries

    Learn Python reprlib to summarize large lists, strings, and recursive objects while keeping logs safe, bounded, and readable.

    Ler mais

    Tempo de leitura: 5 minutos
    27/08/2026
    A close-up shot showcasing the intricate scales of a snake, highlighting texture and color.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python graphlib: Topological Task Order

    Learn Python graphlib to order dependencies, detect cycles, run ready tasks in parallel, and build safe task pipelines.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026