University of Wales Trinity Saint David
ACCA7023 - Agile and DevOps

Virtualisation and Containers

Overview

This is the first lab in the Docker Foundations series and it assumes you have never used Docker. You will install Docker, run your first containers, and use them to see for yourself what a container is and how it differs from a virtual machine. By the end you will be able to explain, with evidence you gathered, why containers have become the normal way to package and ship software.

Learning Objectives

By the end of this lab you will be able to:

Concept

Software rarely runs alone. It needs a particular language runtime, particular libraries and particular settings. Install two applications that need different versions of the same library on one server and one of them breaks. Move an application to a new server and something that was installed by hand on the old one is missing. Developers summarise this as it works on my machine.

Virtualisation was the first widely adopted answer. A program called a hypervisor divides one physical computer into several virtual machines (VMs). Each VM behaves like a complete computer with its own operating system, so each application can have exactly the environment it needs. A Type 1 hypervisor runs directly on the hardware and is what data centres and cloud providers use (VMware ESXi, Microsoft Hyper-V, KVM). A Type 2 hypervisor runs as an application on a normal desktop operating system (VirtualBox, VMware Workstation). The cost is weight. Every VM carries a whole operating system, which takes gigabytes of disk and memory and tens of seconds to boot.

Containerisation isolates applications without duplicating the operating system. Every container on a machine shares the one kernel of the host, the core of the operating system that manages processes, memory and devices. The kernel keeps containers apart with two features. Namespaces limit what a process can see, so each container has its own file system, process list, network and host name. Control groups (cgroups) limit what a process can use, such as memory and CPU time. A container is therefore an ordinary process that has been given a private view of the system. It starts in a fraction of a second and costs little more than the application itself.

A container is started from an image, a read-only package holding a file system (the application, its runtime and its libraries) plus some settings such as which command to run. One image can start any number of containers, in the same way that one class can create many objects. Docker is the most widely used tool for building images and running containers. It is not the only one, and the image format it popularised is now an open standard maintained by the Open Container Initiative (OCI).

Containers do not replace virtual machines. Cloud providers run your containers inside VMs, and on Windows and macOS Docker Desktop itself runs a small Linux VM to provide the Linux kernel that containers need. The two technologies are layers that work together.

Tasks

Task 1 - Install Docker and prepare a terminal

Install Docker Desktop from docker.com/products/docker-desktop. On Windows, accept the option to use the WSL 2 back end. On Linux you may install Docker Engine instead. Start Docker Desktop and wait until it reports that the engine is running.

All commands in this series are written for a Bash terminal. On macOS and Linux use the built-in terminal. On Windows use Git Bash, which is installed with Git for Windows. Git Bash helpfully rewrites anything that looks like a Linux path, such as /app, into a Windows path. With Docker that help is harmful, because those paths are inside a Linux container. Turn it off permanently, then open a new Git Bash window.

# Windows Git Bash only
echo 'export MSYS_NO_PATHCONV=1' >> ~/.bashrc

Now ask Docker for its version.

docker version

Success condition: the output has two sections, Client and Server. If the Server section is missing with an error about connecting, Docker Desktop is not running yet.

The two sections show how Docker is built. The docker command you type is only a client. It sends your request to the Docker Engine (also called the daemon, dockerd), a background service that does the real work of pulling images and running containers. On Windows the client line says windows while the server line says linux, because the engine runs inside Docker Desktop's Linux VM.

Task 2 - Run your first container

docker run hello-world

Success condition: a message starting Hello from Docker!. Read the numbered list in that message. It describes exactly what just happened. The client contacted the engine. The engine could not find an image called hello-world on your machine, so it pulled (downloaded) it from Docker Hub, a public library of images. It created a container from the image and ran it. The program printed its message and exited, and the container stopped with it.

docker run hello-world

Run it a second time and notice there is no download. The image is now stored locally and is reused.

Task 3 - Compare the three ways to run software

The diagram compares running two applications directly on a server, in virtual machines, and in containers. Click each layer and read what it does, then answer the questions.

Which layer exists once per application in a virtual machine design but only once per server in a container design?

Every VM boots its own guest operating system with its own kernel. Containers all share the host kernel. Libraries and code are packaged per application in both designs.

