Runtimes Basics
This section gives you hands-on footing with the container runtime stack: what tools live on a node, how to inspect them, and where Docker fits versus where containerd takes over.
Prerequisites
- A Linux host or a local Kubernetes cluster (kind, minikube, or a managed cluster on 1.34-1.36).
- Docker Engine 29.6.1 with BuildKit for building images locally.
- Node access (SSH or a debug pod) to run
crictl/ctragainst containerd. kubectlconfigured against your cluster.
# Quick check of the local Docker install (build/dev side)
docker version --format '{{.Server.Version}}'
docker buildx versionBasic Examples
1. Build an image with Docker
Docker's job is the developer inner loop: build and push OCI images.
docker build -t registry.example.com/app:1.0 .
docker push registry.example.com/app:1.0- BuildKit is the default backend in Docker Engine 29, so builds are parallel and cache-aware.
- The output is a standard OCI image, runnable by any compliant runtime.
- Nothing here requires Docker to be present on your Kubernetes nodes.
- Tag with a registry prefix so the image is pullable from the cluster.
2. See what runtime a node uses
kubectl reports the container runtime per node.
kubectl get nodes -o wide- Look at the
CONTAINER-RUNTIMEcolumn - typicallycontainerd://1.xorcri-o://1.x. - Managed clusters (GKE, EKS, AKS) all default to containerd.
- This tells you which CRI tool (
crictlconfig) to use on the node. - Never assume Docker; it will not appear here.
3. List running containers at the CRI layer
On a node, crictl is the CRI-native equivalent of docker ps.
crictl ps
crictl podscrictl pslists application containers;crictl podslists pod sandboxes.crictltalks to the same socket the kubelet uses, so you see the real state.- Requires
crictlconfigured with the runtime endpoint, usuallyunix:///run/containerd/containerd.sock. - Use this when
kubectlis not enough and you need node-level truth.
4. Inspect the containerd socket directly
ctr is containerd's own low-level CLI, useful for debugging containerd itself.
ctr --namespace k8s.io containers list
ctr --namespace k8s.io images list- Kubernetes containers live in the
k8s.iocontainerd namespace; you must pass it. ctrbypasses the CRI abstraction, so prefercrictlfor pod-oriented work.- Handy for confirming an image was actually pulled to the node.
ctris not meant for production orchestration - it is a debugging tool.
5. Pull an image with crictl
You can pre-pull images at the CRI layer to test registry connectivity.
crictl pull registry.example.com/app:1.0- This exercises the node's image credentials and network path to the registry.
- A failure here isolates registry problems from scheduling problems.
- The image lands in containerd's content store, shared by all pods on the node.
- Use digests (
@sha256:...) in production for immutability.
6. Read container logs from the runtime
crictl logs reads the same logs the kubelet exposes.
crictl logs <container-id>- Get the container ID from
crictl psfirst. - Useful when the kubelet or API server is unhealthy and
kubectl logsfails. - Logs come straight from the runtime's log path on disk.
- Combine with
crictl inspect <id>to see the full OCI config.
7. Confirm the low-level runtime
Check which OCI runtime binary containerd invokes.
containerd config dump | grep -A3 'runtimes.runc'
which runc- The default handler is
runc; alternatives appear as extra handlers. runc --versionconfirms the version and the libseccomp it was built against.- This is where a RuntimeClass handler like
runscorcrunwould be registered. - Do not edit this by hand on managed nodes; it is provisioned for you.
Intermediate Examples
8. Route a pod to a sandboxed runtime with RuntimeClass
RuntimeClass lets specific pods use a stronger isolation runtime.
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
---
apiVersion: v1
kind: Pod
metadata:
name: untrusted
spec:
runtimeClassName: gvisor
containers:
- name: app
image: registry.example.com/app@sha256:abc123...- The
handlermust be registered in the node's containerd config. - Pods without
runtimeClassNameuse the node default (runc). - Use this for untrusted or multi-tenant workloads that need a sandbox.
- Schedule such pods only onto nodes that actually have the handler installed.
9. Debug a node with a privileged crictl session
When a pod is stuck, drop to the node and inspect the sandbox lifecycle.
crictl pods --state NotReady
crictl inspectp <pod-id>
crictl stopp <pod-id> && crictl rmp <pod-id>inspectpshows the pod sandbox config, including its network namespace.- Manually stopping a sandbox can clear a stuck pod the kubelet failed to reap.
- Do this only as a last resort; the kubelet normally owns this lifecycle.
- Always confirm with
kubectlafterward that state reconciled.
10. Separate build nodes from run nodes
Keep image building off your worker nodes entirely.
# CI runner (has Docker/BuildKit): build + push
docker buildx build --push -t registry.example.com/app:1.0 .
# Worker node (containerd only): kubelet pulls and runs it
kubectl run app --image=registry.example.com/app:1.0- Build tooling lives in CI, not on production workers.
- Workers only need a CRI runtime and pull credentials.
- This keeps the node attack surface minimal and the runtime consistent.
- The OCI image format is the clean handoff between the two worlds.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).