Docker and the Makefile
You have a typed service, a lifespan that loads the model once, Depends wiring, BackgroundTasks, auto-docs, and CORS. The last piece is shipping: a Dockerfile that builds a reproducible image, and a Makefile that hides the incantations behind simple targets.
FROM python:3.11-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
WORKDIR /app
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
COPY pyproject.toml ./
RUN uv sync --frozen
COPY . .
ENV PYTHONUNBUFFERED=1
ENV PATH="/app/.venv/bin:$PATH"
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]Slim Python base, uv for fast installs, copy pyproject first so Docker caches the dependency layer, then copy the rest. No reload flag because this is the production image.
setup:
@cp -n .env.example .env || true
@uv sync
run:
@uv run uvicorn main:app --reload --host 0.0.0.0 --port 8000
dev: setup run
build:
docker compose build
up:
docker compose up -d
down:
docker compose downA Makefile gives you verbs your teammates can memorize in a minute. make dev gets them from clone to running server. make up and make down wrap Docker compose.
The lifespan runs once per worker process. If you start uvicorn with multiple workers, each worker loads its own copy of the client. That is usually fine. For a very large model you either keep a single worker, share state over a network service, or move to a dedicated serving layer. For the bulk of services, per-worker is exactly the tradeoff you want.
AI prompt: Try it: review your Dockerfile
Loading practice…
Checkpoint: Final checkpoint
Loading practice…