Skip to content

Install and verify

  • Lesson 3
  • 50 min
  • Module 1: Understand containers
Lesson 3 of 15 · Docker and Cloud Essentials for AI DevelopersInstall and verify

What you will be able to do

Get a Docker installation that you know works before you build anything with it. You install Docker, prove it with a known first container, and run a web server in a container. Then you remove everything again, so you also know how to get back to a clean state.

Before you startWhat to know and to have readyAbout 50 minutes

What you need to know

What you need on your computer

  • A computer where you can install programs: administrator rights on macOS, a Windows account that can approve an installation, or sudo on Linux.
  • At least 4 GB of memory on a Mac, or 8 GB on Windows (Docker’s own minimums). About 2 GB of free disk space for Docker and the images in this module.
  • An internet connection. The images in this module come from Docker Hub.
  • A web browser, and curl (Windows: curl.exe in PowerShell, as in the API course).

Tested with Docker Desktop 4.81.0 (Docker Engine 29.6.1) on macOS, Apple silicon. The Windows and Linux steps are not tested: they follow Docker’s installation pages.

Docker has two forms:

  • Docker Engine is the program that runs containers, with the docker command. It runs on Linux. It is free and open source.
  • Docker Desktop is an app for macOS, Windows and Linux. It contains Docker Engine, inside a small Linux virtual machine, with a window to manage it. On macOS and Windows, you need it, because containers need a Linux kernel.

This lesson installs Docker Desktop on macOS and Windows, and Docker Engine on Linux (Ubuntu).

The licence of Docker Desktop. Docker Desktop is free for personal use, education, non-commercial open-source projects, and small businesses. A small business has fewer than 250 employees and less than US$10 million in annual revenue. Larger companies need a paid subscription. If you use a work computer, ask your company which one applies. Docker Engine on Linux is free for everyone. (Source: Docker Desktop license agreement.)

Other tools can also run the same images, for example Podman Desktop, Rancher Desktop and Colima. This course tests only Docker.

An image is built for one CPU architecture: the kind of processor that can run its programs. Two are common:

Architecture Linux name Computers
linux/arm64 aarch64 Macs with Apple silicon, many new Windows laptops, some cloud servers
linux/amd64 x86_64 Most Windows and Linux PCs (Intel and AMD processors), most cloud servers

Find yours:

Terminal window
uname -m

arm64 means Apple silicon. x86_64 means Intel.

On Amira’s Mac, uname -m prints arm64.

Most official images are multi-platform: one name and tag, with one variant for each architecture. Docker downloads the variant for your computer. This command lists the variants (shortened):

Terminal
docker buildx imagetools inspect python:3.14.8-slim-trixie
Output (shortened)
Name: docker.io/library/python:3.14.8-slim-trixie
MediaType: application/vnd.oci.image.index.v1+json
Digest: sha256:f85c5697265c178cc6887276c55fe16cf3d14ca35c3df6a5eab3b360534a55d2
...
Platform: linux/amd64
...
Platform: linux/arm/v5
Platform: linux/arm/v7
Platform: linux/arm64/v8
Platform: linux/386
Platform: linux/ppc64le
Platform: linux/riscv64
Platform: linux/s390x

(The output also has entries with Platform: unknown/unknown. They hold information about how the image was built, not programs.)

Docker Desktop can also run the other architecture, slowly, with emulation: software that imitates the other processor. --platform chooses the variant:

Terminal
docker run --rm --platform linux/amd64 python:3.14.8-slim-trixie python -c "import platform; print(platform.machine())"
Output
x86_64

The architecture matters again in Module 5. Azure Container Apps runs only linux/amd64 images, so on an Apple silicon Mac you build the API’s image for linux/amd64.

Choose your system. Follow only one tab.

Docker Desktop supports the current version of macOS and the two versions before it.

  1. Find your processor with uname -m, as in the section before.
  2. Open Install Docker Desktop on Mac, and download the installer for your processor: Apple silicon or Intel chip.
  3. Open the downloaded Docker.dmg file.
  4. Drag the Docker icon to the Applications folder.
  5. Open Docker from the Applications folder.
  6. Read the Docker Subscription Service Agreement, and accept it if the licence terms in Docker Desktop or Docker Engine allow your use.
  7. Wait until Docker Desktop says that the engine is running. You do not need to sign in to Docker Hub for this course. If Docker Desktop asks, skip the sign-in.

Docker Desktop must run when you use the docker command. After a restart of the Mac, open it again, or turn on its setting to start at sign-in.

Run three checks, in this order. Each one tests one more part.

1. The docker command can reach the engine.

Terminal
docker version

The output (shortened):

