Laptop to Production

Images and containers

The package and the running copy.

Time
30 minutes
Level
Beginner

Part 1: The problem

QuickCart, a Friday release

Leo fixed a bug in the order service and tested it on his laptop. Everything passed. He released it to QuickCart’s servers.

Within minutes, every order failed. The servers had an older version of one Python library than Leo’s laptop. The code was identical; what it ran on was not.

The next Saturday, lunch orders tripled. New servers were started to cope, but each one took over two minutes to boot and install Python and every library. For those two minutes, customers saw errors.

Both of QuickCart’s problems have the same cause. The app is installed fresh on every machine, from whatever versions are around that day. QuickCart needs to build the app and everything it depends on once, check it, and then run that exact thing everywhere.

That is what a container image is.

Part 2: The idea

An image is a package that holds everything your app needs to run: your code, the exact libraries it uses, and the small set of operating-system files those libraries expect. It’s built once and never changes.

A container is a running copy of an image. Start an image and you get a container. Start it five times and you get five containers, all identical.

Key point: Build the image once. Run that same image everywhere. Never install the app on the server.

Image and container, side by side

ImageContainer
What it isA package: code, libraries, system filesA running copy of an image
Can it change?No. It’s read-onlyIt can write files, but only to its own thin layer
How many?One per version of your appAs many as you start
When it’s deletedYou delete it on purposeIts written files go with it
LikeA class, or an app buildAn object, or the app running on one phone

What a container really is

A container isn’t a small virtual machine. It’s an ordinary program running on the host’s operating system, with two restrictions the Linux kernel applies to it:

Kernel featureWhat it does for the container
NamespacesGive it a private view: its own files, its own process list, its own network. It can’t see the other containers
cgroups (control groups)Limit how much CPU and memory it may use

So a container shares the host’s kernel — the core of the operating system — with every other container on the machine. That’s why it’s small and quick, and it’s the difference from a virtual machine:

Virtual machineContainer
CarriesA whole operating system, with its own kernelJust the app, its libraries and a few system files
Typical sizeGigabytesTens to hundreds of megabytes
Starts inAbout a minute: it boots an OSAbout a second: it starts a process
IsolationStrong: separate kernelsGood, but the kernel is shared

Containers usually run inside virtual machines, not instead of them. On AWS, your containers run on EC2 instances, which are virtual machines. On macOS and Windows, Docker Desktop runs a small Linux virtual machine, and your containers share its kernel. On Linux, containers share your own system’s kernel directly.

Images are made of layers

An image isn’t one big file. It’s a stack of layers, each one a set of file changes: a base Linux, then Python, then your libraries, then your code. Each line of a Dockerfile usually makes one layer.

LayerHoldsChanges
BaseA minimal Linux, such as DebianRarely
RuntimePython 3.12Rarely
LibrariesFlask and gunicornSometimes
Your codeapp.pyEvery release
Container layerFiles the running container writesThrown away when the container is removed

Layers are shared. If two images use the same base, it’s downloaded and stored once. When you release a new version, a server downloads only the layers that changed — usually just your code, a few kilobytes.

Registries store images

A registry is a server that stores images, like an app store for containers. You push an image to it, and servers pull from it.

RegistryWho runs itQuickCart uses it for
Docker HubDocker. Public images like nginx and pythonBase images
Amazon ECRAWS. Private, in your accountQuickCart’s own images — see Storing images: ECR

An image’s full name says where it lives:

Part of 111122223333.dkr.ecr.us-east-1.amazonaws.com/quickcart-orders:v1What it means
111122223333.dkr.ecr.us-east-1.amazonaws.comthe registry: here, the ECR registry of one AWS account in one region
quickcart-ordersthe repository: one app’s images
v1the tag: which version

A short name like nginx:1.27 is shorthand for docker.io/library/nginx:1.27 — the official nginx repository on Docker Hub.

Tags move. Digests don’t

A tag, like v1 or 1.27, is a label someone put on an image. Labels can be moved: anyone allowed to push can put v1 on a different image tomorrow.

A digest, like sha256:4f1c…, is calculated from the image’s contents. Change one byte, and the digest changes. So a digest always means exactly the same image.

TagDigest
Looks likenginx:1.27nginx@sha256: followed by 64 characters
Chosen byA personCalculated from the contents
Can it point at different code later?Yes, unless the registry forbids itNever
Use it forHumans: “we’re on v2”Machines: “run exactly this”

And latest is not special. It’s just the tag Docker uses when you don’t give one. It usually means “whatever was pushed most recently”, so it tells you nothing about what’s running. Don’t release with it.

