SaveMyCert
Log in
5 of 5 free questions left today·for unlimited practice
Kubernetes Fundamentals

Containerization: Images, Container Runtimes, CRI and the OCI Explained

12 min readKCNA · Kubernetes FundamentalsUpdated

A container is an isolated process running on a shared host kernel, boxed in by Linux namespaces for isolation and cgroups for resource control, and started from a portable, layered image. Everything Kubernetes does rests on this foundation: every Pod you deploy is one or more containers, pulled as images from a registry and executed by a container runtime that the kubelet drives through the Container Runtime Interface. The KCNA exam tests this layer directly, asking what containers actually are, how they differ from virtual machines, how images and registries work, which runtimes Kubernetes supports, and what the Open Container Initiative standardizes. It also expects you to know the story of the Dockershim removal and why Docker-built images kept working afterward. This lesson gives you that whole picture at the recognition depth the exam demands.

What you’ll learn
  • Define a container as an isolated process built on Linux namespaces and cgroups, and contrast it with a virtual machine
  • Describe container images as stacks of read-only layers identified by tags and digests, distributed through registries
  • Recognize the basic Dockerfile instructions and how each produces an image layer
  • Explain the Container Runtime Interface and identify containerd and CRI-O as the mainstream CRI runtimes
  • State what the Open Container Initiative standardizes and why the Dockershim removal did not break Docker-built images
  • Outline how a Pod's containers use images and share a network namespace

What a container actually is

A container is not a small virtual machine. It is an ordinary Linux process, or group of processes, that the kernel has been told to isolate and constrain. Two kernel features do the work. Namespaces control what the process can see: the PID namespace gives it its own process tree, the network namespace its own interfaces and IP address, the mount namespace its own filesystem view, plus UTS (hostname), IPC and user namespaces. Control groups (cgroups) control what the process can use: how much CPU, memory and I/O it may consume. Add a layered image as its root filesystem and you have a container.

The decisive difference from a virtual machine is the kernel. Every container on a host shares that host's kernel; a VM runs on a hypervisor and carries its own complete guest operating system and kernel. That single fact explains all the trade-offs the exam asks about:

AspectContainerVirtual machine
KernelShares the host kernelOwn guest kernel per VM
VirtualizesThe operating systemThe hardware
StartupMilliseconds to secondsSeconds to minutes (full OS boot)
SizeMegabytes (image layers)Gigabytes (full OS disk)
DensityHigh: many per hostLower: heavy per-VM overhead
IsolationKernel-level; weaker boundaryHardware-level; stronger boundary

Because the kernel is shared, containers are lighter and faster to start but offer a weaker isolation boundary than VMs, which is why sensitive multi-tenant platforms sometimes wrap containers in lightweight VMs. For the exam, remember the one-liner: containers virtualize the operating system, VMs virtualize the hardware.

Container images: layers, tags and registries

A container image is the packaged, portable artifact a container is started from: the application binary, its dependencies, and metadata such as the default command to run. An image is built as a stack of read-only layers, each recording a set of filesystem changes. Layers are content-addressed and shared: if ten images are built on the same base layer, that layer is stored and downloaded once. When a container starts, the runtime stacks the read-only layers and adds a thin writable layer on top using copy-on-write; anything the container writes there vanishes when the container is deleted, which is why containers are treated as ephemeral and state belongs in volumes.

Images live in registries, servers that store and serve them, such as Docker Hub, Quay.io, GitHub Container Registry and the managed registries of the cloud providers. A full image reference has the form registry/repository:tag, for example docker.io/library/nginx:1.27. When you omit parts, defaults fill in: Docker Hub as the registry and latest as the tag.

The distinction between tags and digests matters. A tag is a mutable, human-friendly pointer: whoever owns the repository can re-point myapp:latest at a completely different image tomorrow. A digest such as nginx@sha256:... is the immutable content hash of the image and always identifies exactly one build. That is why latest is discouraged in production manifests: two nodes pulling it at different times can silently run different software. Pulling is a single command:

