How do you package and deploy a Python application?

7 minintermediatedeploymentdockerpackagingproduction

Quick Answer

For a library, build a **wheel** (`python -m build`) and publish it to PyPI (or a private index) so it can be installed via `pip`. For a deployable application/service, the standard modern approach is a **Docker container** with dependencies pinned via a lock file, giving a reproducible runtime environment independent of the host machine's Python version or installed packages — orchestrated via Kubernetes, a PaaS (Heroku, Fly.io), or a serverless platform depending on the workload.

Detailed Answer

Packaging a library: wheels and PyPI

python -m build              # builds dist/mypackage-1.0.0-py3-none-any.whl + a source dist
python -m twine upload dist/*   # publishes to PyPI

A wheel (.whl) is a pre-built, ready-to-install package format — pip install mypackage fetches and installs it without needing to run any build step on the user's machine (unlike a source distribution, which may require compiling C extensions locally). This is the right target when the deliverable is a reusable library other projects will pip install.

Packaging an application: containerization

FROM python:3.12-slim

WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN pip install poetry && poetry install --no-dev --no-root

COPY . .
CMD ["python", "-m", "myapp"]

For a deployable service (as opposed to a library), a Docker image bundles the exact Python version, exact pinned dependencies (via the lock file), and the application code into one artifact that runs identically regardless of the host machine — eliminating "works on my machine" class issues entirely, since the container is the runtime environment.

Why containers dominate for applications specifically

  • Reproducibility: the same image runs identically in CI, staging, and production — no drift from differing host Python versions or system libraries.
  • Isolation: no conflicts with other applications' dependencies on the same host.
  • Orchestration compatibility: container images are the standard unit Kubernetes, ECS, and most modern deployment platforms expect.

Deployment targets, by workload shape

WorkloadCommon choice
Long-running web service, need fine controlKubernetes / ECS running the container
Simpler apps, less ops overhead desiredPaaS (Heroku, Fly.io, Render) — often deploys straight from a Procfile/buildpack, no Dockerfile needed
Event-driven, sporadic/bursty invocationsServerless (AWS Lambda, Google Cloud Functions) — packaged as a zip/layer or container image, billed per invocation
CLI tool distributed to end usersA wheel published to PyPI, or a bundled executable (pyinstaller, shiv) for non-Python-savvy users

Entry points and process management in production

gunicorn myapp.wsgi:application --workers 4     # WSGI, process-based concurrency
uvicorn myapp.asgi:application --workers 4        # ASGI, event-loop-based concurrency per worker

A production WSGI/ASGI application is served by a dedicated application server (Gunicorn, Uvicorn) rather than a development server (Django's runserver, Flask's built-in dev server) — the dev servers are explicitly not designed for production traffic (no proper worker management, no production-grade concurrency handling).

Interview-ready summary: Libraries are packaged as wheels and published to PyPI for pip install; deployable applications are typically containerized with pinned dependencies for full environment reproducibility, then run via an orchestration platform or PaaS matched to the workload's shape (long-running service, serverless, or CLI tool). Production traffic is always served by a dedicated app server (Gunicorn/ Uvicorn), never a framework's built-in development server.