DEV Community

Sergio Méndez
Sergio Méndez

Posted on

Container Runtimes for Cloud Native Projects

Hi, readers this time I want to show you different tools that you can use to create containers. We are going to explore Podman, Containerd and Youki, some projects coming from CNCF. The idea of this blog post is to use the best tool for containers based on your needs, and share some basics about how to use this tools in your cloud native projects.

This blog post will focus on:

  • Container Runtimes including Podman, Containerd and Youki.

What you will learn

  • Container Runtime Introduction
  • Containerd installation
  • Create a container with the containerd Go SDK
  • Install nerdctl
  • Using Compose with nerdctl
  • Working with Podman
  • Podman Compose
  • Running Containers with youki

Container Runtime Introduction

For many developers, Docker is synonymous with containers. It
provides an excellent developer experience by combining image
management, container execution, networking, and a familiar CLI into a
single tool.

However, modern Cloud Native platforms especially Kubernetes have
evolved beyond Docker as their default runtime. Today, most Kubernetes
distributions rely on containerd, while alternative tools like
Podman and nerdctl provide different ways to build and manage
containers. Underneath them all, OCI runtimes such as runc and
youki are responsible for actually creating and running containers.

Understanding how these technologies work together is an important skill
for anyone working with Kubernetes, DevOps, or Platform Engineering.

Let's summarize some container tools as follows:

  • containerd: the container runtime used by most Kubernetes distributions.
  • nerdctl: A Docker-compatible CLI for containerd.
  • Podman: A daemonless container engine with first-class rootless support.
  • youki A modern OCI runtime written in Rust.

Let's see how this tools are distributed in different abstraction layers:

           Container CLI
      Docker CLI | nerdctl | Podman
                 │
          Container Engine
   Docker Engine | containerd
                 │
             OCI Runtime
            runc | youki
                 │
            Linux Kernel
Enter fullscreen mode Exit fullscreen mode

Each layer has a different responsibility:

  • Container Engine manages images, networking, and container lifecycle.
  • OCI Runtime creates and starts Linux containers.
  • CLI tools provide a user-friendly interface.

Containerd installation

Let's asume that we are using Alpine, you have to activate the community repositories in order to install containerd for this run the following steps:
1. Edit /etc/apk/repositories to uncomment the community repository

vi /etc/apk/repositories
# Uncomment the community repo for example the line will look like this
# http://dl-cdn.alpinelinux.org/alpine/v3.23/community
Enter fullscreen mode Exit fullscreen mode

2. Update the repositories

apk update
Enter fullscreen mode Exit fullscreen mode

3. Install containerd

apk add containerd
Enter fullscreen mode Exit fullscreen mode

4. Start containerd

service containerd start
containerd --version
Enter fullscreen mode Exit fullscreen mode

Create a container with the containerd Go SDK

1. Install Go in Alpine

apk add git
apk add go
Enter fullscreen mode Exit fullscreen mode

2. Create a directory to create your go code

cd $HOME
mkdir containerd-example
cd containerd-example
go mod init containerd/example
Enter fullscreen mode Exit fullscreen mode

3. Install Containerd libreries to compile your example

go get github.com/containerd/containerd
go get github.com/containerd/containerd/cio
go get github.com/containerd/containerd/oci
go get github.com/containerd/containerd/namespaces
Enter fullscreen mode Exit fullscreen mode

4. Create the code as main.go file using the next content

package main

import (
    "context"
    "fmt"
    "log"
    "syscall"
    "time"

    "github.com/containerd/containerd"
    "github.com/containerd/containerd/cio"
    "github.com/containerd/containerd/oci"
    "github.com/containerd/containerd/namespaces"
)

func main() {
    if err := redisExample(); err != nil {
        log.Fatal(err)
    }
}

func redisExample() error {
    // create a new client connected to the default socket path for containerd
    client, err := containerd.New("/run/containerd/containerd.sock")
    if err != nil {
        return err
    }
    defer client.Close()

    // create a new context with an "example" namespace
    ctx := namespaces.WithNamespace(context.Background(), "example")

    // pull the redis image from DockerHub
    image, err := client.Pull(ctx, "docker.io/library/redis:alpine", containerd.WithPullUnpack)
    if err != nil {
        return err
    }

    // create a container
    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)

    // create a task from the container
    task, err := container.NewTask(ctx, cio.NewCreator(cio.WithStdio))
    if err != nil {
        return err
    }
    defer task.Delete(ctx)

    // make sure we wait before calling start
    exitStatusC, err := task.Wait(ctx)
    if err != nil {
        fmt.Println(err)
    }

    // call start on the task to execute the redis server
    if err := task.Start(ctx); err != nil {
        return err
    }

    // sleep for a lil bit to see the logs
    time.Sleep(3 * time.Second)

    // kill the process and get the exit status
    if err := task.Kill(ctx, syscall.SIGTERM); err != nil {
        return err
    }

    // wait for the process to fully exit and print out the exit status

    status := <-exitStatusC
    code, _, err := status.Result()
    if err != nil {
        return err
    }
    fmt.Printf("redis-server exited with status: %d\n", code)

    return nil
}
Enter fullscreen mode Exit fullscreen mode

Install nerdctl

1. Install nerdctl to use containerd

apk add nerdctl
nerdctl --version
Enter fullscreen mode Exit fullscreen mode

2. Use nerdctl to create a container

nerdctl run -d nginx
Enter fullscreen mode Exit fullscreen mode

Using Compose with nerdctl

nerdctl compose up -d
nerdctl compose ps
nerdctl compose logs -f
nerdctl compose build
nerdctl compose down
Enter fullscreen mode Exit fullscreen mode

Working with Podman

1. Install Podman

apk add podman
Enter fullscreen mode Exit fullscreen mode

2. Create a container using Podman

podman run -it -d redis
Enter fullscreen mode Exit fullscreen mode

3. List containers with Podman

podman ps
Enter fullscreen mode Exit fullscreen mode

Podman Compose

podman compose up -d
podman compose ps
podman compose logs -f
podman compose down
Enter fullscreen mode Exit fullscreen mode

Running Containers with youki

1. Install youki dependencies

apk add libseccomp curl

curl -sSfL https://github.com/youki-dev/youki/releases/download/v0.6.0/youki-0.6.0-$(uname -m)-musl.tar.gz | tar -xzvC /usr/bin/ youki

youki --version # Test youki installation
Enter fullscreen mode Exit fullscreen mode

2. List containers created by youki

podman run --runtime /usr/bin/youki -d -p 8080:80 nginx
youki list
Enter fullscreen mode Exit fullscreen mode

Conclusion about Container Runtimes

The Cloud Native ecosystem has become increasingly modular.
Understanding how containerd, nerdctl, Podman, and youki
work together helps you better understand how modern container
platforms and Kubernetes itself run workloads in production.

Thanks for reading! Last to say, see you in my next blog post.

Follow me

These are my social networks:

https://www.linkedin.com/in/sergioarmgpl
https://sergiops.xyz
https://x.com/sergioarmgpl
https://www.instagram.com/sergioarmgpl/

Please contribute to these awesome projects:

Top comments (0)