What you will be able to explain
- Distinguish an image, a container, a tag and a digest.
- Read a Dockerfile and explain why multistage builds matter.
- Follow an image from a registry to a CRI-compatible runtime.
Reading requires no setup. The code examples use Docker with Linux containers, an internet connection and free local ports 8081 and 8082.
Container images
An image is the packaged filesystem and configuration used to start a container. A container is an instance created from that image, with its own process state and writable layer. You can start several containers from the same image without rebuilding the application.
An image is not a full virtual machine. Containers share the host kernel; Docker Desktop normally runs Linux containers inside a Linux virtual machine. The image must match the runtime platform: an image built for linux/arm64 is not automatically suitable for a linux/amd64 node.
Filesystem layers record changes and can be shared between images. A running container adds a writable layer, but writes there do not update the original image. Data that must outlive the container belongs in an appropriate volume or external service.
A tag such as academy-web:1.0 is a human-readable reference and can be moved to different content. A digest identifies content. Use a version tag for readability and a verified digest when a deployment must select a specific image. The name latest is only a tag, not a promise of freshness.
Building application images with Docker
The example is a small Go HTTP service using only the standard library. The compiler runs inside Docker, so Go does not need to be installed on the host. The files below belong to one directory. The service listens on :8080 so requests can reach it through the container network.
A Dockerfile is the build recipe. FROM chooses the starting image, WORKDIR sets the working directory, COPY adds source files, and RUN compiles the binary during the build. ENTRYPOINT defines the process started later, when a container runs. USER selects a non-root numeric identity; it does not create a named account.
The final dot in docker build is the build context. COPY can read included files from that context. .dockerignore keeps unnecessary or sensitive files out of it; it is not a substitute for reviewing what is included. Never copy credentials into an image and delete them in a later layer. For private dependencies, use BuildKit secret mounts rather than ARG or ENV for secrets.
This first image works, but it retains the compiler and build environment. EXPOSE documents the application port; it does not publish it on the host. The -p option below maps host port 8081 to container port 8080 and binds only to local loopback. Image creation and container startup are separate operations.
module example.com/academy-web
go 1.26.0package main
import (
"fmt"
"log"
"net/http"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello from Proxy Academy")
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
}
log.Fatal(server.ListenAndServe())
}FROM golang:1.26-alpine
WORKDIR /src
COPY go.mod main.go ./
RUN CGO_ENABLED=0 go build -trimpath -o /server .
USER 65532:65532
EXPOSE 8080
ENTRYPOINT ["/server"].git
.env
.env.*
*.pem
*.key
bin/
coverage/docker build -t academy-web:single .
docker run -d --name academy-web-single -p 127.0.0.1:8081:8080 academy-web:singlecurl --fail http://127.0.0.1:8081/Hello from Proxy AcademyMultistage image builds
A multistage Dockerfile separates tools needed to build the application from files needed to run it. The build stage compiles the service. The runtime stage starts from scratch and copies only the resulting binary. The compiler and source tree remain outside the final image.
For this standard-library Go service, CGO_ENABLED=0 produces a binary that does not depend on a C runtime. scratch is an empty starting point: there is no shell, package manager, certificate store or timezone database. If your application needs these, add the required files or choose a suitable minimal runtime image. A Java application, for example, normally needs a Java runtime.
Build caching reuses unchanged work. In projects with external dependencies, copy dependency manifests and download dependencies before copying frequently changing source files. A dependency change should invalidate the relevant cache. Cache efficiency does not replace rebuilding to pick up reviewed security fixes.
The Go tag in this example is convenient for learning, but it can change. For repeatable release builds, pin the base image by a verified digest and update it deliberately. A small image reduces unnecessary packages; it does not prove the application is secure.
FROM golang:1.26-alpine AS build
WORKDIR /src
COPY go.mod main.go ./
RUN CGO_ENABLED=0 go build -trimpath -o /server .
FROM scratch AS runtime
COPY --from=build /server /server
USER 65532:65532
EXPOSE 8080
ENTRYPOINT ["/server"]docker build -f Dockerfile.multistage -t academy-web:1.0 .
docker image ls academy-web
docker run -d --name academy-web-runtime -p 127.0.0.1:8082:8080 academy-web:1.0curl --fail http://127.0.0.1:8082/Hello from Proxy AcademyStoring images in a remote registry
A local image belongs to the machine or builder that created it. Kubernetes nodes need a way to obtain it. A registry stores and distributes images; a repository groups related images; tags identify references within a repository. For example, docker.io/YOUR_DOCKERHUB_USER/academy-web:1.0 names the registry, namespace, repository and tag.
The commands below illustrate publishing to Docker Hub. Replace YOUR_DOCKERHUB_USER with a namespace you control and create the repository with the intended visibility before pushing. docker tag adds a reference; it does not rebuild the image. docker push uploads the referenced image. It is an external publication step, not a local build.
Authenticate using docker login and the supported sign-in flow. Do not place a password or token in the Dockerfile. A private repository requires read access on the Kubernetes side as well; your laptop login is not automatically available to every node. That access is commonly configured through imagePullSecrets or a platform credential provider.
After publishing, record the registry digest for deployment. A multi-platform image index can select different architecture-specific images. Building on an ARM laptop alone does not create an AMD64 image; build and verify the platforms your cluster needs.
docker login
docker tag academy-web:1.0 docker.io/YOUR_DOCKERHUB_USER/academy-web:1.0
docker push docker.io/YOUR_DOCKERHUB_USER/academy-web:1.0
docker pull docker.io/YOUR_DOCKERHUB_USER/academy-web:1.0The Container Runtime Interface
Docker is useful for building and testing images, but a Kubernetes node does not need Docker Engine to run an image produced by Docker. OCI image specifications describe packaging and distribution formats. CRI describes how kubelet asks a compatible runtime to manage images and containers. They solve different problems.
CRI is a gRPC interface between kubelet and the container runtime. Kubelet uses its image service to request image operations and its runtime service to manage Pod sandboxes and containers. containerd and CRI-O provide compatible implementations; the runtime delegates low-level container execution to components such as runc.
The scheduler chooses a node; it does not execute containers. Once a Pod is assigned, kubelet works with the runtime on that node. A missing image, incompatible platform or registry permission failure can prevent startup even when the Pod specification was accepted. Think of the path as image reference → registry access → image pull → container startup.
Cleanup
A container, an image, a volume and the build cache have separate lifecycles. Stopping a container leaves it on disk. Removing the container removes its writable layer, but does not remove its image or a named volume. Removing a local image also does not delete the copy in a remote registry.
The example cleanup names only the two demonstration containers and their image tags. Stop the containers before removing them. If you created an additional registry tag, remove that local tag separately when you no longer need it. Avoid broad prune commands on a shared development machine: unrelated resources may still be useful.
docker stop academy-web-single academy-web-runtime
docker rm academy-web-single academy-web-runtime
docker image rm academy-web:single academy-web:1.0Summary
Build produces an image; run creates a container from it. Multistage builds keep build tools out of the runtime image. A registry makes the artifact available to other machines, while tags and digests serve different identification needs.
Kubernetes takes this artifact as an input. Kubelet and a CRI-compatible runtime turn an image reference into running containers on the selected node. The next lesson explains how the control plane coordinates that work and reconciles the desired state.
Sources and further reading
Original explanations and examples based on Docker and Kubernetes documentation.