Skip to content

Images and running containers

  • Lesson 2
  • 45 min
  • Module 1: Understand containers
Lesson 2 of 15 · Docker and Cloud Essentials for AI DevelopersImages and running containers

What you will be able to do

Know what is in an image, what a running container adds to it, and what is lost when the container is removed. This is the base for every later module: the API’s code goes in the image, and data that must survive goes somewhere else.

Before you startWhat to know and to have readyAbout 45 minutes

What you need to know

What you need on your computer

  • Docker, to run the commands yourself. If you do not have it yet, do Install and verify: Install Docker and Check the installation first, then come back. Or read this lesson first: every command has its real output. The module practice at the end of the next lesson asks you to run the same steps.
  • An internet connection. The first command downloads the Python image (46 MB).
  • No project files. This lesson does not change ticket-api.

Tested with Docker Desktop 4.81.0 (Docker Engine 29.6.1) on macOS, Apple silicon. The Windows and Linux steps are not tested. The docker commands are the same on every system.

Amira downloads the official Python image from Docker Hub. To download an image is to pull it:

Terminal
docker pull python:3.14.8-slim-trixie

The output (on a computer that does not have the image yet):

3.14.8-slim-trixie: Pulling from library/python
2037d17aae31: Pulling fs layer
11de3117eae8: Pulling fs layer
bbeda6b4abb7: Pulling fs layer
9822e19f729e: Pulling fs layer
...
9822e19f729e: Pull complete
Digest: sha256:f85c5697265c178cc6887276c55fe16cf3d14ca35c3df6a5eab3b360534a55d2
Status: Downloaded newer image for python:3.14.8-slim-trixie
docker.io/library/python:3.14.8-slim-trixie

Each line with a short ID is one layer: a set of files that the image adds on top of the layers below it. Docker downloads the layers separately, at the same time. The digest is the exact fingerprint of the image. Module 2 uses it to pin a version. library/python means a Docker Official Image: one of a set of images on Docker Hub that Docker reviews, and that get security updates quickly. Module 2 says more about how to choose an image.

Now the image is on the computer:

Terminal
docker image ls python
Output
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
python:3.14.8-slim-trixie f85c5697265c 206MB 46MB

The image has two sizes. CONTENT SIZE (46 MB) is the compressed layers: what a pull or a push moves over the network. DISK USAGE (206 MB) is what the image uses on this computer, with its layers unpacked. Your numbers can be a little different if your computer already has other images that share layers.

Amira starts a container that runs one Python command:

Terminal
docker run --name c18m01-first python:3.14.8-slim-trixie python -c "print('Hello from a container')"
Part Meaning
docker run Make a new container from an image, and start it.
--name c18m01-first A name for the container. Without it, Docker chooses a random name. In this course, names start with the module (c18m01-), so you can find and remove your containers later.
python:3.14.8-slim-trixie The image.
python -c "..." The command to run in the container, instead of the image’s own start command.

The output:

Hello from a container

The command ended, so the container stopped. It still exists. docker ps shows only the containers that run now; docker ps -a (“all”) shows the stopped ones too. --filter name=c18m01- shows only the containers whose names have that text:

Terminal
docker ps --filter name=c18m01-
docker ps -a --filter name=c18m01-
Output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
87a66f714f56 python:3.14.8-slim-trixie "python -c 'print('H…" Less than a second ago Exited (0) Less than a second ago c18m01-first

The first list is empty. The second has the container, with Exited (0): the program ended with exit code 0, which means success. Docker keeps what the program printed. docker logs shows it again, and docker rm removes the stopped container:

Terminal
docker logs c18m01-first
docker rm c18m01-first
Output
Hello from a container
c18m01-first

A container lives exactly as long as its main program. This is the most important fact about containers: a container is a process, with Docker around it.

The same image can answer other questions. For example, which Python and which processor:

Terminal
docker run --rm python:3.14.8-slim-trixie python -c "import sys, platform; print(sys.version, platform.machine())"
Output
3.14.8 (main, Oct 6 2026, 02:02:59) [GCC 14.2.0] aarch64