Client:
Version: 29.6.1
API version: 1.55
OS/Arch: darwin/arm64
Context: desktop-linux
Server: Docker Desktop 4.81.0 (232925)
Engine:
Version: 29.6.1
API version: 1.55 (minimum version 1.40)
OS/Arch: linux/arm64
...

There are two parts. The Client is the docker command on your computer (darwin/arm64: macOS on Apple silicon). The Server is the engine. Its OS/Arch is linux/arm64, because it runs in Docker Desktop’s Linux virtual machine. If the Server part is missing, see the Aside after the third check.

2. The engine runs the system and the architecture that you expect.

Terminal
docker info --format '{{.OperatingSystem}} {{.Architecture}}'
Output
Docker Desktop aarch64

On Windows, the first words are also Docker Desktop. On Linux, you see the name of your Linux system instead.

3. The engine can pull an image from a registry and run a container.

Terminal
docker run hello-world

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

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
58dee6a49ef1: Pulling fs layer
58dee6a49ef1: Download complete
c3bdf82c34d1: Download complete
58dee6a49ef1: Pull complete
Digest: sha256:5e23090353324d887c48ad5e5c56d294eab81588df9605b07d1afe895f9cc8f8
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(arm64v8)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
...

The message itself lists what worked. The daemon is the engine: a program that runs in the background. hello-world:latest: you gave no tag, so Docker used the tag latest. (arm64v8) is the architecture of the variant that it pulled. On an Intel or AMD computer, it is amd64. The hello-world image is 22.6 kB, so this check is fast.

Worked example: a web server in a container

Section titled “Worked example: a web server in a container”

Now a container that you can reach from the browser. nginx is a web server. Amira runs the official nginx image, with a pinned version, 1.30.5, the stable version when the course was written:

Terminal
docker run -d --name c18m01-web -p 127.0.0.1:8080:80 nginx:1.30.5
Part Meaning
-d Run in the background.
--name c18m01-web The container’s name.
-p 127.0.0.1:8080:80 Publish a port. A port is a number that chooses which program on a computer gets a connection (as in the API course). Connections to port 8080 on this computer go to port 80 in the container, where nginx listens. 127.0.0.1 means that only this computer can connect. Module 3 explains every part.
nginx:1.30.5 The image and an exact version.

The output:

Unable to find image 'nginx:1.30.5' locally
1.30.5: Pulling from library/nginx
a36cd258528a: Pulling fs layer
...
Digest: sha256:9bf97bd7714f5e24c1ccd545ecb9eb5435cb6d109c97cebb15e7e455e0239edb
Status: Downloaded newer image for nginx:1.30.5
de15a5783a027b866ca221ac078b172bb11af74007f11fe8f6f459d2e4b06c3b

docker ps shows the published port in the PORTS column:

Terminal
docker ps --filter name=c18m01-web
Output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
de15a5783a02 nginx:1.30.5 "/docker-entrypoint.…" Less than a second ago Up Less than a second 127.0.0.1:8080->80/tcp c18m01-web

Open http://127.0.0.1:8080 in the browser. The page has the title Welcome to nginx! and a few lines of text. From the terminal, curl shows the start of the same page:

Terminal window
curl -s http://127.0.0.1:8080 | head -4
Output
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

nginx writes one log line for each request. docker logs shows them (the last lines):

Terminal
docker logs c18m01-web
Output (last lines)
...
2026/10/07 16:52:59 [notice] 1#1: start worker process 37
2026/10/07 16:52:59 [notice] 1#1: start worker process 38
172.17.0.1 - - [07/Oct/2026:16:52:59 +0000] "GET / HTTP/1.1" 200 896 "-" "curl/8.7.1" "-"

The request came from an address like 172.17.0.1, not from 127.0.0.1. Docker forwarded the connection to the container through its own small network, and that address is Docker’s side of it. Module 3 explains it.

Then Amira looks inside the container. She asks nginx for its version, and changes the start page in the container:

Terminal
docker exec c18m01-web nginx -v
docker exec c18m01-web sh -c 'echo "<h1>Changed in the container</h1>" > /usr/share/nginx/html/index.html'
curl -s http://127.0.0.1:8080
Output
nginx version: nginx/1.30.5
<h1>Changed in the container</h1>

docker diff c18m01-web lists the changed page, C /usr/share/nginx/html/index.html, together with files that nginx itself made when it started, such as A /run/nginx.pid. docker inspect shows the container’s state and configuration:

Terminal
docker inspect c18m01-web --format '{{.State.Status}} {{.Config.Image}} {{json .NetworkSettings.Ports}}'
Output
running nginx:1.30.5 {"80/tcp":[{"HostIp":"127.0.0.1","HostPort":"8080"}]}

Predict: Amira removes the container and starts a new one with the same command. Which page does the browser show?