docker pull nginx:1.27

Building images with a Dockerfile

Images are typically built from a Dockerfile, a plain-text recipe of instructions executed top to bottom. The exam expects recognition of the core instructions, not fluency in writing them. FROM names the base image the build starts from. RUN executes a command during the build, such as installing packages. COPY brings files from the build context into the image. EXPOSE documents the port the application listens on. CMD and ENTRYPOINT define what runs when a container starts from the image: ENTRYPOINT is the fixed executable, CMD supplies default arguments that are easy to override.

FROM node:20-alpine
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Each instruction that changes the filesystem produces a new layer, and the builder caches layers: if an instruction and everything before it are unchanged, the cached layer is reused instead of rebuilt. That is why Dockerfiles copy dependency manifests and install packages before copying the frequently-changing application code, so edits to your code do not invalidate the expensive install layer.

You build and tag with docker build -t myapp:1.0 . and publish with docker push. Two size-reduction ideas are worth recognizing: small base images such as alpine or distroless images that omit shells and package managers, and multi-stage builds, where one stage compiles the application and a final minimal stage copies in only the finished binary, leaving compilers and build tools out of the shipped image. Smaller images pull faster and expose less attack surface.

Container runtimes and the CRI

The container runtime is the node-level software that actually pulls images and runs containers. Kubernetes does not run containers itself: on every node, the kubelet asks the runtime to do it, speaking a standard gRPC API called the Container Runtime Interface (CRI). The CRI defines operations like pull this image, create this sandbox, start this container, so any runtime that implements it can plug into Kubernetes without changes to the kubelet. This pluggability is the whole point: the interface decouples Kubernetes from any single vendor's runtime.

Two CRI runtimes dominate real clusters. containerd is a graduated CNCF project that began life inside Docker and was donated as a standalone runtime; it is the default in most managed Kubernetes services. CRI-O is a lighter runtime created specifically to serve the CRI for Kubernetes and nothing else; it is the default in Red Hat OpenShift. Functionally both do the same job for the kubelet.

AspectcontainerdCRI-O
OriginExtracted from Docker, donated to CNCFBuilt by the Kubernetes community, led by Red Hat
ScopeGeneral-purpose runtime, also used outside KubernetesPurpose-built to implement the CRI for Kubernetes only
Common homeDefault in most managed Kubernetes offeringsDefault in OpenShift

One more layer completes the picture: these are high-level runtimes that manage images and container lifecycles, and they delegate the final step of creating the isolated process to a low-level runtime, almost always runc, which sets up the namespaces and cgroups and starts the process. Kubelet speaks CRI to containerd or CRI-O; they invoke runc.

The Dockershim removal, and why Docker images still work

Docker Engine predates the CRI and never implemented it. To keep supporting Docker as a runtime, the Kubernetes project maintained an adapter inside the kubelet called the Dockershim, which translated CRI calls into Docker Engine API calls. Maintaining a vendor-specific shim inside core Kubernetes was a growing burden, especially since Docker itself uses containerd underneath, so the shim added a hop that contributed nothing. Kubernetes deprecated the Dockershim in version 1.20 and removed it in version 1.24. Since 1.24, the kubelet only speaks CRI, and nodes use a CRI runtime such as containerd or CRI-O; teams that still want Docker Engine as the runtime can install cri-dockerd, an externally maintained adapter.

The exam's favorite follow-up: did this break images built with Docker? No, and understanding why is the point. An image built by docker build is an OCI-compliant image, the same standardized format every runtime consumes. containerd and CRI-O pull and run Docker-built images exactly as before. What was removed was Docker Engine's role as the node runtime that Kubernetes drives; the image format was never Docker-specific in any way that mattered, because it is governed by an open standard.

So the correct summary, and the one to recognize among wrong answers claiming Kubernetes dropped Docker support entirely, is: Kubernetes removed its adapter for Docker Engine as a runtime; developers still build images with Docker every day, push them to registries, and run them on Kubernetes clusters whose nodes use containerd or CRI-O.