--rm removes the container when its program ends, so you do not need docker rm. On an Intel or AMD computer, the last word is x86_64.

docker history lists the layers of an image, the newest at the top, with the instruction that made each one:

Terminal
docker history python:3.14.8-slim-trixie
Output
IMAGE CREATED CREATED BY SIZE COMMENT
f85c5697265c 39 hours ago CMD ["python3"] 0B buildkit.dockerfile.v0
<missing> 39 hours ago RUN /bin/sh -c set -eux; for src in idle3 p… 16.4kB buildkit.dockerfile.v0
<missing> 39 hours ago RUN /bin/sh -c set -eux; savedAptMark="$(a… 45.3MB buildkit.dockerfile.v0
<missing> 39 hours ago ENV PYTHON_SHA256=c2215904f02b175596dc493515… 0B buildkit.dockerfile.v0
<missing> 39 hours ago ENV PYTHON_VERSION=3.14.8 0B buildkit.dockerfile.v0
<missing> 39 hours ago RUN /bin/sh -c set -eux; apt-get update; a… 4.99MB buildkit.dockerfile.v0
<missing> 39 hours ago ENV PATH=/usr/local/bin:/usr/local/sbin:/usr… 0B buildkit.dockerfile.v0
<missing> 2 days ago # debian.sh --arch 'arm64' out/ 'trixie' '@1… 110MB debuerreotype 0.17

Read it from the bottom:

  1. The files of Debian 13, a small Linux system: 110 MB.
  2. A few system packages, installed with apt-get: 4.99 MB.
  3. Python 3.14.8, built from its source code: 45.3 MB.
  4. Short names for the Python commands, such as python for python3: 16.4 kB.
  5. ENV and CMD lines: 0 B. They add no files. They are metadata: information about how to run the image. ENV sets environment variables, and CMD is the start command.

<missing> is normal. The layers were built on another computer, so only the top one has an ID here.

docker image inspect shows the metadata. --format chooses which values to print:

Terminal
docker image inspect python:3.14.8-slim-trixie --format 'Cmd={{.Config.Cmd}} User={{.Config.User}} Arch={{.Architecture}}'
Output
Cmd=[python3] User= Arch=arm64

The start command is python3. User= is empty, which means that the program runs as root, the administrator of the container’s system. Module 2 changes that for the ticket API. arm64 is the processor that this copy of the image is for.

Amira starts a container that keeps running. sleep infinity waits forever, and -d (“detached”) starts the container in the background, so the terminal is free:

Terminal
docker run -d --name c18m01-box python:3.14.8-slim-trixie sleep infinity
docker ps --filter name=c18m01-
Output
aa62c8ec9cb50a09e1259d59b20422651168cecb7363ef53177e4a0b46e2a58f
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aa62c8ec9cb5 python:3.14.8-slim-trixie "sleep infinity" Less than a second ago Up Less than a second c18m01-box

The long line is the full container ID. docker exec runs a second command in a running container. She writes a file in it, and reads it back:

Terminal
docker exec c18m01-box sh -c 'echo "written inside" > /tmp/note.txt'
docker exec c18m01-box cat /tmp/note.txt
Output
written inside

docker diff lists what changed in the container, compared with its image. A means added, C changed, D deleted:

Terminal
docker diff c18m01-box
Output
C /tmp
A /tmp/note.txt

docker top shows the processes in the container. There is one, sleep infinity:

Terminal
docker top c18m01-box
Output
UID PID PPID C STIME TTY TIME CMD
root 8886 8863 0 16:54 ? 00:00:00 sleep infinity

Predict: Amira stops the container and starts it again. Is the file still there? Then she removes the container and starts a new one from the same image. Is the file there?

Terminal
docker stop c18m01-box
docker ps -a --filter name=c18m01-
docker start c18m01-box
docker exec c18m01-box cat /tmp/note.txt
Output
c18m01-box
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aa62c8ec9cb5 python:3.14.8-slim-trixie "sleep infinity" 10 seconds ago Exited (137) Less than a second ago c18m01-box
c18m01-box
written inside

