On a containerd Kubernetes node, what replaces `docker ps`, and how does crictl's view differ?
answer
- The docker CLI talks to the wrong daemon
- A CRI-shaped client ships with Kubernetes
- Sandboxes and containers are listed separately
- Containerd namespaces: the cluster uses k8s.io
basics
~20 scrictl replaces it. The docker CLI talks to dockerd, which is not the runtime on such a node. crictl speaks the CRI gRPC API instead and is sandbox-aware: crictl pods lists pod sandboxes, crictl ps lists the containers inside them.
solid answer
~50 s`docker ps` asks dockerd over `/var/run/docker.sock`, and on a CRI node the node agent never talked to dockerd, so there is nothing there to list - often no `docker` binary either. Even when Docker Engine is installed, its containers sit in containerd's `moby` namespace while the cluster's sit in `k8s.io`, so they stay invisible to each other. The CRI-native tool is `crictl` from the Kubernetes cri-tools project: it speaks CRI gRPC to the endpoint in `/etc/crictl.yaml`, typically `unix:///run/containerd/containerd.sock`, and mirrors the contract's shape - `crictl pods` for sandboxes, `crictl ps` for containers, `crictl images` for what the ImageService holds, plus `logs`, `inspect` and `exec`. It is a debugging client, not a manager: the node agent owns those containers and will recreate or garbage-collect whatever you touch. `nerdctl` is the docker-ergonomic alternative, but needs `--namespace k8s.io` or it shows an empty table.
code
bash · 8 lines# What the cluster's runtime is actually running on this node
crictl pods --name fanout
crictl ps -a --pod 4f1c9d2b7a83e
crictl logs --previous 9b2e41c07d5a
crictl inspect 9b2e41c07d5a
# Docker-style ergonomics against containerd itself - namespace is mandatory
nerdctl --namespace k8s.io psgo deeper
Recall that the docker CLI only ever talks to the Docker daemon, so on a node running a different runtime it lists nothing useful. Know the name crictl and that crictl ps is the closest equivalent to docker ps.
Explain why the containers are invisible rather than absent - separate daemons, and separate containerd namespaces (moby versus k8s.io) - and show that crictl's verbs mirror the CRI services, with sandboxes listed apart from containers.
Demonstrate a node-level investigation with no cluster access: find the sandbox, list exited containers, read the previous instance's logs, inspect exit codes and mounts, and know that exec is impossible in a shell-less image. Also know that writes fight the node agent.
Own the operability angle: which debugging tools are installed and configured on every node before an incident, whether endpoints are pinned rather than auto-detected, and how you keep node-level access from becoming an unaudited backdoor around the cluster API.
## Who each CLI is talking to `docker ps` is a thin client; the work happens in `dockerd`, which the CLI reaches over `/var/run/docker.sock`. On a Kubernetes node whose runtime is containerd, the node agent never spoke to dockerd - it makes CRI gRPC calls into containerd - so there is no dockerd holding those containers, and frequently no `docker` binary installed at all. Even where Docker Engine *is* installed on such a node, for builds or for legacy tooling, `docker ps` still will not show the cluster's containers. Docker Engine drives containerd inside the containerd **namespace** `moby`, while the Kubernetes node agent uses the namespace `k8s.io`. A containerd namespace is a tenancy partition of the runtime's own metadata, so the two sets of containers are simply invisible to each other's clients. This is the single most common surprise after a runtime migration: the command runs, exits zero, and prints an almost empty table. ## crictl: a CLI shaped like the CRI contract `crictl` ships from the Kubernetes `cri-tools` project and speaks the CRI gRPC API directly to a runtime endpoint. Because the contract has two services, crictl's verbs split the same way: - RuntimeService: `crictl pods` (sandboxes), `crictl ps` (containers), `crictl inspect`, `crictl inspectp`, `crictl logs`, `crictl exec`, `crictl stats` - ImageService: `crictl images`, `crictl pull`, `crictl rmi`, `crictl imagefsinfo` The endpoint comes from `/etc/crictl.yaml` (`runtime-endpoint: unix:///run/containerd/containerd.sock`) or from `--runtime-endpoint`. Recent crictl releases warn when you rely on endpoint auto-detection instead of configuring it, so set it explicitly on nodes you expect to debug under pressure. Two structural differences from `docker ps` are worth stating out loud in an interview. **Sandboxes are first class.** CRI models a pod as a sandbox plus its containers, so `crictl pods` lists the sandboxes and `crictl ps` lists containers with the sandbox they belong to. A pod that fails before any application container starts shows up as a sandbox with nothing inside it - a state the docker CLI has no vocabulary for. **Much of docker's surface does not exist here.** There is no `crictl build`: the ImageService can pull, list and remove images, but building is not part of the CRI contract at all. There is no `crictl network` or `crictl volume` either - pod networking is configured by a CNI plugin when the sandbox is created, and volumes are mounted by the node agent from the pod spec, so the runtime holds no such objects to list. ## crictl is a debugger, not a manager Everything crictl shows is owned by the node agent, which continuously reconciles the node's containers against the pods assigned to it and garbage-collects whatever it does not recognise. `crictl rm` on a live container gets it recreated seconds later; `crictl rmi` on an image the node still wants forces a re-pull. Treat crictl as read-mostly: `pods`, `ps -a`, `logs`, `inspect`, `images`, `stats`. ## A worked example A chat-message fan-out service ships as a static Go binary in a `scratch` image - 11.4 MB, no shell, no coreutils. Three of its 14 replicas keep restarting on one node, and you have a root shell on that node but no cluster access: ```bash crictl pods --name fanout crictl ps -a --pod <pod-id> crictl logs --previous <container-id> crictl inspect <container-id> ``` `crictl exec -it <container-id> sh` is useless here, because the image contains no `/bin/sh`. That is exactly when `logs --previous` and `inspect` carry the investigation: the exit code, the resolved args and the mounts come from the runtime's own record of the container rather than from anything inside the image. ## nerdctl and ctr `nerdctl` is a docker-compatible CLI for containerd: `nerdctl ps`, `nerdctl run`, and, with BuildKit behind it, `nerdctl build` - something crictl cannot do at all. It works at containerd's own layer rather than through CRI, so it is namespace-scoped and defaults to the `default` namespace; `nerdctl ps` on a Kubernetes node prints an empty table until you say `nerdctl --namespace k8s.io ps`. `ctr` is containerd's own low-level client, equally namespace-scoped and deliberately unfriendly. The rule of thumb: reach for **crictl** when the question is 'what does the cluster's runtime think is running on this node', and for **nerdctl** when you want docker-like ergonomics against containerd itself.
- Why does crictl have no network or volume commands the way the docker CLI does?Because CRI does not model them. The contract covers sandbox and container lifecycle plus images, and nothing else: pod networking is configured by a CNI plugin at the moment the sandbox is created, and volumes are mounted by the node agent from the pod spec before the container starts. The runtime holds no network or volume objects, so there is nothing for a CRI client to list.
- Can you build an image on a node with crictl?No. CRI's ImageService can pull, list, inspect and remove images, but building is outside the contract entirely - there is no build RPC. If you must build on a node you need a builder such as BuildKit, through `docker buildx` or `nerdctl build`. The usual answer is that builds belong in CI, producing an image that nodes only ever pull.
- A colleague runs `nerdctl ps` on a node and sees nothing, yet pods are running. What is wrong?Nothing is broken - it is looking in the wrong containerd namespace. The Kubernetes node agent creates its containers in the `k8s.io` namespace, while nerdctl and ctr default to `default`. `nerdctl --namespace k8s.io ps` shows them. The same partition explains why Docker Engine, which uses the `moby` namespace, cannot see them either.
saying these in an interview costs you the question
- Says `docker ps` works because containerd is just Docker
- Thinks crictl can build images from a Dockerfile
- Uses `crictl rm` to clean up cluster-managed containers
- Assumes nerdctl's default namespace shows cluster containers
- Believes crictl talks to the cluster API server, not the runtime
- Expects `crictl exec sh` to work in a shell-less image