The Open Container Initiative (OCI)

The interoperability that made the Dockershim removal painless comes from the Open Container Initiative, an open governance project established in 2015 under the Linux Foundation to standardize container formats and runtimes. Docker seeded it by donating its image format and its runtime code, and the industry, including the major cloud providers and container vendors, maintains the specifications together. The OCI publishes three specifications the exam expects you to name:

  • The image spec defines what a container image is: the layout of its filesystem layers, its manifest and its configuration metadata. Any OCI image can be built by one tool and run by another.
  • The runtime spec defines how to run a container from an unpacked image: the configuration of namespaces, cgroups, mounts and process settings, and the lifecycle a runtime must implement.
  • The distribution spec defines the registry API for pushing and pulling images, so any client can talk to any registry.

The reference implementation of the runtime spec is runc, the donated low-level runtime that containerd and CRI-O both use to create the actual isolated process. Alternative runtime-spec implementations exist for stronger isolation, such as sandboxed and lightweight-VM runtimes; because they honor the same spec, they slot in without changing Kubernetes.

Keep the two interface layers straight, because questions blur them deliberately: OCI standardizes the artifacts and the low-level runtime behavior, industry-wide; CRI is the Kubernetes-specific API the kubelet uses to talk to a high-level runtime. containerd sits in the middle, speaking CRI upward to the kubelet and driving an OCI runtime, runc, downward.

Running containers: docker and podman

You should recognize the everyday commands of the two mainstream client tools. With Docker, docker run creates and starts a container from an image, with familiar flags: -d detaches it to run in the background, -p 8080:80 publishes container port 80 on host port 8080, -e sets an environment variable, and --name labels it. Supporting commands include docker ps to list running containers, docker logs to read output, docker exec to run a command inside a running container, and docker stop and docker rm to end and remove it.

docker run -d --name web -p 8080:80 nginx:1.27
docker ps
docker logs web
docker exec web nginx -v
docker stop web

Podman is the main alternative, and its differences are exactly what the exam probes. Docker uses a client-server design: the CLI talks to a long-running privileged daemon that owns every container. Podman is daemonless, running containers as ordinary child processes with no central daemon, and it emphasizes rootless operation, letting unprivileged users run containers, which shrinks the attack surface. Its command line is deliberately compatible with Docker's, so podman run, podman ps and podman build work as you would expect; both tools produce and consume the same OCI images. In the same Red Hat family, buildah specializes in building OCI images and skopeo in inspecting and copying them between registries.

For KCNA, the takeaways are recognition-level: these are developer-facing tools for building and running individual containers on a machine. On a Kubernetes node, the kubelet and the CRI runtime do that job; you do not run docker commands to operate Pods.

How Pods use container images

Kubernetes never runs a bare container; its smallest deployable unit is the Pod, which wraps one or more containers. Each container in a Pod names its image in the spec, and when the Pod is scheduled to a node, the kubelet instructs the CRI runtime to pull each image from its registry and start the containers. Private registries are handled with image pull secrets referenced by the Pod, so the runtime can authenticate.

The imagePullPolicy field controls when the node pulls: Always contacts the registry on every start (the default when a Pod uses the latest tag or no tag), IfNotPresent pulls only when the image is missing from the node's local cache (the default for specific tags), and Never uses only the local cache and fails if the image is absent. When a pull fails, because the tag does not exist, the registry needs credentials, or the network is down, the Pod's container reports ErrImagePull and then ImagePullBackOff as Kubernetes retries with increasing delays; recognizing that status as an image problem, not a scheduling or crash problem, is a standard exam discrimination.

Containers in the same Pod are more intimate than neighboring containers on a host: they share the same network namespace, meaning one IP address for the whole Pod and the ability to reach each other over localhost, and they can share storage volumes. The runtime holds those shared namespaces in a minimal infrastructure container, often called the pause container, that anchors the Pod so application containers can restart without the Pod losing its network identity. This is the mechanic behind the sidecar pattern, where a helper container, such as a log shipper or proxy, runs beside the main application container in one Pod.