“Works on my laptop”, solved — with one catch

An image fixes library versions, system files and settings. It can’t fix the processor. The programs inside an image are compiled for one type of chip:

Built onMakes images forRuns on
An Apple-chip Maclinux/arm64 by defaultARM servers, such as AWS Graviton
Most Intel or AMD machineslinux/amd64Most EC2 instances and Fargate’s default

Part 3: See it

Below are four QuickCart apps — orders, menu, payments and delivery — packed onto one server. Start with Virtual machines, then switch to Containers. Choose Add an app in each, and drag the stack to turn it.

Run each app in
A stack of layers for three apps running in virtual machines: hardware, then the host operating system, then a hypervisor, then for each app a guest operating system with its own kernel, its libraries and the app.Hardwareone physical serverHost operating systemHypervisorsplits the server into virtual machinesGuest OSits own kernelLibrariesOrdersGuest OSits own kernelLibrariesMenuGuest OSits own kernelLibrariesPayments
Operating systems running
4: the host, plus one guest OS per app
Each app carries
a whole operating system: gigabytes
A new copy starts in
about a minute: it boots an operating system
Isolation
strong: every VM has its own kernel
Virtual machines, 3 appsEvery app gets a whole virtual machine, and every VM boots its own guest operating system. Switch to Containers and watch what disappears.

What to notice: the thick guest operating system layer appears once for every app with virtual machines, and not at all with containers. That missing layer is why a container is megabytes instead of gigabytes, and starts in a second. It’s also why containers share one kernel: that is the price of being light.

Part 4: Do it

Free — everything runs on your own computer.

You’ll pull a real image, run two containers from it, look inside one, and tidy everything away. About 20 minutes.

