Cloud Native 12 min read

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.

Raymond Ops
Raymond Ops
Raymond Ops
Mastering containerd: Architecture, Installation, and Go Demo

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

containerd architecture diagram
containerd architecture diagram

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-release

and

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 service status
containerd service status

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>
demo output
demo output

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.

DockerKubernetesGoLinuxcontainerdcontainer runtime
Raymond Ops
Written by

Raymond Ops

Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.