Skip to content

Environment consistency

  • Lesson 1
  • 35 min
  • Module 1: Understand containers
Lesson 1 of 15 · Docker and Cloud Essentials for AI DevelopersEnvironment consistency

What you will be able to do

Understand why the same project can work on one computer and fail on another, even with a lock file. Then learn the one idea that solves it: put the whole environment in a package, an image, and run that package everywhere.

Before you startWhat to know and to have readyAbout 35 minutes

What you need to know

What you need on your computer

  • The ticket API at the end of the API course, in ~/projects/ticket-api (Windows: $HOME\projects\ticket-api). It has requirements-lock.txt and ticket_api/config.py. If the project is missing or broken, copy the API course’s snapshot end-of-m07, as in Use a snapshot.
  • You do not need Docker for this lesson. You install it in Install and verify: Install Docker. Here, you read Amira’s commands and their real outputs, and the guided practice uses only your terminal.

Tested with Docker Desktop 4.81.0 (Docker Engine 29.6.1) on macOS, Apple silicon. The Windows and Linux steps are not tested.

Amira owns ticket-api, the support-ticket API from the API course. Her colleague Sam wants to run it too. Sam has Python 3.12 on a Windows laptop. Amira has Python 3.14.6 on a Mac with Apple silicon. Sam clones the project and makes a virtual environment. Then Sam installs the exact versions from the lock file, as the README says:

Sam's terminal (Windows PowerShell)
python -m pip install -r requirements-lock.txt

The install stops at one package, uvloop. On Windows, pip finds no ready-made package for it, so it tries to build it from its source code. The build stops with uvloop’s own message:

The cause, from uvloop's setup.py (version 0.23.0)
RuntimeError: uvloop does not support Windows at the moment

This Windows step is not tested here: the line above is the check in uvloop’s source code that stops the build. Amira can show the cause on her own computer. She asks pip which files exist for Windows and Python 3.12. The --platform and --python-version options make pip look for another computer’s files, without installing anything. She runs pip in a container, which you meet later in this lesson:

Amira's terminal
docker run --rm python:3.14.8-slim-trixie pip download --disable-pip-version-check --no-deps --only-binary=:all: --platform win_amd64 --python-version 3.12 --dest /tmp/w uvloop==0.23.0

The output:

ERROR: Could not find a version that satisfies the requirement uvloop==0.23.0 (from versions: none)
ERROR: No matching distribution found for uvloop==0.23.0

The same question for two other packages in the lock file, httptools==0.8.0 and watchfiles==1.3.0, gives a Windows file for each (httptools-0.8.0-cp312-cp312-win_amd64.whl, watchfiles-1.3.0-cp310-abi3-win_amd64.whl). Only uvloop has none.

Why is uvloop in the lock file at all? fastapi[standard] installs Uvicorn, the server. Uvicorn asks for uvloop only on systems that are not Windows: uvloop makes the server faster on Linux and macOS. Amira made the lock file with pip freeze on her Mac. pip freeze writes what is installed on this computer, so the Mac’s list has uvloop:

Amira's terminal
grep uvloop requirements-lock.txt
Output
uvloop==0.23.0

The lock file is correct for Amira’s Mac. It is not correct for Sam’s laptop. Nobody made a mistake: the two computers are different environments.

Without a container: Amira's Mac has the ticket_api code, packages from the lock file, Python 3.14.6 and macOS on Apple silicon, and the API starts. Sam's laptop has the same code, but uvloop cannot install, Python 3.12 and Windows 11 are different, and the install stops. With a container: one read-only image holds the ticket_api code, the packages with exact pins, Python 3.14.8 and Debian 13 Linux files. docker run starts it on Amira's Mac and on Sam's laptop, and it runs on both.
Without a container, each computer has its own environment. With a container, both computers run the same image.
Text description of the diagram

The left part shows two computers without a container. Amira’s Mac has four parts: the ticket_api code, the packages from the lock file, Python 3.14.6, and macOS on Apple silicon. The API starts. Sam’s laptop has the same code, but three parts are different: uvloop cannot install, Python is 3.12, and the system is Windows 11. The install stops. The right part shows one image with the code, exact package versions, Python 3.14.8 and the files of Debian 13, a Linux system. docker run starts the same image on both computers, and it runs on both.

The environment of a program is everything around its code that changes how it runs. For the ticket API, it has five parts:

Part Example for the ticket API What the Python and API courses did about it
The operating system and its libraries macOS, Windows, or a Linux system such as Debian or Ubuntu Nothing
The interpreter Python 3.14.6 or Python 3.12 requires-python = ">=3.12" in pyproject.toml: a range, not one version
The packages FastAPI 0.142.2, Uvicorn, uvloop The virtual environment isolates them. The lock file fixes their versions.
The settings API_KEY, SHOW_DOCS, CLASSIFIER_MODE Environment variables, read in config.py
The files The ticket_api code. Later, a model file. Git

The lock file fixed only one part, the packages, and only for one kind of computer. The operating system and the Python version were still different on every computer. A server adds a third environment, usually Linux, which neither Amira nor Sam has.

The problem gets bigger with AI services. Packages for machine learning often have a different build for each operating system and each kind of processor. A later course, Deploying AI Models for Real Users, puts a trained model in the same kind of package. A model file is one more thing that every copy of the environment must have, in exactly the right version.

The solution is to package the environment, not only describe it.

  • An image is a package that holds a program and everything that it needs to run. That is the files of a small Linux system, the interpreter, the packages and the code. Nobody can change an image after Docker makes it. It also holds a little information about how to start the program, such as its start command.
  • A container is a running copy of an image. Docker starts the image’s program as a process, and gives that process its own view of the files, the network and the other processes. You can start many containers from one image.
  • Docker is the tool that builds images and runs containers. You type docker commands in the terminal. They go to the Docker Engine, a program that does the work.
  • A registry is a server that stores images, so that other people and servers can download them. Docker Hub is the default public registry. You use images from Docker Hub in this module, and push your own image to a registry in Module 5.

The settings are the one part that stays out of the image. The same image runs on Amira’s Mac with a test key, and on a server with the real key. Module 3 gives a container its settings when it starts.

Source files (Dockerfile, ticket_api, requirements-run.txt) go through docker build, in Module 2, to an image. A registry, Docker Hub, with images that others built, goes through docker pull to an image. The image has read-only layers and a start command. docker run starts container 1 and container 2 from the image. Each container is a running process with its own writable layer.
An image comes from your source files (docker build) or from a registry (docker pull). Each docker run starts a new container from it.
Text description of the diagram

An image can come from two places. You build it from source files with docker build: a Dockerfile, the ticket_api code and the list of packages. You do that in Module 2. Or you download it from a registry such as Docker Hub with docker pull: an image that someone else built. The image holds read-only layers and a start command. Each docker run starts a new container from the image. A container is a running process with its own writable layer, so container 1 and container 2 do not share their changes.

Worked example: the same image on every computer

Section titled “Worked example: the same image on every computer”

Amira compares her Mac with a container. First, her Mac:

Amira's terminal
python3 --version
uname -sm

uname -sm prints the name of the operating system and the kind of processor. The output:

Python 3.14.6
Darwin arm64

Darwin is the core of macOS. arm64 is the processor of Apple silicon. Then she runs the same questions in a container from the official Python image:

Amira's terminal
docker run --rm python:3.14.8-slim-trixie sh -c 'python --version; uname -sm; grep PRETTY_NAME /etc/os-release'

The output:

Python 3.14.8
Linux aarch64
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"

Inside the container, the system is Linux (Debian 13, called “trixie”), with Python 3.14.8, on the same Mac. aarch64 is Linux’s name for arm64. On Sam’s laptop, the same command prints the same Python version and the same Debian version. Only the processor name can be different: an Intel or AMD laptop prints x86_64. Install and verify explains this difference.

The name python:3.14.8-slim-trixie is an image name and a tag. python is the image. The tag after the colon chooses one version of it: Python 3.14.8, the small (“slim”) variant, on Debian trixie.

The API course listed the five things that every hosting platform does, and said that Docker is a different way to describe them. Here is the map. You learn each part in this course:

Platform step With Docker Where in this course
Build: install Python and the dependencies A Dockerfile, and docker build, make an image Module 2
Start: run the start command The image’s CMD Module 2
Listen: send requests to the app on a port EXPOSE in the image, and -p when the container starts Module 3
Configure: give the app its settings -e, an environment file or a secret file, when the container starts Module 3
Keep it running: HTTPS, restarts, logs docker logs on your computer; a container platform in the cloud Modules 3 and 5

With a container, you do the build step yourself, once, on your computer. The platform then only runs the image. The result is the same on your computer, on Sam’s computer and on the server.

Guided practice: list your own environment

Section titled “Guided practice: list your own environment”

