Phase 0: Development Environment & Mathematical Foundations · 45 min · Docker · uv · VS Code
The Problem
A data science student shares a Jupyter notebook. The reviewer tries to run it. It fails because of a missing sklearn version, a different Python version, and an environment variable pointing to a local file path. The student says, "It works in my Colab."
Professional AI engineers ship reproducible environments. Every dependency is locked. Every configuration is explicit. Every collaborator can run the project identically.
"Works on my machine" is a bug, not an excuse
The whole job of this lesson is to make that sentence impossible to say. When your environment is locked and explicit, your code runs the same on your laptop, your teammate's, and the production server — which is the baseline expectation on any real team.
The Concept
Why ML Environments Are Harder Than Web Dev Environments
A web developer's dependencies are usually pure Python or JavaScript packages. An ML engineer's dependencies include compiled C extensions (NumPy, PyTorch), CUDA libraries that must match the GPU driver, system-level binaries (tokenizers, FAISS), and sometimes platform-specific wheels that differ between macOS, Linux, and Windows. A single version mismatch in any of these layers can produce silent numerical errors, crashes on import, or training runs that work on one machine but produce different loss curves on another.
This is why ML reproducibility is not just "pin your pip versions." You need to control four layers:
- Python version — different Python versions have different standard library behaviors, different
asyncio implementations, and different default dict ordering guarantees - Package versions — PyTorch 2.1 and 2.2 produce different numerical outputs on the same input due to internal algorithm changes; scikit-learn 1.3 and 1.4 changed default hyperparameters for several estimators
- System libraries — CUDA 12.1 vs 12.2, different BLAS implementations (OpenBLAS vs MKL), different glibc versions on Linux
- Hardware — GPU model affects which CUDA features are available; CPU architecture affects which instruction sets (AVX2, AVX-512) the compiled extensions use
In practice, you control layers 1-2 with a package manager and lockfile, layer 3 with Docker, and accept layer 4 as a variable you document rather than control.
The Environment Stack
┌─────────────────────────────────────────────────────────────┐
│ IDE + Extensions │
│ VS Code / Cursor / Windsurf with Python + AI extensions │
├─────────────────────────────────────────────────────────────┤
│ Container (optional but recommended) │
│ Docker: exact OS + system libraries + CUDA │
├─────────────────────────────────────────────────────────────┤
│ Python Environment │
│ uv / conda: isolated Python + packages │
├─────────────────────────────────────────────────────────────┤
│ Package Manager │
│ uv (fast, lockfile) / pip (basic) / conda (scientific) │
├─────────────────────────────────────────────────────────────┤
│ Version Control │
│ Git + GitHub: branches, PRs, CI/CD │
└─────────────────────────────────────────────────────────────┘
Each layer solves a specific failure mode. Git handles code changes and collaboration. The package manager handles Python-level dependencies. Docker handles system-level dependencies. The IDE ties it together with linting, testing, and debugging. Skip a layer and you get a specific class of bug: skip the lockfile and you get version drift; skip Docker and you get "works on my OS but not theirs"; skip Git and you lose the ability to roll back a broken change.
Why uv Over pip?
For years, pip was the only option and it worked — slowly. pip resolves dependencies by backtracking: it tries a version, finds a conflict, backtracks, tries another. For a dependency tree with 200 packages (typical for an ML project), this can take minutes and sometimes fails to find a valid resolution at all. uv solves this with a Rust-based resolver that uses a global cache and parallel downloads, making dependency resolution 10-100x faster.
But speed is not the main reason to use uv. The main reason is the lockfile. A lockfile records the exact version, hash, and source of every package installed — including transitive dependencies you never explicitly asked for. When your teammate runs uv sync, they get byte-for-byte identical packages. With pip alone, running pip install -r requirements.txt resolves versions at install time, which means two people installing on different days can get different transitive versions even with the same requirements file.
| Feature | pip | uv |
|---|
| Install speed | ~30s for ML stack | ~3s for ML stack |
| Lockfile | ❌ (pip-compile needed) | ✅ Built-in uv.lock |
| Resolution | Slow, sometimes conflicts | Fast, reliable |
| Virtual envs | Manual (python -m venv) | Automatic |
| Python versions | External (pyenv) | Built-in (uv python install) |
A teammate clones your repo and it fails because they have a different package version than you. What would have prevented this?
A lockfile records the exact version of every dependency — including transitive dependencies you didn't explicitly install. When your teammate installs from it, they get byte-for-byte the same packages you have — eliminating "different version" failures.
Project Structure for AI Engineering
my-ai-project/
├── .github/
│ └── workflows/
│ └── ci.yml # Automated tests
├── src/
│ ├── __init__.py
│ ├── main.py # Application entry
│ ├── models/ # ML model code
│ └── utils/ # Shared utilities
├── tests/
│ └── test_main.py
├── data/
│ └── .gitkeep # Data dir (gitignored contents)
├── notebooks/
│ └── exploration.ipynb # Experiments only
├── prompts/ # Versioned prompts
├── evals/ # Evaluation configs
├── .env.example # Required env vars (no secrets)
├── .gitignore
├── pyproject.toml # Project config + dependencies
├── uv.lock # Locked dependency versions
├── Dockerfile
├── docker-compose.yml
└── README.md
Unlock the full lesson
You've read the first 2 sections. The rest of this lesson covers Build It, Use It, Ship It, Evaluation, Exercises, Key Terms, Common Pitfalls, Interview Framing — plus a hands-on lab, quiz, and project artifact.
Create a free account to unlock Phase 0 and Phase 1 of every course — no credit card.