After stop and start, the file is still there. (docker stop took 10 seconds here. It asks the program to stop, and waits up to 10 seconds. sleep does not react to that request in a container, so Docker forced it to stop: exit code 137. Module 2 shows how the API stops at once.)

Now she removes the container, with -f (“force”) because it is running, and starts a new one:

Terminal
docker rm -f c18m01-box
docker run -d --name c18m01-box python:3.14.8-slim-trixie sleep infinity
docker exec c18m01-box cat /tmp/note.txt
Output
c18m01-box
908dbd393fa639f56aa3f90f0a482fefb9b4c2cf40b7dec4ed5f8818fe7ff437
cat: /tmp/note.txt: No such file or directory

The file is gone. The new container has the same name and the same image, but it is a different container.

Here is the rule behind the result. The layers of an image are read-only: no container can change them. When Docker starts a container, it adds one thin, empty writable layer on top of the image’s layers. Every file that the container writes, changes or deletes goes into its writable layer. The container sees the layers together, as one file system.

  • docker stop stops the process. The writable layer stays.
  • docker rm deletes the container, and its writable layer with it.
  • A new container gets a new, empty writable layer.

docker ps --size shows how small the writable layer is. With the note in it:

Terminal
docker ps --size --filter name=c18m01-box
Output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE
21f0351f8228 python:3.14.8-slim-trixie "sleep infinity" 1 second ago Up Less than a second c18m01-box 12.3kB (virtual 160MB)

12.3 kB is the writable layer. “virtual 160MB” is the writable layer plus the image’s unpacked layers, which the container uses but does not copy.

At the top, two writable layers, one per container: c18m01-a has /tmp/note.txt, c18m01-b has nothing written. They are deleted with the container by docker rm. Below them, the image layers of python:3.14.8-slim-trixie, read-only and shared: metadata (CMD python3, ENV) 0 B, Python links 16.4 kB, Python 3.14.8 45.3 MB, apt packages 4.99 MB, Debian 13 files 110 MB.
Every container from an image shares the same read-only layers, and has its own writable layer on top. docker rm deletes the writable layer.
Text description of the diagram

The diagram has two parts. At the bottom are the image layers of python:3.14.8-slim-trixie, from docker history. They are read-only and shared by every container. From the bottom up: Debian 13 (110 MB), apt packages (4.99 MB), Python 3.14.8 (45.3 MB), Python links (16.4 kB). At the top of the stack is the metadata, CMD python3 and the ENV lines, which add 0 B. (In docker history, the ENV lines are between the other layers.) Above the image are two writable layers, one for each container. Container c18m01-a wrote /tmp/note.txt. Container c18m01-b wrote nothing. docker rm deletes a container’s writable layer.

Amira starts two containers from the same image:

Terminal
docker run -d --name c18m01-a python:3.14.8-slim-trixie sleep infinity
docker run -d --name c18m01-b python:3.14.8-slim-trixie sleep infinity
docker ps --filter name=c18m01-
Output
f507c4b684d5984760d67bdd2c9d4856c15ca19481d9d84af8b9a326959182da
bc257ecef1f618a63a53fb3bdc855cae09f8ea92c8d60334bb7204f3af017cd4
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bc257ecef1f6 python:3.14.8-slim-trixie "sleep infinity" 1 second ago Up Less than a second c18m01-b
f507c4b684d5 python:3.14.8-slim-trixie "sleep infinity" 1 second ago Up Less than a second c18m01-a

She writes a note in c18m01-a only, and looks for it in c18m01-b:

Terminal
docker exec c18m01-a sh -c 'echo only-in-a > /tmp/note.txt'
docker exec c18m01-b cat /tmp/note.txt
Output
cat: /tmp/note.txt: No such file or directory

Each container has its own writable layer, so it has its own view of the files. Each one also has its own name on the network, and its own list of processes:

