Why containers
The environment problem: what a venv can't reach
A virtual environment, from the Python setup lesson,
isolates Python packages — one project’s pip installs don’t leak into
another’s. But a venv sits entirely inside one Python installation, on
one operating system — it has no ability to change or isolate anything
below that: not the Python version itself, not OS-level system
libraries, not the OS itself.
Both machines below start with an identical requirements.txt and an
identical venv setup script. Click Run on each pane and watch the same
commands play out differently depending on what’s already on that
machine:
click Run to see this pane's outputclick Run to see this pane's outputSame requirements.txt, same venv commands — it still fails, because the
missing piece is a system library, something a venv was never able to
touch in the first place. Now both panes run docker build -t my-app .
and docker run my-app instead — identical output on both, since neither
machine’s own OS is what’s actually running the code anymore:
click Run to see this pane's outputclick Run to see this pane's outputA second, different reason: restricting what code can touch
There’s a motivation for containers that matters specifically for the kind of code this course is building toward, separate from “it runs the same everywhere”: isolation as a security boundary, not just a consistency guarantee. An agent that executes tool calls, or runs code an LLM generated, is running code whose exact behavior you don’t fully control in advance — restricting what that code can actually reach (the filesystem, the network, environment variables holding credentials) matters in a way it doesn’t for code you wrote yourself and trust completely.
A small script standing in for “an agent’s generated code” — it lists
the contents of /, reads an environment variable named
AWS_SECRET_KEY, and attempts to make an outbound network request.
Click Run on each pane to compare running it directly on the host versus
inside a minimal, locked-down container:
click Run to see this pane's outputclick Run to see this pane's outputSame code, dramatically different capabilities — not because the code changed, but because the boundary around it did. This is the shape of the actual problem: you don’t need to fully trust code to safely run it, if the container around it only exposes exactly what that code is supposed to need.
A trap the isolation motivation makes worse, not better: secrets in images
Isolation only helps if you don’t undermine it yourself — and there’s a
specific, easy mistake worth naming here: baking a secret (an API key, a
.env file, a database password) directly into an image, rather than
passing it in at docker run time (the way -e does, covered later in
this lesson’s concept on building and running a container). A
COPY .env . instruction, or a hardcoded key in a RUN command, means
that secret is now baked permanently into the image itself — present in
every layer, every container built from it, and anywhere that image
ever gets pushed or shared, even if the file is later deleted in a
subsequent instruction. Deleting a file in a later layer doesn’t remove
it from the earlier layer where it was actually added — the same
layer-caching mechanics covered later in this lesson mean the earlier
layer, secret included, is still sitting there in the image’s history.
The fix, previewed here and covered properly later: pass secrets in at
run time, never bake them into the build.
What does a virtual environment's isolation boundary actually cover?