Terminal
docker stop c18m01-web
docker rm c18m01-web
docker run -d --name c18m01-web -p 127.0.0.1:8080:80 nginx:1.30.5
curl -s http://127.0.0.1:8080 | grep title
Output
c18m01-web
c18m01-web
862f869879fcebb37848ce3029080ce6c73f35cfa4ef893db53f85426a2ba799
<title>Welcome to nginx!</title>

The original page is back. The changed page was in the writable layer of the old container. nginx stopped at once (0.3 s here), because nginx reacts to the stop request. At the end, she removes the container:

Terminal
docker rm -f c18m01-web

You can always return to the state before this module. Do these steps at the end of the module, or when something is confusing:

  1. List all your containers, also the stopped ones: docker ps -a. Look for the names that start with c18m01-, and for the hello-world container. A container that you started without --name has a random name, such as laughing_pare.
  2. Remove each container that you made, by name: docker rm -f c18m01-web. For the hello-world container, use its random name: docker rm laughing_pare.
  3. Remove the images that you do not need any more: docker image rm nginx:1.30.5 hello-world. Keep python:3.14.8-slim-trixie: Module 2 uses it.
  4. Check the space that Docker uses: docker system df.

On a test computer with only the Python image left, docker system df printed:

Output
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 1 0 205.8MB 205.8MB (99%)
Containers 0 0 0B 0B
Local Volumes 0 0 0B 0B
Build Cache 0 0 0B 0B

Your numbers are larger if you have other images. Remove only images and containers that you know. Do not use docker system prune: it deletes things from other projects too.

Common mistake

Port is already allocated

Symptom: Amira starts a second web container on the same port. docker run fails:

Terminal
docker run -d --name c18m01-web2 -p 127.0.0.1:8080:80 nginx:1.30.5
Output
ec0cb79f15b8a3b44612760dfb5ef515368fef837982ed3ff449e113a6e496b0
docker: Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint c18m01-web2 (28a55085448d341d9cc02dcf57bb2836ec659d84d0e1b8db9b43e377685a49f3): Bind for 127.0.0.1:8080 failed: port is already allocated
Run 'docker run --help' for more information

The exit code is 125: Docker itself failed, not the program in the container. Then docker ps -a shows the new container with the status Created: it exists, but it never started.

Cause: only one program can use a port on one address. c18m01-web already has 127.0.0.1:8080. The container port, 80, is not the problem: every container has its own port 80.

Fix:

  1. Find what uses the port: docker ps shows 127.0.0.1:8080->80/tcp in the PORTS column of c18m01-web. If no container uses it, another program on your computer does.
  2. Remove the container that was only created: docker rm c18m01-web2.
  3. Either stop the first container, or use another host port: docker run -d --name c18m01-web2 -p 127.0.0.1:8081:80 nginx:1.30.5.
  4. Remove both containers when you are done: docker rm -f c18m01-web c18m01-web2.

For a real AI service: two copies of a service on one computer need two host ports, but they can use the same container port. In the cloud, the platform chooses the host side for you (Module 5).

Check your understanding

docker version shows a Client part but no Server part. What does that tell you, and what do you do?

Show the answer

The docker command is installed, but it cannot reach the engine. The client and the engine are two programs, and only the client answered. On macOS and Windows, start Docker Desktop and wait until the engine runs. On Linux, start the service with sudo systemctl start docker, and check that you use sudo or are in the docker group. Then run docker version again: both parts must appear.

Check your understanding

Amira's Mac is arm64. A cloud server is amd64. Why does docker run python:3.14.8-slim-trixie work on both, and what must she remember for her own image later?

Show the answer

The Python image is multi-platform: one name and tag, with a variant for linux/arm64, one for linux/amd64, and others. Each computer pulls the variant for its own processor. An image that she builds on her Mac is, by default, only for linux/arm64. A server that needs linux/amd64, such as Azure Container Apps, cannot run it, so in Module 5 she builds it for linux/amd64.

Guided: the worked example above is the guided part. You ran nginx, published its port, read its page and its logs, looked inside with exec, diff and inspect, and replaced the container. Do it again yourself if you only read it.

Your task: run and inspect a small container of your own, then stop and remove it. Use the Python image as a small web server, which lists the files of a folder:

  1. Start python -m http.server 8000 in a container named c18m01-files, in the background, with port 8000 of the container published on 127.0.0.1:8081.
  2. Open http://127.0.0.1:8081 in the browser, and find the request in the container’s logs.
  3. Write a file /practice.txt into the container, and get it with the browser or curl at http://127.0.0.1:8081/practice.txt.
  4. Show which files changed in the container, its main process, and its image and command with docker inspect.
  5. Stop the container, and look at its status.
  6. Remove it, and check that it is gone.