Terminal
docker exec c18m01-a hostname
docker exec c18m01-b hostname
docker exec c18m01-a python -c "import os; print(sorted(int(p) for p in os.listdir('/proc') if p.isdigit()))"
Output
f507c4b684d5
bc257ecef1f6
[1, 25]

The host name of a container is its short ID. Inside c18m01-a, only two processes exist: number 1 is sleep infinity, and number 25 is the Python command that asks the question. The container cannot see the processes of the other container, or of the computer.

One thing the containers share: the kernel, the core of the operating system that runs every process and talks to the hardware.

Terminal
docker exec c18m01-a uname -r
docker exec c18m01-b uname -r
uname -r
Output
6.12.76-linuxkit
6.12.76-linuxkit
25.6.0

Both containers report the same Linux kernel, 6.12.76-linuxkit. The last line is the Mac’s own kernel, version 25.6.0, which is not Linux. Containers need a Linux kernel. On macOS and Windows, Docker Desktop runs one small Linux virtual machine, and every container uses its kernel. On a Linux computer, containers use the computer’s own kernel.

Amira removes the two containers:

Terminal
docker rm -f c18m01-a c18m01-b

A virtual machine (VM) is a complete computer made in software: it has its own operating system, with its own kernel. A program called a hypervisor shares the real hardware between the VMs. A container has no kernel of its own. It is a process on the host’s kernel, which Docker separates from the other processes.

Two stacks. Containers: three containers, each with app, Python and libraries, on Docker Engine, on one Linux kernel that all containers share, on the hardware. Virtual machines: two VMs, each with app, Python and libraries and its own kernel, on a hypervisor, on the host operating system and kernel, on the hardware.
Containers share one kernel. Each virtual machine has its own kernel.
Text description of the diagram

The left stack shows containers. Three containers, each with an app, Python and libraries, run on the Docker Engine. Below it is one Linux kernel, which all the containers share. At the bottom is the hardware: CPU, memory and disk. The right stack shows virtual machines. Two VMs each have an app, Python and libraries, and their own kernel. They run on a hypervisor, on the host’s operating system and kernel, on the same kind of hardware.

Container Virtual machine
Has its own kernel No: it shares the host’s Linux kernel Yes
Starts In about a second: it starts one process (docker run --rm python:3.14.8-slim-trixie true took 0.4 to 1.0 s here) When its whole operating system has started
Size Only the program’s files: 206 MB for this Python image A whole operating system
Separation Good for your own programs. All containers depend on one kernel. Stronger: a problem in one kernel stays in one VM

Cloud platforms often use both. In Module 5, Azure runs your container, and Azure decides where the kernel and the VMs are. That is part of what you pay for.

  1. Start a container that keeps running.

    Terminal window
    docker run -d --name c18m01-practice python:3.14.8-slim-trixie sleep infinity

    Expected: a long container ID.

  2. Write a file in it.

    Terminal window
    docker exec c18m01-practice sh -c 'echo "my note" > /tmp/note.txt'
  3. Check the change with docker diff c18m01-practice. Expected: C /tmp and A /tmp/note.txt.

  4. Check the size of the writable layer with docker ps --size --filter name=c18m01-practice. Expected: a few kB, and “virtual 160MB”.

  5. Remove the container with docker rm -f c18m01-practice.

  6. Start a new container with the command from step 1.

  7. Look for the file with docker exec c18m01-practice cat /tmp/note.txt. Expected: cat: /tmp/note.txt: No such file or directory.

  8. Remove the container with docker rm -f c18m01-practice, and check with docker ps -a --filter name=c18m01- that the list is empty.

Show why step 7 fails

The note was in the writable layer of the first container. docker rm -f in step 5 deleted that container and its writable layer. The container in step 6 has the same name, but it is new. It has a new, empty writable layer on the same read-only image. The image never had the note.

Start two containers, c18m01-a and c18m01-b, as in Many containers from one image. In c18m01-a, delete the pip3 command: docker exec c18m01-a rm /usr/local/bin/pip3. Predict: does pip3 --version still work in c18m01-a? In c18m01-b? What does docker diff c18m01-a show? Then run the commands and check. Remove both containers at the end.