You need Docker installed and running. Set up your computer covers it. On macOS and Windows, open Docker Desktop and wait until it says it’s running. On Linux, the Docker service starts on its own.

  1. Check Docker works

    Terminal
    docker run --rm hello-world
    PartWhat it means
    docker runstart a container from an image. If the image isn’t on your computer yet, download it first
    --rmremove the container as soon as it finishes
    hello-worlda tiny image that prints a message and exits
    You should see
    Unable to find image 'hello-world:latest' locally
    latest: Pulling from library/hello-world
    Status: Downloaded newer image for hello-world:latest
    
    Hello from Docker!
    This message shows that your installation appears to be working correctly.

    Between these lines you’ll also see a Pull complete line and a Digest line, and after them more text explaining what just happened. Notice the first line: you didn’t give a tag, so Docker used latest.

  2. Pull an image with a real version

    Terminal
    docker pull nginx:1.27
    PartWhat it means
    docker pulldownload an image from a registry, without running it
    nginxthe repository: the official nginx web server on Docker Hub
    :1.27the tag: version 1.27. Always name a version rather than relying on latest
    You should see
    1.27: Pulling from library/nginx
    a2318d6c47ec: Pull complete
    7bd1fe4d8e2b: Pull complete
    c9ba5b3e04d7: Pull complete
    Digest: sha256:<64 letters and numbers>
    Status: Downloaded newer image for nginx:1.27
    docker.io/library/nginx:1.27

    Your layer IDs and digest will be different, and there are several more Pull complete lines. Each Pull complete line is one layer. Copy the Digest line somewhere; you’ll use it in step 6. The last line is the image’s full name.

    Now list the images on your computer:

    Terminal
    docker images
    PartWhat it means
    docker imageslist every image stored on this computer
    You should see

    A row for nginx with tag 1.27, and one for hello-world. Each has an image ID and a size. The exact columns depend on your Docker version.

  3. Run a container

    Terminal
    docker run -d -p 8080:80 --name web nginx:1.27
    PartWhat it means
    docker runstart a container
    -ddetached: run it in the background and give you your terminal back
    -p 8080:80publish a port: connections to port 8080 on your computer go to port 80 inside the container, where nginx listens
    --name webcall the container web, so later commands can use the name
    nginx:1.27the image to start it from
    You should see
    4d29ffd981e4c0a7b5e1d2f3a9c8b7e6d5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0

    That long string is the new container’s ID. Yours will be different. Now open http://localhost:8080 in your browser.

    You should see

    A page headed Welcome to nginx! It came from inside the container, through the port you published.

  4. See what's running

    Terminal
    docker ps
    PartWhat it means
    docker pslist running containers. Add -a to include stopped ones
    You should see
    CONTAINER ID   IMAGE        COMMAND                  CREATED          STATUS          PORTS                                     NAMES
    4d29ffd981e4   nginx:1.27   "/docker-entrypoint.…"   30 seconds ago   Up 29 seconds   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp   web

    The PORTS column shows the port you published. Next, see what nginx has written to its log:

    Terminal
    docker logs web
    PartWhat it means
    docker logsprint everything a container has written to its output
    webwhich container
    You should see

    Some start-up lines from nginx, then a line for your browser’s visit containing "GET / HTTP/1.1" 200. On AWS, this same output is what ends up in CloudWatch Logs.

  5. Start a second container from the same image

    One image can run as many containers as you like. Each needs its own name and its own port on your computer:

    Terminal
    docker run -d -p 8081:80 --name web2 nginx:1.27
    PartWhat it means
    -p 8081:80this time, port 8081 on your computer goes to port 80 in the new container
    --name web2a different name; two containers can’t share one
    nginx:1.27the same image as before. Nothing is downloaded this time

    Open http://localhost:8081. The same page, from a second, separate container.

  6. Look inside, and find the shared kernel

    Run a command inside the running container to see which Linux the image contains:

    Terminal
    docker exec web cat /etc/os-release
    PartWhat it means
    docker execrun a command inside a container that’s already running
    webwhich container
    cat /etc/os-releaseprint the file that names the Linux distribution
    You should see
    PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
    NAME="Debian GNU/Linux"
    VERSION_ID="12"

    There are more lines after these. The image carries Debian’s files, whatever system your computer runs. Now ask for the kernel:

    Terminal
    docker exec web uname -r
    PartWhat it means
    uname -rprint the version of the kernel this program is running on
    You should see

    A kernel version number. With Docker Desktop it ends in linuxkit, such as 6.10.14-linuxkit: the kernel of the small Linux virtual machine inside Docker Desktop. On Linux, it’s your own system’s kernel, such as 6.8.0-45-generic. Run the same command with web2 instead of web and you get the identical answer: both containers share one kernel. Only the files are their own.

  7. Run an image by its digest

    A tag can move; a digest can’t. Use the digest you copied in step 2. Replace DIGEST with everything after sha256::

    Terminal
    docker run --rm nginx@sha256:DIGEST nginx -v
    PartWhat it means
    nginx@sha256:DIGESTthe image with exactly this content. @ instead of : means “by digest”, not “by tag”
    nginx -vinstead of starting the web server, run this command: print nginx’s version
    --rmremove the container when the command finishes
    You should see
    nginx version: nginx/1.27.5

    Your patch number may be different. Whatever happens to the 1.27 tag later, this digest will always run exactly the image you have now.

  8. Clean-up step: Clean up

    Stop both containers:

    Terminal
    docker stop web web2
    PartWhat it means
    docker stopask containers to shut down cleanly. Docker waits up to 10 seconds, then forces them
    web web2the containers to stop
    You should see
    web
    web2

    Remove them:

    Terminal
    docker rm web web2
    PartWhat it means
    docker rmdelete stopped containers, including anything they wrote. Their names are free again
    You should see
    web
    web2

    Delete the images too:

    Terminal
    docker image rm nginx:1.27 hello-world
    PartWhat it means
    docker image rmdelete images from this computer. It refuses while any container, even a stopped one, still uses them
    nginx:1.27 hello-worldthe two images you downloaded
    You should see

    Untagged: and Deleted: lines for each image. Run docker ps -a and docker images: neither lists anything from this lab.

Part 5: Check yourself

What is the difference between an image and a container?

Why does a container start in about a second, when a virtual machine takes a minute or more?

The tag v1 on an image in a registry. Can it point at different code next week?

Leo builds an image on his Apple-chip Mac and it runs fine. On AWS it fails at once with "exec format error". Why?

You remove a container that had written files inside itself. What happens to those files?

Part 6: Cheat sheet

On one card
Command or ideaWhat it means
ImageThe package: code, libraries, system files. Read-only
ContainerA running copy of an image. Its written files go when it’s removed
LayerOne set of file changes in an image. Shared between images
docker pull nginx:1.27Download an image
docker run -d -p 8080:80 --name web nginx:1.27Start a container in the background, with a published port
docker ps / docker ps -aRunning containers / all containers
docker logs webWhat a container has printed
docker exec web COMMANDRun a command inside a running container
docker stop web then docker rm webStop it, then delete it
Tag (:1.27)A movable label. Never release with latest
Digest (@sha256:)The exact content. Never changes
exec format errorBuilt for the wrong processor. Use --platform linux/amd64

Finished the build and the quiz? Mark it done to fill in its stop on the route map.