Docker 70 🐳 containerd CLI Integration: Interacting directly via ctr and nerdctl
Docker Engine has always been a layered system. When you run docker run, the CLI talks to dockerd, which delegates to containerd, which in turn uses runc to create the actual container process . The containerd layer is the core runtime: it manages images, snapshots, and container lifecycle, and it is the component that Kubernetes interacts with through the Container Runtime Interface. When you need to interact with containerd directly—without Docker in the path—you have two CLI options with very different personalities. ctr is the low-level debugging tool that ships with containerd itself. nerdctl is a Docker-compatible CLI designed for everyday use, with support for Compose, rootless mode, and image signing .
The choice between them is not about capability but about intent. ctr exposes the raw containerd API surface: namespaces, content stores, snapshots, and tasks. It is unsandboxed, not user-friendly, and explicitly documented as a debugging tool rather than a production interface . nerdctl wraps the same runtime with Docker-like commands, adds BuildKit integration for builds, and supports nerdctl compose up for multi-container applications . For a homelab or a development machine that has moved to containerd without Docker, nerdctl is the practical daily driver. For debugging why a container will not start or inspecting the content store, ctr is the tool that tells you what is actually happening inside the runtime.
Key point: ctr is the low-level debugging CLI bundled with containerd; nerdctl is a Docker-compatible CLI that provides a familiar workflow for everyday container operations, Compose support, and BuildKit builds, while still operating directly on containerd without Docker Engine.
Why both tools exist
The runtime layer problem. Docker Engine is a complete platform: it includes the CLI, the daemon, the image builder, volume management, and networking. containerd is the runtime component underneath it. When Docker was removed from Kubernetes as the default runtime (the dockershim removal in Kubernetes 1.24), the industry standardized on containerd as the CRI-compatible runtime . This means that on many systems today, containerd is running but Docker is not. The question becomes: how do you interact with the runtime when the Docker CLI is not installed?
The debugging versus daily-use distinction. ctr is shipped with containerd and is explicitly described as an “unsupported debug and administrative client” . It uses containerd‘s native namespaces, which are separate from Kubernetes namespaces and from each other. A container started with ctr run without specifying a namespace goes into the default namespace, and ctr ps in a different namespace will not see it. This is intentional: ctr is for operators who need to inspect the runtime’s internal state, not for developers who want to run a Redis container. nerdctl solves the daily-use problem by providing the Docker command surface (run, ps, exec, logs, build, compose) on top of the same containerd runtime .
The feature gap. nerdctl supports features that ctr lacks: port publishing (-p), restart policies, bridge networking, credential helpers from ~/.docker/config.json, logs, build, and compose up . These are the commands developers reach for every day. ctr has run and images pull, but it does not have -p for port mapping, and its container listing is namespaced in a way that confuses users expecting Docker behavior. The nerdctl documentation explicitly notes that ctr lacks equivalents for nerdctl run -p, nerdctl logs, nerdctl build, and nerdctl compose up .
The rootless and advanced features. nerdctl supports rootless containerd through containerd-rootless-setuptool.sh, lazy image pulling with Stargz and Nydus, encrypted images with ocicrypt, and P2P image distribution over IPFS . These are features that ctr does not expose at the same level of usability. For a homelab or a development environment that wants to experiment with these capabilities without Docker, nerdctl is the entry point.
The Kubernetes debugging angle. nerdctl --namespace k8s.io ps -a lists the containers that Kubernetes is running, because all Kubernetes containers are in the k8s.io containerd namespace . This gives you a Docker-like view of what the kubelet is doing without needing crictl, which is a separate CRI-specific tool. crictl is designed for CRI debugging and is maintained by the Kubernetes community, but it is not Docker-compatible and is focused on inspection rather than creation .
a. ctr: the low-level debugging client
ctr is installed automatically when you install containerd from the official release tarball. The binary is in the bin/ directory alongside containerd and the containerd-shim binaries . It connects to the containerd daemon over the Unix socket at /run/containerd/containerd.sock by default.
The first thing to understand about ctr is namespaces. Every operation happens within a namespace, and the default is default. If you start a container with ctr run and then list containers with ctr containers list, you are looking at the default namespace. Kubernetes containers live in the k8s.io namespace. You specify the namespace with --namespace or -n . This is a common source of confusion: a container that is running perfectly well is invisible if you are looking in the wrong namespace.
Pulling and running an image with ctr requires the full image reference, including the registry:
ctr images pull docker.io/library/redis:alpine
ctr run docker.io/library/redis:alpine redis
The ctr run command starts the container in the foreground and attaches to it. There is no -d flag in the Docker sense; ctr is not designed for detached container management. There is no port publishing. There is no restart policy. These are not oversights; they are reflections of ctr‘s purpose as a debugging tool .
Where ctr is useful is in inspecting the runtime’s internal state. ctr containers info <id> returns the full container specification. ctr tasks list shows the running tasks (a task is the runtime’s representation of a running container). ctr images list shows the images known to the runtime, and ctr content list shows the underlying content store. If a container fails to start, ctr events streams the runtime’s events, and ctr tasks info shows the task’s status and exit code. These commands are not user-friendly, but they are direct windows into what containerd is actually doing .
b. nerdctl: the Docker-compatible CLI
nerdctl is a separate binary that you install from its GitHub releases page. The nerdctl-full tarball includes the dependencies that nerdctl needs: CNI plugins for networking, BuildKit for building images, and RootlessKit for rootless mode . If you install only the nerdctl binary, you must provide those dependencies yourself.
The command surface is intentionally Docker-like:
nerdctl run -d --name web -p 8080:80 nginx:alpine
nerdctl ps
nerdctl logs web
nerdctl exec -it web sh
These commands behave as you expect from Docker. The -p flag publishes a port. The -d flag detaches. The ps command lists running containers, and logs streams the container’s stdout and stderr. This is the daily-use experience that ctr lacks .
nerdctl build uses BuildKit, which must be running as a separate daemon (buildkitd). If BuildKit is not installed and running, nerdctl build fails. This is a difference from Docker, where the builder is integrated into the daemon. The nerdctl-full tarball includes BuildKit, and the documentation recommends BuildKit v0.11.0 or later for full feature support .
nerdctl compose supports the Compose specification. You can run nerdctl compose up with a docker-compose.yaml file, and nerdctl will create the services, networks, and volumes defined in the file . This is the capability that makes nerdctl a viable replacement for Docker Compose in a containerd-only environment.
The Kubernetes integration is a practical feature for anyone debugging a cluster. nerdctl --namespace k8s.io ps -a lists the containers running in the Kubernetes namespace, with names that include the pod and container identifiers. nerdctl --namespace k8s.io logs <container-id> reads the logs of a Kubernetes container without needing kubectl or crictl . This is useful when you are on a node and want to see what a pod is doing without the API server.
Rootless mode is another differentiator. nerdctl can run containers as an unprivileged user when containerd is configured for rootless operation. The setup is containerd-rootless-setuptool.sh install, and after that, nerdctl run works without sudo . This is a security improvement for development environments and a capability that ctr does not match in usability.
c. Choosing between ctr, nerdctl, and the Docker CLI
The decision depends on what is installed and what you are trying to do. If Docker is installed and working, the Docker CLI is the right tool for development. It has the most complete feature set, the best documentation, and the broadest ecosystem support. The fact that Docker uses containerd underneath is an implementation detail that you do not need to manage.
If Docker is not installed and only containerd is present—which is common on Kubernetes nodes and on systems that have adopted the CRI-native runtime—then nerdctl is the practical daily driver. It provides the commands you expect, supports Compose, and integrates with BuildKit for builds. It is a non-core sub-project of containerd, maintained by the same community, and it is designed specifically to fill the gap that ctr leaves .
ctr remains the debugging tool. When nerdctl reports an error that you do not understand, ctr can show you the underlying container spec, the task state, and the runtime events. It is also the tool that is always present with containerd, so it is the one you can rely on when nothing else is installed. The limitation is that it is not designed for creating and managing application containers in the way developers expect.
crictl is the fourth tool in this space, and it is specifically for CRI debugging. It talks to any CRI-compatible runtime (containerd, CRI-O) and is maintained by the Kubernetes community. It is useful for inspecting pods and containers on a Kubernetes node, but it is not Docker-compatible and it does not support creating containers outside the kubelet’s control . For Kubernetes debugging, crictl is the standard; for general container work on a containerd host, nerdctl is the standard.
Complete Example Session
The following session demonstrates pulling and running a container with ctr, then the same operation with nerdctl, and shows the Kubernetes namespace listing.
# ============================================
# PART 1: PULL AND RUN WITH CTR
# ============================================
# Pull an image into the default namespace
sudo ctr images pull docker.io/library/nginx:alpine
# List images in the default namespace
sudo ctr images list
# Run a container (foreground, no port publishing)
sudo ctr run docker.io/library/nginx:alpine my-nginx
# In another terminal, list containers
sudo ctr containers list
# Output shows the container ID and image
# List running tasks
sudo ctr tasks list
# Output shows the task and its state
# ============================================
# PART 2: INSPECT WITH CTR
# ============================================
# Get the container ID from the previous list
sudo ctr containers info <container-id>
# Check the task status
sudo ctr tasks info <container-id>
# ============================================
# PART 3: PULL AND RUN WITH NERDCTL
# ============================================
# Pull the image (default namespace)
nerdctl pull nginx:alpine
# Run a detached container with port publishing
nerdctl run -d --name web -p 8080:80 nginx:alpine
# List containers
nerdctl ps
# View logs
nerdctl logs web
# Execute a command inside the container
nerdctl exec -it web sh
# Inside the container: exit
# ============================================
# PART 4: COMPOSE WITH NERDCTL
# ============================================
# Create a compose file
cat > docker-compose.yaml << 'EOF'
services:
web:
image: nginx:alpine
ports:
- "8081:80"
redis:
image: redis:alpine
EOF
# Start the stack
nerdctl compose -f docker-compose.yaml up -d
# List the stack containers
nerdctl compose -f docker-compose.yaml ps
# Stop the stack
nerdctl compose -f docker-compose.yaml down
# ============================================
# PART 5: KUBERNETES NAMESPACE
# ============================================
# On a Kubernetes node, list containers in the k8s.io namespace
sudo nerdctl --namespace k8s.io ps -a
# The output includes Kubernetes pods and their container IDs
# Read logs from a Kubernetes container
sudo nerdctl --namespace k8s.io logs <container-id>
# ============================================
# PART 6: CLEANUP
# ============================================
# Stop and remove the nerdctl container
nerdctl stop web
nerdctl rm web
# Remove the ctr container (find its ID first)
sudo ctr tasks kill <container-id>
sudo ctr containers remove <container-id>
# Remove the pulled images
nerdctl rmi nginx:alpine
sudo ctr images remove docker.io/library/nginx:alpine
The six parts cover ctr pull and run, ctr inspection, nerdctl run with port publishing and logs, nerdctl compose, the Kubernetes namespace listing, and cleanup. What the session demonstrates is that ctr gives you direct access to the runtime’s internal state, while nerdctl gives you the Docker workflow on top of the same runtime.
Quick Reference
Tool Comparison
| Capability | ctr | nerdctl | Docker CLI |
|---|---|---|---|
| Runtime | containerd | containerd | containerd via dockerd |
| Docker-compatible | No | Yes | Yes |
Port publishing (-p) | No | Yes | Yes |
Detached mode (-d) | No | Yes | Yes |
| Logs | No | Yes | Yes |
| Build | No | Yes (BuildKit) | Yes |
| Compose | No | Yes (nerdctl compose) | Yes |
| Rootless | Limited | Yes | Yes |
| Primary purpose | Debugging | Daily use | Development |
Common ctr Commands
| Command | Purpose |
|---|---|
ctr images pull <ref> | Pull an image |
ctr images list | List images in namespace |
ctr containers list | List containers |
ctr containers info <id> | Inspect a container |
ctr tasks list | List running tasks |
ctr tasks info <id> | Inspect a task |
ctr run <ref> <name> | Run a container (foreground) |
Common nerdctl Commands
| Command | Purpose |
|---|---|
nerdctl run -d -p 8080:80 nginx | Run detached with port |
nerdctl ps -a | List all containers |
nerdctl logs <name> | View logs |
nerdctl exec -it <name> sh | Shell into container |
nerdctl build -t myimage . | Build with BuildKit |
nerdctl compose up -d | Start Compose stack |
nerdctl --namespace k8s.io ps -a | List Kubernetes containers |
Namespace Behavior
| Tool | Default Namespace | Kubernetes Namespace |
|---|---|---|
ctr | default | k8s.io |
nerdctl | default | k8s.io |
| Docker | N/A | N/A |
Best Practices
✅ Do This:
sudo ctr images pull docker.io/library/nginx:alpine # ✅ full reference
sudo nerdctl run -d -p 8080:80 --name web nginx:alpine # ✅ Docker-like
sudo nerdctl --namespace k8s.io ps -a # ✅ debug K8s
nerdctl compose -f stack.yaml up -d # ✅ Compose stack
❌ Don’t Do This:
ctr run nginx # ❌ no full ref
nerdctl run -p 8080:80 nginx # ❌ without sudo if rootful
# expect ctr -p port mapping # ❌ not supported
# confuse ctr namespaces with Kubernetes namespaces # ❌ different concepts
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
ctr container invisible | Wrong namespace | Use --namespace flag |
ctr run no port access | ctr lacks -p | Use nerdctl or Docker |
nerdctl build fails | BuildKit not running | Start buildkitd or use full tarball |
| Kubernetes containers not listed | Wrong namespace | nerdctl --namespace k8s.io ps -a |
nerdctl requires sudo | Rootful containerd | Use rootless setup or sudo |
Real-World Examples
1. Pull with ctr
sudo ctr images pull docker.io/library/redis:alpine
2. Run with nerdctl
nerdctl run -d --name redis -p 6379:6379 redis:alpine
3. List containers with nerdctl
nerdctl ps -a
4. View logs with nerdctl
nerdctl logs redis
5. Compose stack with nerdctl
nerdctl compose -f docker-compose.yaml up -d
6. List Kubernetes containers
sudo nerdctl --namespace k8s.io ps -a
7. Inspect container with ctr
sudo ctr containers info <id>
8. Check task state with ctr
sudo ctr tasks list
9. Build with nerdctl
nerdctl build -t myapp:v1 .
10. Rootless run
nerdctl run -d -p 8080:80 nginx:alpine
Visual
The Runtime Stack
┌──────────────────────────────────────────────┐
│ DOCKER CLI │
│ │ │
│ ▼ │
│ dockerd (Docker daemon) │
│ │ │
│ ▼ │
│ containerd ──────────────────────────────┐ │
│ │ │ │
│ ├── ctr (debug CLI) │ │
│ ├── nerdctl (Docker-like CLI) │ │
│ │ │ │
│ ▼ │ │
│ runc (creates container process) │ │
└──────────────────────────────────────────────┘
ctr vs nerdctl Workflow
┌──────────────────────────────────────────────┐
│ CTR │
│ ctr images pull docker.io/library/nginx │
│ ctr run docker.io/library/nginx my-nginx │
│ ctr containers list │
│ (foreground, no ports, debugging) │
│ │
│ NERDCTL │
│ nerdctl run -d -p 8080:80 nginx:alpine │
│ nerdctl ps │
│ nerdctl logs web │
│ (detached, ports, logs, daily use) │
└──────────────────────────────────────────────┘
Namespace Separation
┌──────────────────────────────────────────────┐
│ containerd namespaces: │
│ ┌────────────────────────────────────────┐ │
│ │ default ← ctr/nerdctl default │ │
│ │ k8s.io ← Kubernetes containers │ │
│ │ moby ← Docker containers │ │
│ └────────────────────────────────────────┘ │
│ │
│ A container in "default" is invisible │
│ when listing "k8s.io" │
└──────────────────────────────────────────────┘
Compose with nerdctl
┌──────────────────────────────────────────────┐
│ docker-compose.yaml │
│ │ │
│ ▼ │
│ nerdctl compose up -d │
│ │ │
│ ▼ │
│ containerd creates services, networks, │
│ volumes from the Compose specification │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
ctr | Low-level debugging CLI, bundled with containerd |
nerdctl | Docker-compatible CLI for containerd |
| Docker CLI | Full platform, uses containerd via dockerd |
ctr port publishing | Not supported |
nerdctl port publishing | Supported (-p) |
nerdctl Compose | Supported (nerdctl compose) |
nerdctl build | Requires BuildKit |
| Kubernetes namespace | k8s.io |
ctr namespace | default |
| Rootless | nerdctl via containerd-rootless-setuptool.sh |
Key takeaways:
ctris a debugging tool, not a daily driver. It exposes the rawcontainerdAPI—namespaces, content stores, tasks—and lacks port publishing, detached mode, logs, and builds .nerdctlprovides the Docker workflow oncontainerd. It supportsrun -d -p,ps,logs,exec,build, andcompose up, making it the practical replacement for Docker CLI on acontainerd-only host .- Namespaces are separate.
ctrandnerdctloperate incontainerdnamespaces. Kubernetes containers are ink8s.io, Docker containers are inmoby, and the default isdefault. Listing the wrong namespace makes containers invisible . nerdctlneeds dependencies for full functionality. CNI plugins for networking, BuildKit for builds, and RootlessKit for rootless mode. Thenerdctl-fulltarball bundles them .ctris always present. It ships withcontainerd, so it is the tool you can rely on when nothing else is installed. Whennerdctlerrors,ctrshows the runtime state.crictlis for Kubernetes CRI debugging. It is not Docker-compatible and is focused on inspection, not container creation .
Remember: The shift from Docker to containerd does not mean losing the container workflow. It means choosing the right tool for the job. nerdctl gives you the Docker experience—run, ps, logs, compose, build—on top of the runtime that Kubernetes itself uses. ctr gives you the runtime’s internal perspective: what the content store holds, what the task state is, and why a container will not start. Use nerdctl for your daily work, and reach for ctr when you need to understand what is happening underneath. The Docker CLI remains the most complete development tool, but on a containerd-only system, nerdctl is the closest thing to it, and ctr is the safety net that is always there.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!