Show a model answer
Terminal
docker run -d --name c18m01-files -p 127.0.0.1:8081:8000 python:3.14.8-slim-trixie python -m http.server 8000
curl -s http://127.0.0.1:8081/ | grep title
docker logs c18m01-files
Output
8d637cfef539aca7ea7eda064f85ce136529f1cfe31e632d9e5ab4d3024fe0ba
<title>Directory listing for /</title>
172.17.0.1 - - [07/Oct/2026 17:01:06] "GET / HTTP/1.1" 200 -

The page lists the folders of the container’s file system (bin/, etc/, tmp/ and others), because the server started in /.

Terminal
docker exec c18m01-files sh -c 'echo "practice" > /practice.txt'
curl -s http://127.0.0.1:8081/practice.txt
docker diff c18m01-files
docker top c18m01-files
docker inspect c18m01-files --format 'Image={{.Config.Image}} Cmd={{.Config.Cmd}} Status={{.State.Status}}'
Output (docker diff shortened)
practice
C /usr
C /usr/local
C /usr/local/lib
C /usr/local/lib/python3.14
...
A /usr/local/lib/python3.14/http/__pycache__/server.cpython-314.pyc
...
A /practice.txt
UID PID PPID C STIME TTY TIME CMD
root 17450 17427 15 17:01 ? 00:00:00 python -m http.server 8000
Image=python:3.14.8-slim-trixie Cmd=[python -m http.server 8000] Status=running

docker diff shows /practice.txt, and also many __pycache__ files. Python saved compiled copies of the modules that it imported, in the writable layer. Module 2 turns this off for the ticket API with PYTHONDONTWRITEBYTECODE=1.

Terminal
docker stop c18m01-files
docker ps -a --filter name=c18m01-files
docker rm c18m01-files
docker ps -a --filter name=c18m01-files
Output
c18m01-files
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8d637cfef539 python:3.14.8-slim-trixie "python -m http.serv…" 14 seconds ago Exited (137) 1 second ago c18m01-files
c18m01-files
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

docker stop took about 11 seconds and ended with Exited (137): like sleep, this Python server does not react to the stop request, so Docker forced it after 10 seconds. The last list is empty: the container is gone, with its writable layer and /practice.txt. The image is still there.

Check your understanding

Retrieval: which command lists stopped containers too, and which command removes a stopped container?

Show the answer

docker ps -a lists every container, also the stopped ones (docker ps lists only the running ones). docker rm <name> removes a stopped container. For a running container, use docker rm -f <name>, or docker stop first.

Check your understanding

Predict: Sam changes the nginx start page with docker exec in c18m01-web, then runs docker stop c18m01-web and docker start c18m01-web. Which page does the browser show? And after docker rm -f and a new docker run?

Show the answer

After stop and start: the changed page. The container still exists, so its writable layer, with the changed index.html, is still there. After docker rm -f and a new docker run: Welcome to nginx! again. docker rm deleted the old writable layer, and the new container sees only the image’s read-only layers, which never changed.

Check your understanding

Completion check: explain which state belongs to the image and which belongs to the running container. Use the module practice as the example.

Show the answer

The image holds everything that is the same for every container. That is the read-only layers with the files: Debian 13, Python 3.14.8 and its standard library. It is also the metadata: the start command python3 and the ENV values. It does not change when a container runs. docker history and docker image inspect show it.

The container holds what belongs to one running copy:

  • its process, python -m http.server 8000 (docker top);
  • its writable layer with every change: /practice.txt and the __pycache__ files (docker diff);
  • its name, its network address and its published port, 127.0.0.1:8081;
  • its logs (docker logs) and its state: running, then Exited (137).

You choose the command and the port with docker run, and docker inspect shows them. All of this goes away with docker rm. So data that must survive cannot stay in a container’s writable layer: Module 3 shows where it goes instead.

Recap

In this module, you:

  • saw why a project works on one computer and fails on another. The environment is more than the packages, and a lock file fixes only the packages;
  • learned that an image is read-only layers plus metadata. A container is a process with a writable layer, on a shared Linux kernel. A virtual machine has its own kernel;
  • installed Docker, checked it with docker version, docker info and hello-world, and found your CPU architecture;
  • ran, inspected, changed, stopped and removed containers, and proved that docker rm deletes the writable layer.

Reset procedure: list your containers with docker ps -a, and remove every c18m01- container and the hello-world container with docker rm -f <name>. Remove nginx:1.30.5 and hello-world with docker image rm. Keep python:3.14.8-slim-trixie. Check with docker system df. The ticket-api project did not change in this module.

Next, you write a Dockerfile for the ticket API, and build its first image.

Next: Dockerfile essentials