Mastering containerd: Architecture, Installation, and Go Demo
This article explains what containerd is, why it is needed, its technical goals and architecture, provides step‑by‑step installation on Ubuntu, shows how to configure it as a system service, and demonstrates a complete Go program that creates, runs, and cleans up a Redis container using the containerd client library.
Why containerd is needed
Containerd is an industrial‑grade container runtime that emphasizes simplicity, robustness, and portability. It manages the full lifecycle of containers on the host, including image transfer and storage, container execution, storage, and networking.
Manage container lifecycle (creation to destruction)
Pull/push container images
Storage management (images and container data)
Invoke runC to run containers (interact with other runtimes)
Manage container network interfaces and networking
Note: Containerd is designed to be embedded in a larger system, not used directly by developers or end users.
Reasons for a separate containerd
Extracted as an independent project from the Docker engine (open‑source approach)
Can be used by projects such as Kubernetes CRI (standardization)
Lays the foundation for broad industry collaboration (similar to runC)
Technical direction and goals
Simple gRPC‑based API and client library
Full OCI support (runtime and image specifications)
Well‑defined core container functions with stability and high performance
Decoupled system allowing plugins for image, filesystem, and runtime extensions
Install and run containerd
We will install the latest containerd on Ubuntu 16.04. First, install runC (required by containerd).
Download and extract containerd
<code>$ sudo tar -C /usr/local -xf containerd-1.1.0.linux-amd64.tar.gz</code>The package installs five files into
/usr/local/bin:
containerd: the runtime providing OCI‑compliant gRPC API
containerd-releaseand
containerd-stress: release and stress‑test tools
containerd-shim: per‑container shim used by Docker
ctr: simple CLI for debugging containerd
Generate default configuration
<code>$ sudo su
$ mkdir /etc/containerd
$ containerd config default > /etc/containerd/config.toml</code>The default configuration is sufficient for the demo.
Create a systemd service
<code>[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target
[Service]
ExecStartPre=/sbin/modprobe overlay
ExecStart=/usr/local/bin/containerd
Delegate=yes
KillMode=process
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity
[Install]
WantedBy=multi-user.target</code>Enable and start the service:
<code>$ sudo systemctl daemon-reload
$ sudo systemctl enable containerd.service
$ sudo systemctl start containerd.service
$ sudo systemctl status containerd.service</code>Containerd is now installed successfully.
Demo
Using the Go client library, we connect to the containerd daemon, pull a Redis image, create a container, and run it.
Connect to containerd
<code>package main
import (
"log"
"github.com/containerd/containerd"
)
func main() {
if err := redisExample(); err != nil {
log.Fatal(err)
}
}
func redisExample() error {
client, err := containerd.New("/run/containerd/containerd.sock")
if err != nil {
return err
}
defer client.Close()
return nil
}</code>Set a namespace to isolate resources:
<code>ctx := namespaces.WithNamespace(context.Background(), "demo")</code>Pull Redis image
<code>image, err := client.Pull(ctx, "docker.io/library/redis:alpine", containerd.WithPullUnpack)
if err != nil {
return err
}</code>Create OCI spec and container
<code>container, err := client.NewContainer(
ctx,
"redis-server",
containerd.WithImage(image),
containerd.WithNewSnapshot("redis-server-snapshot", image),
containerd.WithNewSpec(oci.WithImageConfig(image)),
)
if err != nil {
return err
}
defer container.Delete(ctx, containerd.WithSnapshotCleanup)</code>Create and run task
<code>task, err := container.NewTask(ctx, cio.NewCreator(cio.WithStdio))
if err != nil {
return err
}
defer task.Delete(ctx)
// let the container run for a while
time.Sleep(3 * time.Second)
if err := task.Kill(ctx, syscall.SIGTERM); err != nil {
return err
}
status := <-exitStatusC
code, _, err := status.Result()
if err != nil {
return err
}
fmt.Printf("redis-server exited with status: %d\n", code)</code>Build and run the demo:
<code>$ go build main.go
$ sudo ./main</code>Conclusion
As container technologies become standardized, containerd will play a crucial role in the stack, providing core services that are likely to become the de‑facto standard for managing containers, enabling higher‑level platforms to build directly on its foundation.
Raymond Ops
Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.