Lecture 23 - Dependency Management, Virtual Environments, and Containers
1. Dependency management
2. Virtual environments
venv and pip, and the requirements.txt every Dockerfile readsconda for software that is not Python, uv for speed3. Containers
4. Your first image
-c means “run this command”pip, conda, and others install and track those packagesconda, pip, uv)renv (R) or uv (Python), which record exact versionspip (Python only): requirements.txtconda (Python and R): environment.ymluv (Python only): pyproject.toml and uv.lockrequirements.txt best: every Dockerfile reads it, including your project starter’svenv and pip: an isolated Pythonvenv ships with Python. It creates a folder with its own python and site-packages.venv/ to your .gitignore (Python 3.13 and later does this for you). The package list goes into git, but the folder stays on your machinewhich python tells you which interpreter you are actually using:.venv/bin/python, so the environment is active. The install itself:python3 -m venv /opt/venv and sets ENV PATH="/opt/venv/bin:$PATH", so later steps use that Python without activating itrequirements.txt: the file you commitpip freeze writes it and pip install -r reads itpip freeze prints every installed package with its exact version:pip lists the dependencies of your dependencies== pins say “these exact versions”pip freeze inside a conda environment and some lines come out as name @ file:///... local paths, which install nowhere elsevenv and the problem never appears--from-history, which records only the packages you asked for:conda env create --file environment.yml, then conda activate l23-demo--from-history and it lists every transitive package instead:--no-builds flag strips the build strings, but macOS-only packages stay in the file--from-historyThe build string (py39h12345) encodes metadata:
py39, py310)gcc9, clang)nomkl, cuda)The platform (linux-64, osx-arm64) is stored separately, not in the build string
Why do builds exist?
The line libblas=3.11.0=9_h51639a9_openblas on the previous slide will not resolve on a Linux collaborator’s machine
uv: a fast Python package manageruv init l23-uv-demo --python 3.14
cd l23-uv-demo
uv add polars requests
Using CPython 3.14.6 interpreter at:
/opt/homebrew/opt/python@3.14/bin/python3.14
Creating virtual environment at: .venv
Resolved 8 packages in 378ms
Prepared 2 packages in 6.66s
Installed 7 packages in 10ms
+ polars==1.44.2
[...]
+ requests==2.34.2uv: project files and sharingpyproject.toml holds your direct dependencies and is meant for humans. Mine after uv add:[sections]. Created by Tom Preston-Werner, a GitHub co-founder. More here
uv init also writes .gitignore, .python-version, README.md and main.py, and starts a git repositoryuv.lock is generated for you, recording the exact version and hash of every packagepyproject.toml and uv.lock. Your collaborator clones and runs uv sync to get the same environment:uv run executes inside the project environment, so there is nothing to activateuv manages Python versions too: uv python install 3.14 and uv python pin 3.14uv hereuv: handing a project to Dockerrequirements.txtrequirements.txt, and uv export writes one from your lock file:uv export --format requirements-txt --no-hashes
Resolved 8 packages in 2ms
# This file was autogenerated by uv via the
# following command:
# uv export --format requirements-txt
# --no-hashes
certifi==2026.7.22
# via requests
charset-normalizer==3.5.1
# via requests
polars==1.44.2
# via l23-uv-demo
requests==2.34.2
# via l23-uv-demo
[...]--no-hashes and each line also carries the wheel’s SHA-256, which pip then verifies# via comments name which package pulled each dependency in. pip freeze never doesvenv and pip workflow at uv’s speed? Drop-in replacements:venv slides, with uv doing the installamd64 or Apple Silicon arm64)Lecture 10 listed four ingredients of a reproducible result: code, data, environment, documentation. A container ships the environment with the code already inside it
Dockerfiledocker command is a client. It sends every instruction to a background daemon, which does the building and the runningClient, daemon, and registry, from the Docker documentation
Every docker command on these slides ran through OrbStack. The commands are identical under Docker Desktop
hello-worlddocker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
58dee6a49ef1: Pull complete
Digest: sha256:5dd0d3e6e255...
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be
working correctly.
[...]Nothing happens and the terminal complains about a daemon? The application is not running: Appendix 02
Dockerfile is a plain-text recipe that builds everything needed to recreate a projectDockerfile with no extensionlectures/lecture-23/docker/ in the course repositoryrequirements.txt, same file from the pip slides:hello.py, so the container has something to do:Dockerfile. The next slide takes it line by line# Start from an official Python image
FROM python:3.14-slim
# Everything below happens inside /app
WORKDIR /app
# Copy requirements first, so Docker can
# cache the install
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy the script
COPY hello.py .
# What runs when the container starts
CMD ["python", "hello.py"]--no-cache-dir -r means “install from the file and don’t keep a cache of the wheels”-t tags the image datasci350-example, and . is the folder holding the Dockerfile:docker build runs the instructions in orderCOPY and RUN add layers to the image. CMD only records the start command| Instruction | What it does | Runs when |
|---|---|---|
FROM |
Picks the base image | Build |
WORKDIR |
Sets the folder for every later step | Build |
COPY |
Brings a file from your folder into the image | Build |
RUN |
Executes a command inside the image | Build |
CMD |
Names the command the container starts with | Start |
The official Python image on Docker Hub: https://hub.docker.com/_/python
docker build --progress=plain -t datasci350-example .
#5 [1/5] FROM docker.io/library/python:3.14-slim
#5 sha256:bf7af02297 30.16MB / 30.16MB 63.7s done
#5 DONE 64.4s
#6 [2/5] WORKDIR /app
#6 DONE 0.2s
#7 [3/5] COPY requirements.txt .
#7 DONE 0.0s
#8 [4/5] RUN pip install --no-cache-dir -r
requirements.txt
#8 51.68 Successfully installed numpy-2.5.2
pandas-3.0.5 [...]
#8 DONE 51.9s
#9 [5/5] COPY hello.py .
#9 DONE 0.1s
#10 exporting to image
#10 naming to datasci350-example:latest done
#10 DONE 2.7s[1/5] to [5/5], are every instruction except CMD[1/5] took 64.4 seconds because Docker downloaded the python:3.14-slim base image, about 44 MB compressed. That happens once per machine[4/5] is the pip install, and its 51.9 seconds is the slow part of every rebuilddocker images shows what came out:docker build -t datasci350-example .
#5 [1/5] FROM docker.io/library/python:3.14-slim
#5 DONE 0.0s
#6 [3/5] COPY requirements.txt .
#6 CACHED
#7 [4/5] RUN pip install --no-cache-dir -r
requirements.txt
#7 CACHED
#8 [2/5] WORKDIR /app
#8 CACHED
#9 [5/5] COPY hello.py .
#9 CACHED
#10 exporting to image
#10 DONE 0.1sCACHED and the build finishes in 0.754 secondsCOPY requirements.txt sits above COPY hello.py: editing your script invalidates only the last layer, and the slow pip install stays cachedCOPY . . at the top and any edit re-installs every packageDockerfile uses the same ordering, for the same reason# numbers are log IDs, so a cached run can list steps out of order. The [n/5] labels give the real orderdocker run to Docker Hub--rm flag deletes the container when it exits, so you do not accumulate hundreds of dead ones:docker push sends only your laptop’s chip type. Optional, if you share an image with people on other computers, build for both chips:amd64) and Apple Silicon (arm64) chips, so it runs on Windows, Linux and any Macdocker build and docker run on a clean machine, and either your report appears or it does notdocker/ folder from the course repository: https://github.com/danilofreire/datasci350/tree/main/lectures/lecture-23/dockerdocker build -t datasci350-example .docker run --rm datasci350-example. Three lines appearrequirements.txt. Add the line polars==1.44.2hello.py. Import polars and print pl.__version__docker build -t datasci350-example . againCACHEDdocker run --rm datasci350-example. Four lines nowCOPY lines and rebuildSolution: Appendix 01
print: the script was fine, but the environment changedvenv and pip: pip freeze writes pins, pip install -r reads them--from-history travels, build strings stay homepyproject.toml + uv.lock, then uv syncYour container is the deliverable: I run docker build and docker run, and your report appears or it does not 😅
Quiz 04: web APIs and JSON, from Lectures 18 and 19. No new material that day
Lecture 25, on 24 November, is Docker for Data Science. We take the project starter’s Dockerfile instruction by instruction, and it installs Quarto as well as Python
Everything today was the small version of that file
Before then:
docker run hello-worlddocker build once on your own wifiQuiz 05 covers Lectures 21, 22, 23, and 25. The final project is due on the last day of classes
Steps 5 and 6, the two edited files:
Step 9, the run:
Steps 7 and 8, the rebuild on my laptop:
#7 forced every later layer to run againpolars, which the first build never fetchedCOPY lines swapped, editing hello.py alone invalidates COPY hello.py and re-runs pip install for nothingCannot connect to the Docker daemon
Docker Desktop is not running. Start the application, wait for the whale icon to settle, and try again. On my laptop OrbStack was stopped, and docker info answered failed to connect to the docker API at unix:///Users/dafreir/.orbstack/run/docker.sock.
permission denied ... /var/run/docker.sock
On Linux, your user is not in the docker group. Run sudo usermod -aG docker $USER, then log out and back in.
pip: command not found
Your virtual environment is not active. Run source .venv/bin/activate and check with which python, which should print a path ending in .venv/bin/python.
No matching distribution found for numpy==2.5.2
That version does not exist for your Python. Check the pin against PyPI, or loosen it to numpy>=2.0.
toomanyrequests: You have reached your pull rate limit
Docker Hub allows 100 anonymous pulls every six hours from one address. Run docker login with your free account (200 pulls) and pull again.
failed to read dockerfile: open Dockerfile: no such file or directory
You are in the wrong folder. The . at the end of docker build -t name . is where Docker looks, so cd into the folder holding the Dockerfile