# Python zipapp web apps [What's a `zipapp`?](https://docs.python.org/3/library/zipapp.html) > NOTE: The built `.pyz` _zipapp_ can run on both Python 2 & 3 but you can only build `.pyz` _zipapps_ with Python 3.5 or later. ## Initial setup There is a single subdirectory called `flaskr` in the beginning. It's an example [Flask](http://flask.pocoo.org/) app that I'm going to package into a Python _zipapp_. ``` $ tree -L 2 --dirsfirst -F . . └── flaskr/ ├── flaskr/ ├── flaskr.egg-info/ ├── venv/ ├── LICENSE ├── MANIFEST.in ├── README.rst ├── requirements.txt └── setup.py 4 directories, 5 files ``` ## Install to virtualenv Now let's create a new [Python virtual environment](https://virtualenv.pypa.io/en/stable/) next to `flaskr` subdirectory and install `Flask` and `gunicorn` into the new virtual environment: ``` $ virtualenv -p python3 venv $ source ./venv/bin/activate $ pip install Flask gunicorn ``` Now there should be two subdirectories: `flaskr` and `venv`: ``` $ tree -L 1 --dirsfirst -F . . ├── flaskr/ └── venv/ 2 directories, 0 files ``` ## Install all dependencies to project directory Let's create a new directory called `app` which will be packaged into a _zipapp_ as a whole. In order to create a _zipapp_ you must have all dependencies installed in the same directory as your application source code (e.g our `app` directory), not in a virtualenv. I'm going to create a [`requirements.txt`](https://pip.pypa.io/en/stable/user_guide/#requirements-files) file to pin the versions of each installed dependent Python package that I've just installed in my virtualenv and then install all of them into the new `app` directory. Alternatively, you could skip the virtualenv step and run `pip install -t ./app Flask gunicorn` if you don't want to pin versions. ``` $ mkdir app $ cd app $ pip freeze > requirements.txt $ pip install -t . -r requirements.txt ``` ## Add application code Let's copy our application code into the new `app` directory: ``` $ cp -r ../flaskr . ``` Or install it if you have a nice distributable Python package that has a `setup.py`: ``` $ pip install -t . ../flaskr ``` ## Cleanup To save disk space you can remove extra `pip` files and cache before creating a _zipapp_: ``` $ rm -rf ./__pycache__ ./*.dist-info $ cd .. ``` Now your project should look like this. ``` $ tree -L 2 --dirsfirst -F . . ├── app/ │ ├── click/ │ ├── flask/ │ ├── gunicorn/ │ ├── jinja2/ │ ├── markupsafe/ │ ├── werkzeug/ │ ├── itsdangerous.py │ └── requirements.txt ├── flaskr/ │ ├── flaskr/ │ ├── flaskr.egg-info/ │ ├── venv/ │ ├── LICENSE │ ├── MANIFEST.in │ ├── README.rst │ ├── requirements.txt │ └── setup.py └── venv/ ├── bin/ ├── include/ ├── lib/ └── pip-selfcheck.json 15 directories, 8 files ``` ## Create a `.pyz` app archive _Zipapp_ module command has this syntax: ``` $ python3 -m zipapp APP_DIR -m ENTRYPOINT_MODULE:ENTRYPOINT_FUNCTION -p PYTHON_INTERPRETER ``` I'm going to use [`gunicorn`](gunicorn.org/) to serve my Flask app because it's pure Python and will work accross any supported platform without worrying about binary compilation (e.g. `uwsgi`). When you run `gunicorn` command on the command line [this is the entrypoint](benoitc/gunicorn@4371ff2ed4babed2ffcc8076926c612732fe01d0/setup.py#L107) that gets called: ``` gunicorn.app.wsgiapp:run ``` So the command line flag for my _zipapp_ entrypoint will be `-m 'gunicorn.app.wsgiapp:run'`. I want to use Python 3 so I also set the interpreter with `-p /usr/bin/env python3`. Let's run our _zipapp_ command ``` $ python3 -m zipapp app -m 'gunicorn.app.wsgiapp:run' -p '/usr/bin/env python3' ``` You should get an executable `.pyz` archive with all dependencies bundled inside: ``` $ ls -lh app.pyz -rwxr--r-- 1 user user 4.0M Dec 18 09:38 app.pyz $ file app.pyz app.pyz: a /usr/bin/env python3 script executable (binary data) ``` ## Run the app from archive In order to run my app I have to provide the entrypoint for `gunicorn` like I would run any WSGI Python app. In my case it's `flaskr:app`: ``` $ ./app.pyz flaskr:app [2017-12-18 09:38:35 +0200] [39081] [INFO] Starting gunicorn 19.7.1 [2017-12-18 09:38:35 +0200] [39081] [INFO] Listening at: http://127.0.0.1:8000 (39081) [2017-12-18 09:38:35 +0200] [39081] [INFO] Using worker: sync [2017-12-18 09:38:35 +0200] [39084] [INFO] Booting worker with pid: 39084 ``` Hooray, it's running! 🎉 ## BONUS: Default entrypoint Let's say I don't want to set my `flaskr:app` entrypoint each time I run the `.pyz` archive. You can do that by creating a custom `app` entrypoint `app/__main__.py` with this content: ```python # -*- coding: utf-8 -*- import sys from gunicorn.app.wsgiapp import run sys.argv.append('flaskr:app') sys.exit(run()) ``` The repackage your app ``` $ python3 -m zipapp app -p '/usr/bin/env python3' ``` And run it ``` $ ./app.pyz [2017-12-18 13:35:30 +0200] [44258] [INFO] Starting gunicorn 19.7.1 [2017-12-18 13:35:30 +0200] [44258] [INFO] Listening at: http://127.0.0.1:8000 (44258) [2017-12-18 13:35:30 +0200] [44258] [INFO] Using worker: sync [2017-12-18 13:35:30 +0200] [44261] [INFO] Booting worker with pid: 44261 ```