Tip. KCNA probes containerization with definitional questions: which kernel features containers are built on (namespaces and cgroups), containers versus VMs (shared kernel is the trigger), and which body standardizes image and runtime formats (OCI, not CNCF or CRI). Expect the Dockershim question in some form, with wrong answers implying Docker images stopped working; the correct framing is that only Docker Engine as a kubelet-driven runtime was removed in 1.24. Distractors also swap CRI and OCI, or containerd and runc, so know which layer each name lives at.

Key takeaways
  • A container is an isolated process on a shared host kernel: namespaces control what it sees, cgroups control what it uses.
  • Containers virtualize the operating system; VMs virtualize the hardware and each carry their own guest kernel.
  • Images are immutable stacks of read-only layers; tags are mutable pointers, digests are immutable content hashes.
  • The kubelet drives the container runtime through the CRI; containerd and CRI-O are the mainstream CRI runtimes, and both delegate to runc.
  • Kubernetes deprecated the Dockershim in 1.20 and removed it in 1.24; Docker-built images still run because they are OCI images.
  • The OCI publishes the image, runtime and distribution specs; runc is the reference runtime implementation.
  • Podman is a daemonless, rootless-friendly alternative to Docker with a compatible CLI, producing the same OCI images.
  • Containers in one Pod share a network namespace and one IP, talking to each other over localhost.

Frequently asked questions

What is the difference between a container and a virtual machine?

A container is an isolated process that shares the host's kernel, using Linux namespaces for isolation and cgroups for resource limits, so it starts in milliseconds and weighs megabytes. A virtual machine runs on a hypervisor with its own complete guest operating system and kernel, giving a stronger hardware-level isolation boundary at the cost of gigabytes of footprint and slower boot. In short: containers virtualize the OS, VMs virtualize the hardware.

Did Kubernetes remove support for Docker?

Kubernetes removed the Dockershim, its built-in adapter that let the kubelet drive Docker Engine as a node runtime, deprecating it in 1.20 and removing it in 1.24. It did not break Docker-built images: docker build produces standard OCI images, which CRI runtimes like containerd and CRI-O pull and run exactly as before. You still build with Docker and deploy to Kubernetes; only Docker Engine's role as the node's runtime went away, and cri-dockerd exists for teams that want it back.

What is the Container Runtime Interface (CRI)?

The CRI is the gRPC API through which the kubelet on each node talks to the container runtime, covering operations like pulling images and creating, starting and stopping containers. It decouples Kubernetes from any specific runtime: anything implementing the CRI plugs in without kubelet changes. The two mainstream CRI runtimes are containerd, the default in most managed Kubernetes services, and CRI-O, a Kubernetes-only runtime that is the default in OpenShift.

What does the Open Container Initiative standardize?

The OCI, founded in 2015 under the Linux Foundation, maintains three specifications: the image spec, defining the layered container image format; the runtime spec, defining how a runtime creates and manages a container from an image; and the distribution spec, defining the registry API for pushing and pulling images. Its reference runtime is runc. These standards are why an image built with Docker, Podman or Buildah runs identically under containerd or CRI-O.

What is the difference between a container image tag and a digest?

A tag is a mutable, human-readable label like nginx:1.27 that the repository owner can re-point to a different image at any time, which is why relying on latest in production is risky. A digest is the immutable SHA-256 content hash of an image, referenced as name@sha256:..., and always identifies exactly one build. Pinning by digest guarantees every node runs byte-for-byte the same image.

How is Podman different from Docker?

Docker uses a client-server design where the CLI talks to a long-running privileged daemon that owns all containers. Podman is daemonless, launching containers as ordinary child processes, and is designed for rootless operation so unprivileged users can run containers, reducing attack surface. Its CLI is intentionally Docker-compatible, and both tools build and run the same OCI-standard images, so skills and images transfer directly between them.

Test yourself on this topic
Practice questions with full explanations.
Practice now

Sign up free to mark lessons complete, bookmark topics and track your exam readiness.