Chapter 01
From notebook to package
The shape of the whole lifecycle, the tools that fill each step, and how to turn notebook code into a tested, locked, containerised Python project that anyone can run.
The MLOps lifecycle
Also called ML lifecycle, MLOps loop, LLMOps
- What
- The loop a production model lives in: data is collected and validated, a model is trained and evaluated, approved and deployed, served to users, monitored, and retrained when it decays. LLMOps is the same loop where prompts, eval sets and model versions are the moving parts.
- Why
- Every tool and practice in this sheet exists to make one arrow in the loop repeatable. Knowing the loop tells you which gap is hurting your project right now.
data -> validate -> train -> evaluate -> register
^ |
| v
retrain <- monitor <- serve <- deploy <- approveWatch out
Treating deployment as the finish line: most of a model's cost and risk comes after it goes live.
Tool map per stage
Also called MLOps stack, MLOps tools
- What
- A reference of common open source tools for each step of the lifecycle. Pick one per row and learn it well; they are largely interchangeable once you understand the job each row does.
- Why
- Job adverts list tools, but the skill is the job behind them. A map keeps you from learning three orchestrators and no monitoring.
Package uv, pytest, Docker
Version Git, DVC
Track MLflow tracking and model registry
Orchestrate Prefect, Airflow, Dagster
Serve FastAPI, BentoML, vLLM
CI/CD GitHub Actions, promptfoo
Monitor Prometheus, Grafana, Evidently
Trace, eval Langfuse, Arize Phoenix, MLflowWatch out
Adopting a whole platform before you have one model in production that needs it.
Project layout and pyproject.toml
Also called src layout, pyproject, Python package
- What
- A repository where notebook code becomes importable modules under
src/<package>/, with dependencies, entry points and tool settings declared in onepyproject.tomlfile. Notebooks stay for exploration and import from the package. - Why
- Code in a package can be tested, reused by the training job and the API, and installed the same way on a laptop, in CI and in a container.
- Example
- src/churn/data.py loads and splits data, src/churn/features.py builds features, src/churn/train.py is the entry point, and tests/ mirrors them.
Watch out
Copying cells from a notebook into the serving code, so training and serving drift apart without anyone noticing.
Dependencies and lockfiles
Also called uv, uv.lock, pinned dependencies, virtual environment
- What
- A lockfile records the exact version of every package, including indirect ones, that your project resolved to. uv, a fast Python package and project manager, writes
uv.lockand recreates the same environment anywhere with one command. - Why
- A model trained with one version of a library can behave differently or fail to load with another. Locked environments make training runs and deployments repeatable.
uv init churn-model --package
cd churn-model
uv add scikit-learn pandas mlflow
uv add --dev pytest ruff
uv run pytest
git add pyproject.toml uv.lockWatch out
Running pip install by hand on the server, so production has versions nobody wrote down.
Config and secrets
Also called environment variables, params.yaml, 12-factor config
- What
- Settings that change between runs or environments, such as paths, hyperparameters, model names and API keys, kept out of the code: parameters in a versioned config file, secrets in environment variables or a secrets manager.
- Why
- The same code can then train on a sample in CI and on full data in production, and no key ever lands in Git or a notebook output.
Watch out
Leaving an API key in a notebook cell that is later committed or shared.
Training entry point
Also called training script, CLI, random seed
- What
- One command that trains the model from start to finish: it reads config, loads data, fixes random seeds, fits the pipeline and saves the artefact, with no manual steps in between.
- Why
- A pipeline, a CI job or a colleague can run it unattended. If training needs someone to click through a notebook, it cannot be automated or reproduced.
import argparse, joblib, numpy as np
from churn.data import load_split
from churn.model import build_pipeline
def main() -> None:
p = argparse.ArgumentParser()
p.add_argument("--data", required=True)
p.add_argument("--seed", type=int, default=42)
args = p.parse_args()
np.random.seed(args.seed)
X, y = load_split(args.data, "train")
model = build_pipeline(seed=args.seed).fit(X, y)
joblib.dump(model, "artefacts/model.joblib")Watch out
Saving only the estimator and not the preprocessing, so serving code has to rebuild features by hand.
Model artefact formats
Also called joblib, pickle, skops, ONNX, model serialisation
- What
- The file a trained model is saved as: joblib or pickle for scikit-learn objects, skops for a safer scikit-learn format, the framework's own format for PyTorch or XGBoost, and ONNX as a portable format many runtimes can load.
- Why
- The format decides which runtime can serve the model, how fast it loads and whether loading an untrusted file can run code.
Watch out
Loading a pickle or joblib file from an untrusted source: unpickling can execute arbitrary code.
Tests for ML code
Also called pytest, unit tests, smoke test
- What
- Fast tests with pytest for the deterministic parts: feature functions, data loading, schemas and output shapes, plus a smoke test that trains on a tiny sample and checks predictions have the right type and range.
- Why
- Most production ML failures are ordinary bugs in data and feature code, not in the maths. Tests catch them before a long training run or a deploy.
Watch out
Testing that accuracy equals an exact number, which fails on every harmless change; test ranges and invariants instead.
Container image
Also called Dockerfile, Docker, image
- What
- A packaged file system with your code, a Python runtime and locked dependencies, built from a Dockerfile, that runs the same way on a laptop, in CI and in the cloud.
- Why
- Training jobs and model APIs both ship as images. It removes "works on my machine" and is what orchestrators and serving platforms expect.
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-install-project
COPY src ./src
RUN uv sync --frozen --no-dev
CMD ["uv", "run", "python", "-m", "churn.serve"]Watch out
Copying the whole repository, including data and secrets, into the image.