Warming up the neural circuits...
By the end of this chapter you will:
Shipping a backend is not finished when tests pass locally. It is finished when deployment is repeatable, observable, and recoverable.
"Works on my machine" usually means:
and disciplined deployment workflows close that gap by making runtime behavior explicit.
FROM python:3.12-slim AS base
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
FROM base AS builder
RUN apt-get update && apt-get install -y --no-install-recommends build-essential && rm -rf /var/lib/apt/lists/*
COPY pyproject.toml ./
RUN pip install --upgrade pip && pip wheel --no-cache-dir --wheel-dir /wheels .
FROM base AS runtime
RUN useradd -m appuser
COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir /wheels/*
COPY . .
USER appuser
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]Multi-stage builds keep runtime images lean and reduce attack surface.
Common options:
Tune workers based on CPU, memory, and workload profile; benchmark with realistic traffic.
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
def health() -> dict[str, str]:
return {"status": "ok"}
@app
Operational defaults:
Graceful lifecycle handling is part of reliability, not optional polish.
Practical strategies:
Pick strategy based on blast radius tolerance and operational maturity.
Frequent failure causes:
Create a runbook that checks these in order before deeper debugging.
A deployment pipeline is part of the product. If deploy cannot be repeated safely by another engineer, it is not production-ready.
| Stage | Must-have checks |
|---|---|
| Build | deterministic image, pinned dependencies |
| Pre-deploy | tests pass, migration reviewed, env variables present |
| Deploy | health probes green, startup logs clean |
| Post-deploy | key endpoints and auth flow smoke-tested |
| Recovery | rollback command and artifact known |
| Mistake | Why it hurts | Better move |
|---|---|---|
| Single-stage bloated image | Slower deploys and larger attack surface | Multi-stage minimal runtime image |
| Running as root in container | Elevated compromise impact | Use non-root runtime user |
| No readiness probe | Traffic routed before service is ready | Explicit readiness endpoint and checks |
| Coupling app and DB migrations manually | Human error in deploy sequence | Automate migration step in pipeline |
| No rollback rehearsal | Slow incident response | Practice rollback path regularly |
Existing FastAPI projectLean runtime image with non-root userAPI app with database dependencySeparate liveness/readiness checksBuild artifact and target environmentOrdered release workflowLogs and health probe failuresRoot cause and fixCurrent and previous image tagsMeasured rollback recovery stepsBeginner:
"Why use multi-stage Docker builds for backend services?"
They reduce runtime image size and attack surface while keeping builds reproducible.
"What is the difference between liveness and readiness checks?"
Liveness checks if process is alive; readiness checks if service is ready to receive traffic.
Senior:
"How do you make backend deployments safe under continuous delivery?"
Use automated gates, gradual rollout strategy, health verification, observability, and rehearsed rollback paths.
"What deployment anti-pattern causes the most avoidable incidents?"
Manual, undocumented deployment steps that bypass repeatable pipeline checks.
Build lean image -> configure runtime -> verify health -> deploy gradually -> monitor -> rollback if needed
Readiness controls traffic safety
Non-root runtime and secret hygiene are baseline controlsWhat is a key benefit of multi-stage Docker builds?
Use separate liveness and readiness semantics when orchestration platform supports both.