alias docker=podman

Nov 6, 2020

I’ve messed around with Podman every now and then and watched it’s development over the last few years, but haven’t used it extensively. With Podman’s release of version 2.1.0, I thought it would be a great time to explore the project further.

Podman logo

Podman is an open-source daemonless container engine alternative to Docker. We could simply substitute podman in for docker:

$ alias docker=podman

However, Podman also has a number of features that set it a part from Docker.

Daemonless

When running commands with the Docker CLI such as run, pull, and push, they are sent to the daemon process dockerd. Dockerd then in turn interacts with another daemon, containerD, which finally interacts with the Linux kernel through the runC container runtime process to make these commands happen.

Podman on the other hand interacts directly with the Linux kernel through runC and uses a fork-and-exec model, where the container processes are children of the Podman process. The absence of a Podman daemon results in increased container security as many (or all) of its commands do not have to be run by root and a UNIX socket is not open by default. Additionally, Podman’s lack of a daemon results in lower idle resource usage.

Registries

Podman defines fully qualified image names as consisting of a registry server, namespace, image name, and tag. When Podman is issued a pull command with an unqualified image, it tries to pull the image from an array of registries in order:

/etc/containers/registries.conf
...
# # An array of host[:port] registries to try when pulling an unqualified image, in order.
unqualified-search-registries = ['registry.fedoraproject.org', 'registry.access.redhat.com', 'registry.centos.org', 'docker.io']
...

Searching these registries is not necessary when using a fully qualified image name, resulting in faster image pulls, and is recommended for best security practices:

/etc/containers/registries.conf
...
# NOTE: RISK OF USING UNQUALIFIED IMAGE NAMES
# We recommend always using fully qualified image names including the registry
# server (full dns name), namespace, image name, and tag
# (e.g., registry.redhat.io/ubi8/ubi:latest). Pulling by digest (i.e.,
# quay.io/repository/name@digest) further eliminates the ambiguity of tags.
...

Fully Qualified Image Name

Example pulling the docker.io nginx image using a fully qualified image name:

$ podman pull docker.io/library/nginx:1.19

Digest

A fully qualified image name can also consist of a registry server, namespace, image name, and digest:

$ podman image inspect --format {{.Digest}} docker.io/library/nginx:1.19
sha256:1d61d07f97f25aa500cb02dec78237ae139c09f52989aaf856c3ba7135b3aa80
$ podman pull docker.io/library/nginx@sha256:1d61d07f97f25aa500cb02dec78237ae139c09f52989aaf856c3ba7135b3aa80

Rootless Containers

Podman utilizes cgroups v2 in order to be able to create, run, and build containers, and define networks as a non-privileged user (although root is still required for ip addresses).

I recently moved from Ubuntu to Fedora on my desktop systems and Podman is conveniently installed on a fresh Fedora 33 system (as well as RHEL and CentOS):

$ podman --version
podman version 2.1.1

Let’s start by running a trusty Nginx container as a non-privileged user on a Fedora 33 machine:

$ podman run -d -p 80:80 docker.io/nginx:1.19
Error: failed to expose ports via rootlessport: "cannot expose privileged port 80, you might need to add \"net.ipv4.ip_unprivileged_port_start=0\" (currently 1024) to /etc/sysctl.conf, or choose a larger port number (>= 1024): listen tcp 0.0.0.0:80: bind: permission denied\n"

Hmm that didn’t work. Port 80 is a privileged port and can’t be exposed on a rootless container. As an alternative we can bind the container’s port 80 to the host’s port 8080.

$ podman run -d --name nignx -p 8080:80 docker.io/nginx:1.19
$ podman rm -f nginx

This time we didn’t get any errors. Alright great, now what else can we do?

Pods

Podman can create and manage pods, a well known concept to those familiar with Kubernetes. Pods are groups of containers that share resources and are managed together. Containers within the same pod can communicate via localhost.

Let’s throw together a quick example of a multi-container pod:

$ podman pod create --name nginx_pod -p 8080:80
$ podman run -d --name nginx --pod nginx_pod docker.io/nginx:1.19 

Let’s break this down. First we created a new pod named nginx_pod and defined port mapping directly in the pod. When a pod is create, an infra container is also create and handles all pod networking specifications:

$ podman ps
CONTAINER ID  IMAGE                         COMMAND               CREATED         STATUS             PORTS                 NAMES
909a2eb0caec  docker.io/library/nginx:1.19  nginx -g daemon o...  23 seconds ago  Up 23 seconds ago  0.0.0.0:8080->80/tcp  nginx
480e7543c6d8  k8s.gcr.io/pause:3.2                                39 seconds ago  Up 23 seconds ago  0.0.0.0:8080->80/tcp  2196541d0bdc-infra
$ podman inspect 480e7543c6d8  
...          
"Ports": {
    "80/tcp": [
        {
            "HostIp": "",
            "HostPort": "8080"
        }
    ]
},
...

Now we’ll run a fedora container (within nginx_pod) and curl http://localhost to return the “Welcome to nginx!” page:

$ podman run -it --rm --pod nginx_pod fedora curl http://localhost
...
<p><em>Thank you for using nginx.</em></p>
...

Additional Security

Resource isolation and read-only mounts are features not exclusive to Podman, but because I haven’t covered them before, I thought now would be a great time to incldue them because of their respective increases in container security.

Resource Isolation

By default containers have all host cpu and memory resources available to them. These resources can be constrained with the following simple run flags:

podman run -d --name constrained --cpus .5 --memory 100M docker.io/nginx:1.19

The –cpus .5 flag sets the CPU limit to .5 for the container (–cpus is a short for the combination of -cpu-period and -cpu-quota). The –memory 100M flag sets a 100 megabyte memory limit for the container. Units can be b (bytes), k (kilobytes), m (megabytes), or g (gigabytes).

Read Only

By default a container’s root filesystem is mounted as read-write. The root filesystem can be mounted read-only with the –read-only run command. If a container is running in –read-only mode, then Podman mounts a read-write tmpfs on /run, /tmp, and /var/tmp automatically. These mounts are ephemeral and lost when the container is removed. All bind mounts remain read-write unless specified as read-only.

$ podman run -d -p 8080:80 --read-only docker.io/nginx:1.19

If systemd is running in your container then /var/log and /var/lib/systemd need to be mounted via tmpfs as well:

$ podman run -d -p 8080:80 --read-only --tmpfs /var/log --tmpfs /var/lib/systemd nginx

Podman Build

While Podman has a container building counterpart named Buildah, Podman can build container images just as easily as Docker can. The build files can either be named Dockerfile or Containerfile.

Just use podman build to get started:

$ podman build -t nginx .

Sneak Peak

That wraps up my introduction to Podman. Next time I’ll cover Podman’s ability to create containers from yaml files via the play kube command and my new project built to replicate Docker Compose using Podman and Python.