This practice needs no Docker. You write down the five parts of the environment on your computer, as Amira did.

  1. Open a terminal in the project folder ~/projects/ticket-api.

  2. Show the Python version.

    Terminal window
    python3 --version

    Expected: one line, such as Python 3.14.6.

  3. Show the operating system and the processor.

    Terminal window
    uname -sm

    Expected: for example Darwin arm64 on a Mac with Apple silicon, Linux x86_64 on most Linux computers, or a Windows name and AMD64 on Windows.

  4. Look for uvloop in the lock file.

    Terminal window
    grep uvloop requirements-lock.txt

    Expected: uvloop==0.23.0 if the file was made on macOS or Linux. No line if it was made on Windows.

  5. Open ticket_api/config.py, and write down every setting that it reads with os.environ.get.

  6. Write the five parts in a short table: operating system, interpreter, packages, settings, files.

Show an example answer (Amira’s Mac)
Part Amira’s Mac
Operating system macOS (Darwin), Apple silicon (arm64)
Interpreter Python 3.14.6
Packages The 63 lines of requirements-lock.txt, with uvloop==0.23.0
Settings ALLOWED_ORIGINS, API_KEY, CLASSIFIER_MODE, CLASSIFIER_TIMEOUT, LOG_LEVEL, SHOW_DOCS
Files The ticket_api folder, pyproject.toml

Only the packages are fixed by a file in the project. The operating system and the Python version are whatever this computer has.

A new colleague has a Linux laptop with an Intel processor and Python 3.13. Use your table from the guided practice. Which parts of the environment are different from Amira’s Mac? Can the lock file fix any of them? Does uvloop install? (uvloop 0.23.0 has ready-made Linux packages for Python 3.13 on Intel and AMD processors.)

Show the answer

Three parts are different: the operating system (Linux, not macOS), the processor (x86_64, not arm64), and the interpreter (3.13, not 3.14.6). The lock file fixes none of them: it only fixes the versions of the packages. uvloop installs, because there is a Linux package for Python 3.13. So the install probably works, but it is still a third environment. A bug that depends on the Python version or the system can still appear only on this laptop. An image removes the question: all three people run Python 3.14.8 on Debian 13.

Common mistake

Edit the lock file until it installs

Symptom: Sam deletes the line uvloop==0.23.0 from requirements-lock.txt. The install works, the tests pass, and Sam commits the changed file. Weeks later, the server runs without uvloop, and nobody decided that.

Cause: the lock file now describes Sam’s laptop, not the server. Uvicorn uses uvloop when it is installed, and the slower standard event loop when it is not. So the server’s environment changed because of one person’s laptop. A lock file made on one operating system is a list for that operating system.

Fix:

  1. Do not edit a lock file by hand to make it install. Put the line back with git restore requirements-lock.txt.
  2. Make the list of packages on the same kind of system that runs the service. In Module 2, you make requirements-run.txt inside a Linux container, so it is the list for the Linux image.
  3. On your own computer, use the image to run the service. Use your virtual environment only to write code and run the tests.

For a real AI service: packages such as PyTorch have a different build for each operating system, processor and graphics card. A list that one person makes on a laptop is often wrong for the server. The image is the place where the server’s list is true.

Check your understanding

Amira's lock file pins every package version. Which parts of the environment does it not fix?

Show the answer

It does not fix the operating system and its libraries, or the Python version: those are whatever the computer has. It does not fix the settings either, and it should not. The settings change between computers on purpose: a test key on a laptop, the real key on the server. It also fixes the packages only for the kind of computer that made it: the Mac’s list has uvloop, which Windows cannot install.

Check your understanding

Predict: Sam installs Docker on the Windows laptop and runs docker run --rm python:3.14.8-slim-trixie python --version. What does it print, and why is it not Python 3.12?

Show the answer

It prints Python 3.14.8. The command runs Python from the image, not the Python that is installed on Sam’s laptop. The image holds its own Linux system files and its own Python 3.14.8. So every computer that runs this image gets the same version.

Check your understanding

Which part of the environment stays out of the image, and why?

Show the answer

The settings, such as API_KEY and SHOW_DOCS. The same image must run on a laptop with a test key, and on a server with the real key and no documentation. If a setting is in the image, you need a different image for each place. And everyone who has the image can read a secret in it. Module 3 gives the settings to the container when it starts.

Recap

  • An environment has five parts: the operating system and its libraries, the interpreter, the packages, the settings and the files.
  • A lock file fixes only the package versions, and only for the kind of computer where it was made. Amira’s Mac list has uvloop, which Windows cannot install.
  • An image holds every part except the settings. A container is a running copy of an image. Docker builds images and runs containers. A registry stores images.
  • Docker describes the five platform steps from the API course: build, start, listen, configure, keep running.

Next, you look inside an image and run your first containers, and you see which changes a container keeps and which it loses.

Next: Images and running containers