Show the answer
docker exec c18m01-a pip3 --version
OCI runtime exec failed: exec failed: unable to start container process: exec: "pip3": executable file not found in $PATH
docker exec c18m01-b pip3 --version
pip 26.2.1 from /usr/local/lib/python3.14/site-packages/pip (python 3.14)
docker diff c18m01-a
C /usr
C /usr/local
C /usr/local/bin
D /usr/local/bin/pip3

pip3 is gone in c18m01-a only. The file is still in the image’s read-only layer. The writable layer of c18m01-a only records “deleted” (D), and hides the file from that one container. c18m01-b and every new container still have pip3. Remove both with docker rm -f c18m01-a c18m01-b.

Common mistake

The container stops at once

Symptom: Amira starts the Python image in the background, without a command. docker run prints an ID, but docker ps shows nothing:

Terminal
docker run -d --name c18m01-py python:3.14.8-slim-trixie
docker ps --filter name=c18m01-py
docker ps -a --filter name=c18m01-py
Output
1e8a3e2b493bb6a733ce77279cfa60aba1caaa3726f5c74a8df530ac91619bfa
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1e8a3e2b493b python:3.14.8-slim-trixie "python3" 3 seconds ago Exited (0) 2 seconds ago c18m01-py

docker logs c18m01-py prints nothing.

Cause: the image’s start command is python3, the interactive Python prompt. In the background, no terminal is connected, so Python reads the end of its input at once and ends, with exit code 0. A container lives only as long as its main process.

Fix:

  1. Look at the status in docker ps -a, and at the command in the COMMAND column. Exited (0) means that the program ended normally. It did not crash.
  2. Give the container a command that keeps running, such as sleep infinity for a test, or a server.
  3. For an interactive Python prompt, connect your terminal instead: docker run -it --rm python:3.14.8-slim-trixie (-i keeps the input open, -t gives it a terminal). Type exit() to leave.
  4. Remove the stopped container: docker rm c18m01-py.

For a real AI service: the service’s start command must start a server that keeps running, such as fastapi run. If the server stops because of an error, the container stops too. docker ps -a and docker logs are the first two places to look.

Check your understanding

Amira writes a file in a running container, then runs docker stop and docker start. Then she runs docker rm and docker run again. When is the file still there, and why?

Show the answer

After docker stop and docker start, the file is still there: stopping ends the process, but the container and its writable layer stay. After docker rm and a new docker run, the file is gone. docker rm deletes the container and its writable layer. The new container starts with a new, empty writable layer on the read-only image.

Check your understanding

Why does docker history show 0B for the ENV and CMD lines of the Python image?

Show the answer

ENV and CMD add no files. They are metadata: ENV sets environment variables for the processes in the container, and CMD is the start command. Docker keeps them in the image’s configuration, which docker image inspect shows, not in a layer of files.

Check your understanding

Two containers run on Amira's Mac. Which of these do they share: the files that they write, their process list, the kernel?

Show the answer

Only the kernel. Each container has its own writable layer, so a file that one writes is not in the other. Each has its own process list: inside c18m01-a, the main process is number 1, and the other container’s processes are not visible. Both use the same Linux kernel, 6.12.76-linuxkit: on a Mac, that is the kernel of Docker Desktop’s small Linux virtual machine. A virtual machine, in contrast, has its own kernel.

Recap

  • An image is a stack of read-only layers, plus metadata: the start command (CMD), environment variables (ENV), the user. docker history and docker image inspect show them.
  • A container is a process, started from an image, with its own writable layer, files view, network name and process list. It shares the host’s Linux kernel. A virtual machine has its own kernel.
  • docker stop keeps the writable layer. docker rm deletes it. A new container starts empty.
  • The commands: docker pull, run (-d, --name, --rm), ps and ps -a, logs, exec, diff, top, stop, start, rm, image ls, history.

Next, you install Docker (if you have not yet), check it with a first container, and run a web server in a container.

Next: Install and verify