How do you choose between a zipapp, an interpreter-carrying bundle, and an image for a Python service?
answer
- Start from what the host guarantees
- Interpreter present, or must it travel?
- Who patches the interpreter's advisories?
- One artifact, atomic rollback
- One paved road beats five choices
basics
~20 sStart from what the target machine guarantees. If a compatible Python is guaranteed, a zipapp or vendored directory is the smallest thing that works. If it is not, the interpreter must travel — in a bundle or an image carrying the environment.
solid answer
~50 sThe first axis is what the target guarantees. A host with a guaranteed compatible interpreter and pure-Python dependencies takes a single `.pyz`; once binary dependencies appear that becomes a directory or an environment built on the target's own platform. A host with no interpreter and no container runtime — an appliance, a customer machine, an on-prem box — needs a bundle carrying the interpreter. A host with a container runtime takes an image carrying the installed environment, the default for services because the artifact *is* the environment and rollback is atomic. The second axis is ownership: ship the interpreter and you own its security patching on your own cadence, which on a three-week release train demands an out-of-band rebuild path. Then weigh size, cold start and who debugs it at 3 a.m., and standardise on one default shape with a named escape hatch.
go deeper
Recall the basic split: some artifacts carry only your code and need Python on the machine, others carry the interpreter and environment too. Know which side a zipapp falls on.
Explain what each shape contains and what it therefore requires from the host, including why binary dependencies rule out some options and why resolving on the build machine must target the deploy platform.
Argue a concrete choice for a concrete service and defend it on guarantees, reproducibility, cold start and debuggability, and describe the build pipeline that produces the artifact from a lockfile.
Own the estate-level tradeoff: who carries the interpreter's patching burden, how an urgent advisory ships outside the normal release cadence, and why one adequate default shape with a named escape hatch beats several individually optimal ones.
## The question behind the question Every Python deployment shape answers one question: *what does the target machine already guarantee?* Python is unusual here because the interpreter is both a runtime and a dependency resolver, and a program's dependencies may include native code compiled against the interpreter's ABI. So the artifact you choose is really a statement about how much of that stack you are willing to leave ambient. ## The shapes, from thinnest to thickest **A source tree plus an install step.** The target has an interpreter and a package manager, and the deploy resolves and installs. It is the thinnest artifact and the worst operational contract: your deploy is a build, it needs network access to an index, and it can fail at 3 a.m. for reasons that have nothing to do with your change. Defensible only where a build cannot be reproduced ahead of time. **A zipapp.** One file containing your code and its pure-Python dependencies, executed by the host's interpreter. Smallest useful artifact, atomic to place and to roll back, no install step. Requires a guaranteed interpreter version on the host and forbids compiled dependencies inside the archive. Excellent for internal tools and jobs on a fleet you control. **A vendored directory.** The same idea unzipped: resolve on a build machine with `pip install --target`, ship the tree, put it on the path. Handles binary dependencies as long as you resolved for the target's platform. Common where a runtime unpacks a flat code directory for you. **An environment built on the target.** A virtual environment created and populated at build or install time on a machine of the target's own platform. Coherent upgrades and uninstalls, working console scripts, but the environment lives on the host and is therefore mutable state you now have to reason about across releases. **A bundle carrying the interpreter.** One artifact that includes CPython, your code and every dependency. The host needs nothing but a compatible operating system and architecture. This is the answer for appliances, customer-installed software, air-gapped estates and anywhere a container runtime is unavailable. **An image carrying the environment.** The interpreter, the environment and the operating-system libraries are the artifact. For services on a platform that runs containers, this is the default: the environment is immutable, versioned and identical from CI to production, rollback is replacing one reference, and nothing about the host's Python matters. ## The axes to argue on **Guarantee.** Ranked first because it eliminates options outright. 'The host has Python 3.14' is only a guarantee if something enforces it; on a heterogeneous fleet it is a hope, and hopes fail asymmetrically — quietly on the one host that drifted. **Ownership of the interpreter's security posture.** This is the axis most candidates miss and the one a lead is really being asked about. If you rely on the host's Python, the platform team patches it and your version can move under you. If you ship the interpreter — in a bundle or an image — you own every CPython advisory for every artifact in the estate, and patching means rebuilding and redeploying all of them. On a three-week release train that is fine for routine work and unacceptable for an urgent advisory, so shipping the interpreter obliges you to build a rebuild-and-ship path that does not wait for the train, plus an inventory that answers 'which artifacts contain which interpreter build'. **Reproducibility.** An artifact that pins the interpreter, the dependency set and the platform baseline turns 'works on my machine' into a testable property. Anything resolved at deploy time cannot. **Cost of the artifact.** Size and cold start matter where they matter: a function-style runtime charges you for unpacking, a fleet of edge devices for bandwidth, a service starting a thousand replicas for image pull. For one long-running worker they are noise, and choosing on size alone is a classic misjudgement. **Debuggability.** Ask who opens the failing thing at 3 a.m. and with what tools. A directory of readable Python on a host is the easiest thing in the world to inspect; a single-file bundle is the hardest, because the code is inside an archive and the interpreter is not the one on the path. ## The organisational answer The technically correct choice per service is less valuable than one default the whole estate shares. A lead's answer is a paved road: one default shape (for most companies, an image for services and a zipapp or bundle for tools that must run outside the platform), one supported build pipeline that produces it from a lockfile, an explicit escape hatch with a named owner for the workloads that genuinely cannot use the default, and a rule that the artifact is what gets tested — CI exercises the built thing on a clean host, not the repository. Five shapes chosen individually well is worse than one shape chosen adequately, because every shape is a separate build pipeline, a separate patching story, and a separate way to be woken up.
- A severe CPython advisory lands. How does your choice of artifact shape the response?If the host owns the interpreter, the platform team patches it and your services inherit the fix on restart; your exposure is that the version can also move under you unannounced. If you ship the interpreter, every artifact containing the vulnerable build must be rebuilt and redeployed, so you need an inventory mapping artifacts to interpreter builds and a release path that does not wait for the normal train. That obligation is part of the decision, not an afterthought.
- When is shipping a source tree with an install step at deploy time still the right answer?Rarely, and only with a reason: a target platform you cannot reproduce in the build, an environment an operator contractually owns, or a genuinely air-gapped index mirror that makes install-time resolution deterministic. The cost is that every deploy becomes a build that can fail for reasons unrelated to your change, at the worst possible time, so it should be a deliberate exception with an owner rather than the default.
- How do you keep per-team choice from producing five deployment shapes across the estate?Make one shape the paved road: a single supported build pipeline that turns a lockfile into the default artifact, wired into CI so using it is the least effort. Allow exceptions but require a named owner and a written reason, and publish the running cost of each extra shape — its own patching story, its own smoke tests, its own on-call knowledge. Consolidation is a platform decision, not a per-service preference.
saying these in an interview costs you the question
- Says an image is always the right answer
- Assumes the deploy host matches the developer laptop
- Ignores who patches the interpreter after an advisory
- Picks a shape on artifact size alone
- Thinks a zipapp removes the need for an interpreter
- Lets every service invent its own deployment shape