What does a namespace do for a containerised process?

Namespaces control what a process can see. Control groups control what it can use.

A Windows laptop runs Docker Desktop. Where do the Linux containers actually run?

Linux containers need a Linux kernel. Docker Desktop provides one in a lightweight VM using WSL 2 on Windows or the Apple virtualisation framework on macOS.

Which statement about isolation is most accurate?

A flaw in the shared kernel can affect every container on the host. A VM has its own kernel, so the boundary is the hypervisor, which is smaller and harder to cross. This is one reason cloud providers run customer containers inside VMs.

Task 4 - Step inside a container

Start a container from the tiny alpine Linux image and open a shell inside it. -i keeps the input open and -t gives you a terminal, usually written together as -it. --rm deletes the container when you leave.

docker run -it --rm alpine sh

Your prompt changes to / #. You are now running commands inside the container. Try each of these.

cat /etc/os-release
ps
hostname
ls /
exit

Success condition: os-release says Alpine Linux even though your computer runs something else. ps shows only one or two processes and your shell is process number 1. The host name is a random string, which is the start of the container's ID. The container has its own file system, its own process list and its own name. Those are namespaces at work.

Task 5 - Prove that containers share a kernel

Alpine and Ubuntu are different Linux distributions with different files and tools. Ask each one which distribution it is, and which kernel it is running on.

docker run --rm alpine cat /etc/os-release
docker run --rm ubuntu:24.04 cat /etc/os-release
docker run --rm alpine uname -r
docker run --rm ubuntu:24.04 uname -r

Success condition: the distributions differ but the kernel version is identical. On Windows it ends in microsoft-standard-WSL2, the kernel of Docker Desktop's VM. A distribution is just a set of files, so a container can carry any distribution it likes. The kernel is the one thing it cannot bring with it. This is also why a Linux container cannot run directly on a Windows kernel.

Task 6 - Limit what a container can use

Control groups let you cap the resources a container can consume, so one misbehaving application cannot starve the others. Start containers with limits and read the limits back from inside.

docker run --rm --memory 64m alpine cat /sys/fs/cgroup/memory.max
docker run --rm --cpus 0.5 alpine cat /sys/fs/cgroup/cpu.max

Success condition: the memory limit reads 67108864, which is 64 MB in bytes. The CPU limit reads 50000 100000, meaning 50 milliseconds of CPU time in every 100 milliseconds, or half of one core. Without these flags a container may use everything the host has.

Task 7 - Tell images and containers apart

docker image ls
docker ps
docker ps -a

docker image ls lists the images stored on your machine, with their sizes. Note how small alpine and hello-world are compared with ubuntu. docker ps lists running containers and is probably empty. docker ps -a lists all containers, including stopped ones.

Success condition: you can find two stopped containers made from hello-world, one for each time you ran it, each with a different random name. The containers you started with --rm are not listed because they deleted themselves. One image, many containers. Clear away the stopped ones. Docker asks you to confirm, so type y.

docker container prune

Task 8 - Evaluate

A company runs 40 small web services. Explain, using the ideas of a guest operating system and a shared kernel, why moving them from one VM each to containers would reduce the number of servers it needs.

Each VM carries a full guest operating system, which typically reserves hundreds of megabytes to gigabytes of memory and several gigabytes of disk before the service itself does anything. Forty VMs means forty copies. Containers share the one host kernel, so each container costs roughly what its own process and libraries need. The same hardware can therefore hold many more services, and they start in under a second instead of booting.

Give one situation in which you would still choose a virtual machine instead of a container.

Any of: running software that needs a different kernel or operating system, such as a Windows application on a Linux host. Running untrusted code from different customers, where the stronger isolation of a separate kernel matters. Needing to change kernel settings or load kernel modules. Running a legacy application that expects a whole machine to itself.

In your own words, what is the difference between an image and a container? Use an analogy from programming.

An image is a read-only template holding the file system and the start-up settings. A container is a running (or stopped) instance created from it, with its own writable layer and its own process. The relationship is like a class and its objects, or a program file and the processes started from it. Many containers can be created from one image and changes inside a container do not alter the image.