The Deployment Artifact Model: Build Once, Configure at Runtime
Every page in this section - Docker images, Gunicorn workers, Lambda packages, Kubernetes manifests - answers a version of the same question: how does python app.py on a laptop become a running, observable, replaceable service in production.
The tempting answer is "it depends on the platform," and the platform-specific pages in this section do differ in real ways.
But underneath Docker, AWS Lambda, and Kubernetes sits one shared model: a Python application is packaged into an immutable artifact, configured only at the moment it starts, and handed to an orchestrator that knows how to start it, check on it, signal it, and eventually replace it.
Deployment Basics shows this model's syntax - WSGI and ASGI servers, worker flags, health routes.
This page is the model those syntax choices are expressions of.
Summary
- A deployable Python service is an immutable artifact (a container image, a Lambda package, a wheel) built once, configured entirely through externalized settings at runtime, and run under a process model an orchestrator can observe and replace.
- Insight: Baking configuration or environment differences into the artifact itself reintroduces "works on my machine" at exactly the layer meant to eliminate it - the same artifact must behave identically in staging and production.
- Key Concepts: artifact, immutability, externalized configuration, process manager, lifecycle contract, replace-not-mutate.
- When to Use: Reasoning about why a Dockerfile, a Lambda deployment package, and a Kubernetes Deployment manifest all shape the same underlying decisions differently.
- Limitations/Trade-offs: Immutable artifacts and externalized config add real ceremony - build pipelines, secret stores, health endpoints - that a single-server deploy-by-
scpworkflow does not need, and that overhead only pays for itself past a certain team or scale. - Related Topics: the twelve-factor app, WSGI/ASGI process models, container image layering, rolling deployments.
Foundations
Before this model became standard, deploying a Python app commonly meant git pull on a server, restarting a process, and hoping the server's installed packages, OS libraries, and Python version still matched what the code expected.
That approach ties the application to one specific machine's accumulated state, which makes "redeploy this exact version elsewhere" an open question rather than a guarantee.
The artifact model answers that question by making the build step produce one self-contained, versioned thing - a container image, a Lambda zip or image, a wheel with pinned dependencies - that gets promoted through environments unchanged.
"Unchanged" is the operative word: the same image tag that passed CI runs in staging and, after approval, is the exact same bytes that run in production, not a rebuild from the same source with hopefully-the-same dependency versions.
That guarantee only holds if the artifact is genuinely immutable - nothing about environment, secrets, or deployment target is baked into it, because any of that would make "the same artifact" a different thing per environment.
Configuration therefore has to come from outside the artifact entirely, read at process startup from environment variables, mounted files, or a secrets manager - a discipline widely known as the twelve-factor app's "config" principle, and the direct subject of Configuration & Secrets in Prod.
Mechanics & Interactions
An orchestrator - Gunicorn's master process, a container scheduler, Lambda's execution service, Kubernetes' control plane - relates to your application through a small, consistent lifecycle contract regardless of which one it is.
It starts the artifact as one or more running instances, waits for a signal that the instance is ready to take traffic, routes work to it, and eventually stops it by sending a termination signal and expecting a bounded, graceful shutdown.
Health checks exist because the orchestrator cannot otherwise know whether a started process is actually able to serve requests - a process can be running and still be unable to reach its database, which is exactly the distinction between a liveness check ("is the process up") and a readiness check ("can it serve traffic right now").
# readiness should fail fast if a hard dependency is unreachable -
# that's what tells the orchestrator "don't route to me yet"
@app.get("/health/ready")
async def ready():
if not await db.ping():
raise HTTPException(status_code=503)
return {"status": "ready"}Inside a single artifact instance, a process manager like Gunicorn adds its own layer of the same contract: a master process starts worker processes, restarts ones that crash, and forwards signals it receives down to its workers, so "one artifact instance" is often itself a small process tree, not a single PID.
The reason this model favors replace over mutate is that a running instance's writable state (installed packages, patched files, ad hoc fixes applied by SSHing in) is exactly the drift the artifact model exists to prevent - so a deploy means starting new instances from the new artifact and terminating old ones, never patching a live process in place.
That single idea - new instances replace old ones rather than being edited - is what Zero-Downtime Deploys and rolling updates are actually implementing: keep enough old instances serving traffic while new ones become ready, then retire the old ones only once the new ones have proven healthy.
Advanced Considerations & Applications
The three concrete deployment targets in this section - Docker/Kubernetes, AWS Lambda, and a plain VM running Gunicorn - are different points on the same artifact-and-contract spectrum, not fundamentally different problems.
A container image is the most general artifact shape: it carries the OS layer, the Python runtime, and your dependencies, and any container-capable orchestrator can run it.
A Lambda deployment package narrows the contract: the "orchestrator" is fully managed, but in exchange the process lifecycle is compressed into a single request-scoped invocation, which is why cold starts and per-invocation state matter there in ways they simply do not for a long-lived Gunicorn worker.
Kubernetes formalizes the lifecycle contract most explicitly, with named lifecycle hooks (postStart, preStop), typed probes (liveness, readiness, startup), and a declarative desired-state model that continuously reconciles running instances against what you asked for.
CI/CD pipelines are the mechanism that actually produces and promotes the artifact - CI/CD Pipelines covers the build side of this model, while this page covers what the resulting artifact has to satisfy once it exists.
| Deployment Target | Strength | Weakness | Best Fit |
|---|---|---|---|
| Container + Kubernetes | Full control over runtime, portable across clouds, explicit lifecycle hooks | Real operational surface to run and secure yourself | Services needing custom runtime behavior or multi-cloud portability |
| AWS Lambda / serverless | No servers to manage, scales to zero, pay-per-invocation | Cold starts, execution time limits, awkward for long-lived connections | Spiky, event-driven, or infrequent workloads |
| VM + process manager (Gunicorn) | Simplest mental model, minimal tooling to adopt | Manual scaling and patching, no built-in rolling replace | Small teams or single-service apps not yet needing orchestration |
Common Misconceptions
- "A Docker image is basically a mini virtual machine." It is a layered filesystem snapshot run as an isolated Linux process sharing the host kernel, which is why it starts in milliseconds rather than minutes and why container isolation is weaker than a VM's.
- "Baking environment-specific config into the image keeps things simple." It quietly reintroduces "works on my machine" at the artifact level, since the thing you promote from staging to production is then no longer actually the same thing.
- "Health checks and readiness probes are the same thing." A liveness check answers "is the process alive," while readiness answers "can it serve traffic right now" - collapsing them into one check makes an orchestrator route traffic to instances that are up but not actually ready.
- "Zero-downtime deploys are a Kubernetes-only feature." The underlying idea - start new instances, confirm health, then retire old ones - applies to any orchestrator, including a load balancer in front of two manually managed VMs; Kubernetes just automates it.
- "Serverless removes the need to think about process lifecycle." It compresses the lifecycle into a single invocation and hides the orchestrator, but cold starts, concurrency limits, and connection reuse are still lifecycle concerns, just expressed differently than in a long-lived worker model.
FAQs
What exactly is a "deployment artifact" in this model?
The self-contained, versioned thing produced by your build step - a container image, a Lambda package, a wheel - that gets promoted through environments without being rebuilt or edited along the way.
Why does configuration have to live outside the artifact?
If configuration were baked in, the "same artifact" would actually differ per environment, defeating the entire point of promoting one build unchanged from staging to production.
How do container images, Lambda, and Kubernetes relate under this model?
- A container image is the general-purpose artifact shape
- Lambda narrows the lifecycle to one request-scoped invocation with a fully managed orchestrator
- Kubernetes makes the lifecycle contract (probes, hooks, desired state) the most explicit of the three
What is the practical difference between a liveness and a readiness check?
Liveness answers whether the process itself is still running and should be restarted if not; readiness answers whether it can currently serve traffic, which can be false even while the process is healthy, such as during a slow startup or a lost database connection.
Why is "replace, don't mutate" the deployment pattern instead of patching a running instance?
A running instance's writable state is exactly the kind of drift the artifact model exists to eliminate, so deploys start fresh instances from the new artifact and retire old ones rather than editing anything live.
Does a single VM running Gunicorn still fit this model?
Yes, at a smaller scale - the artifact might just be a pinned virtual environment or a wheel, and the "orchestrator" is Gunicorn's master process plus a deploy script, but the same build-once-configure-at-runtime shape still applies.
Why do cold starts matter for Lambda but not for a container running Gunicorn?
A Gunicorn worker starts once and serves many requests over its lifetime, amortizing startup cost, while a Lambda execution environment can be created fresh per burst of traffic, making per-invocation startup cost visible in a way it never is for a long-lived process.
How does this model change what CI/CD actually needs to do?
CI's job becomes producing exactly one artifact per change and promoting that same artifact through environments, rather than rebuilding from source at each stage, which is the reason build-once-promote-everywhere is the standard CI/CD shape for this model.
Is Kubernetes required to get zero-downtime deploys?
No - the pattern only requires an orchestrator (even a load balancer with manual scripts) that can bring up new instances, confirm they are healthy, then retire the old ones; Kubernetes just automates each of those steps declaratively.
Where do secrets fit if they can't be baked into the artifact?
They are injected at process startup from the runtime environment - environment variables sourced from a secrets manager, mounted files, or a sidecar - so the same artifact can run with different secrets in different environments without being rebuilt.
What's the cost of adopting this model for a very small project?
Real ceremony - a build pipeline, a registry, health endpoints, a secrets store - that a single-server git pull-and-restart workflow avoids, which is why very small or early-stage projects sometimes reasonably defer parts of this model.
Does immutability mean an artifact can never be updated?
It means a given artifact's contents never change after it is built; an update means building a new artifact with a new version tag and replacing running instances with it, not modifying the existing one.
Related
- Deployment Basics - the WSGI/ASGI and worker syntax this model sits underneath
- Dockerizing Python - producing the container-shaped artifact
- Configuration & Secrets in Prod - externalizing config out of the artifact
- Zero-Downtime Deploys - the replace-not-mutate pattern in practice
- Kubernetes for Python Apps - the most explicit version of the lifecycle contract
- Deployment Best Practices - the operability checklist distilled from this model
Stack versions: This page is conceptual and not tied to a specific stack version.