What Docker is, and why
The problem containers solve, what Docker ships, and the lab VM for this track.
This lesson sets up the lab VM used for the whole track and uses it to show what Docker changes about running software and what it leaves to you. The change is easiest to see in a common failure. A service passes its tests on a developer laptop with Node.js 24 and crashes at startup on a build server that still has Node.js 20, installed by hand years ago from a wiki page. The code is identical; the machine around it is not.
Docker moves the runtime, the libraries and the files an application needs into one versioned artifact, the image, and starts it the same way everywhere a Docker engine runs. The server then needs Docker and nothing else from the application's world.
Images, containers and registries
Three words carry the rest of the track. An image is a read-only bundle of files (an operating system userland such as Alpine or Debian, a language runtime, your application) plus metadata saying which program to start. A container is a process started from an image, with its own view of the filesystem, network and process list, and a thin writable layer of its own on top of the image. A registry is a server that stores images by name; Docker Hub is the default one, and docker run downloads (pulls) an image from it when the machine does not have it yet. The lesson "Images, containers and the core commands" takes the image and container pair apart properly; here you only need the vocabulary.
What Docker actually ships
"Docker" names several pieces. On Linux you install Docker Engine: the dockerd daemon (a background service that owns images, containers, networks and volumes), containerd, which manages the container processes and on current installs also stores the images, and runc, the small program that asks the kernel to create each container. The docker command you type is only a client. It sends API requests to dockerd over the Unix socket /var/run/docker.sock. Two plugins ride along with the client: Buildx, which builds images, and Compose, which runs multi-container applications from one YAML file. On macOS and Windows, Docker Desktop (or an alternative such as OrbStack, Colima or Podman Desktop) runs that same Linux engine inside a small virtual machine, because Linux containers need a Linux kernel.
The lab you will use
Every command and every output in this track was run in a disposable Ubuntu 26.04 virtual machine with Docker Engine 29.8.2, containerd 2.3.6, Buildx 0.37.1 and Compose 5.6.0. You can build the same VM. Download the lab kit from the course page (secopslog-docker-lab.tar.gz), unpack it, and run its setup script on your workstation. It uses Multipass if it is installed and Lima otherwise; Windows users run the multipass commands from the kit's README instead. The first run downloads an Ubuntu cloud image and installs packages, so allow 10 to 20 minutes.
tar -xzf secopslog-docker-lab.tar.gzcd secopslog-docker-lab./setup/create-lab.sh # creates the VM secopslog-dockermultipass shell secopslog-docker # or: limactl shell secopslog-docker/opt/secopslog-lab/setup/verify-lab.sh # inside the VM: PASS/WARN/FAIL per check
The script creates secopslog-docker, installs Docker from Docker's own package repository at pinned versions, and adds your login in the VM to the docker group so you can run docker without sudo. On Multipass that login is ubuntu; on Lima it is your Mac username, with its own home directory. The prompts and paths in this track come from the Multipass VM, so on Lima read /home/ubuntu as your home directory. "Installing Docker Engine and running your first container" shows what the script installed and why that group membership is a serious privilege. A second VM, secopslog-docker-sec, exists for lessons that change the daemon or the host; those lessons say so at the top, so do not create it yet.
Lessons that use files have a Lesson files box on the page with the command that downloads and unpacks them inside the VM, into ~/lab/<lesson>. Everything a lesson creates in Docker is named lab-... or carries the label secopslog.lab=true, and /opt/secopslog-lab/setup/reset-lab.sh uses that to clean up. A plain reset removes lab containers, networks, Compose projects and builders, and keeps volumes, images and your ~/lab/<lesson> folders so you can pick a lesson up again. reset-lab.sh --all also removes lab volumes and lab-built images, and --daemon puts the kit's daemon.json back.
Use the VM rather than your laptop's Docker. The outputs will match what you read here, and the experiments in later courses (root inside containers, the Docker socket, daemon settings) belong on a machine you can throw away.
A runtime the machine does not have
Back to the Node.js problem. The lab VM has no Node.js at all, so the shell answers with exit status 127, "command not found". In an interactive shell Ubuntu adds a hint to install the nodejs package; leave it alone, because the point is that the VM does not need it. The same VM runs Node 24 from the official node:24-alpine image without installing anything:
The second command printed the version from inside the image. --rm deletes the container as soon as the command ends, so nothing is left behind. The third command shows where that Node.js lives, an Alpine Linux 3.24 userland, while the VM itself is Ubuntu. If the image is not on your machine yet, docker run first prints Unable to find image ... locally and the pull progress, then runs the command; the install lesson shows that first pull. The version you see depends on when the 24-alpine tag was last updated, because a tag is a name the publisher can move to a newer build.
One image name, several CPU architectures
"Runs the same everywhere" has one important limit. An image contains compiled programs, and compiled programs are built for a CPU architecture. Popular images are published as an image index, a list that points to one variant per platform. docker image ls --tree shows the variants Docker knows about for an image on this machine:
The lab VM in this run is arm64 (aarch64, an Apple Silicon Mac), so Docker pulled only the linux/arm64/v8 variant and the other rows show 0B. On an Intel or AMD machine the linux/amd64 row carries the size instead, and the image IDs differ. When an image has no variant for your platform, docker run fails with no matching manifest for linux/arm64/v8 in the manifest list entries (or the amd64 equivalent). That is the error people on Apple Silicon or AWS Graviton hit with images built only for amd64. "Multi-platform images" in Docker in depth shows how to build both.
A long-running service
Most containers you run will be long-lived services. This one starts the nginx web server in the background and publishes its port 80 on port 8080 of the VM, bound to the loopback address so only the VM itself can reach it:
docker run -d printed the new container's full ID and returned at once. docker ps lists running containers, and its PORTS column shows the mapping 127.0.0.1:8080->80/tcp. The curl request reached nginx inside the container. docker rm -f stopped and deleted it. Each of those flags gets its own section in "docker run in depth"; the container IDs in your output will be different, since Docker generates a new random ID for every container.
What Docker does not do for you
A container is not a virtual machine. It shares the host's kernel, which is why it starts in a fraction of a second and why the next lesson, "Containers vs virtual machines", is about what that sharing costs. A container is also not a security boundary you can stop thinking about: by default the process inside runs as root, and the Docker daemon itself runs as root on the host. And an image freezes its contents. The Node.js and OpenSSL inside node:24-alpine do not patch themselves; you get fixes by pulling or building a newer image and replacing the container. Docker moves the install guide into a file you can version, test and rebuild. Secrets, upgrades, backups and monitoring are still your job, and later lessons show how to do each of them with containers.
node: command not found, yet docker run --rm node:24-alpine node --version prints v24.21.0. Where did that Node.js binary come from?node --version again afterwards and it still fails with exit status 127.no matching manifest for linux/arm64/v8 in the manifest list entries for an internal image that works on every CI runner. What is the most likely cause?Try this
Run tar -xzf secopslog-docker-lab.tar.gz on a scratch host or disposable cluster and read the output against what this lesson described. Then change one input so it fails, and re-run: the error you get is the one you will meet in production.
Takeaway
If you keep one thing from what docker is, and why, keep “What Docker does not do for you”. Decide now which check you will run when this shows up on a live system, and write it somewhere your team will find it.