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:

Your machine — Ubuntu 22.04, libpq already installed
click Run to see this pane's output
A teammate's machine — Ubuntu 20.04, no libpq
click Run to see this pane's output

Same 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:

Your machine
click Run to see this pane's output
A teammate's machine
click Run to see this pane's output

A 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:

Run directly on the host
click Run to see this pane's output
Run inside a minimal sandboxed container
click Run to see this pane's output

Same 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.

Check your understanding
1/4

What does a virtual environment's isolation boundary actually cover?