Docker vs Podman - Introduction to modern app containers

2021-04-29

(last time edited: 2021-07-26)

tags: podman, docker, containers

"You are absolutely deluded, if not stupid, if you think that a worldwide collection of software engineers who can’t write operating systems or applications without security holes, can then turn around and suddenly write virtualization layers without security holes." - Theo de Raadt, OpenBSD project lead

Docker

Docker is a set of platform as a service (PaaS) products that use OS-level virtualization (cgroups, namespaces) to deliver software in packages called containers. Containers are isolated from one another and bundle their own software, libraries and configuration files; they can communicate with each other through well-defined channels. Because all of the containers share the services of a single operating system kernel, they use fewer resources than virtual machines.

Containers exist since long ago since the first time chroot appeared in 1979.

Containers are nothing new, but they surely advanced a lot. Containers are basically chroot on steroids. Lots of new features are popping everyday. Nowadays it's easier than ever to deploy software, to manage versioning, to share software, and they are even more secure than ever.

In Docker you have can have images of anything, such as an Irssi image or Ubuntu image, busybox tools, an scratch linux image, a winblows nanoserver or any binary such as a hello world, program or shellscripts you may think of. You can find almost anything. Some software are good to run on Docker for multiple reasons. It really depends on what you're trying to achieve, if it's just for personal use or for a corporate infrastructure.

You can find images in the Docker Hub. These images can be used in any system with any architecture as long you have Docker/Podman installed. With these images you can create containers, and you can manage these containers as you like, you can even make your own images by creating a Containerfile or most commonly known as Dockerfile.

There is no need to worry what happens to your host machine or to your container. Containers live isolated from your OS so you can experiment freely. The layers of abstractions, namespaces and permissions help building security. Well NOT REALLY. Vulnerabilities and exposures appear from time to time. That's life my man.

The service and software industry current use Docker to share resources and platforms on the go. They find it very simple to launch complete services with only one commandline. Fast, simple, and money comes bling bling. Containers are more common than you expect, websites are being containerized, APIs, FTPs, messaging servers, etc. Containerization also very helpful at maintaining different versions of software too, this helps with development a lot and many DevOps teams use it.

I do not recommend using Docker if you are a beginner sysadmin. While containers make things look very easy they can hide many things from you at plain sight. They will make you feel successful without learning anything and this is a bad practice. I recommend first learning how to administrate services using systemd, runit or OpenRC, managing iptables, namespaces, uids, guids, kernel commandline, etc.

Do not confuse Docker with software like Snap or Flatpak or AppImage. Docker has nothing to do with those 3, but they slightly share the same idea of making software somewhat universal to run it across all architectures without problem. Docker is a whole lot oriented to services while the other are just desktop package solutions. I use Docker when I need some software that is not available for my Void system. Or use programs that are a pain in the ass to setup manually with Wine such as MTGO.

The only downside of Docker is the big daemon running as root. Also don't forget, do not confuse containers with virtual machines! But we can say these modern containers for applications are somewhat related to virtualization.

And do not confuse application containerization with system containerization. Docker and Podman serve their purpose to run 1 process per container. While other solutions like LXC and LXD serve their purpose to isolate whole new systems.

For an extensive information on Docker take a look at the docs webpage.

Basic usage

First start Docker as a service, add your user to the docker group and relog into your account.

# usermod -G docker -a <user>

Download an image.

$ docker pull hello-world

Show all images.

$ docker images

REPOSITORY      TAG     IMAGE ID        CREATED     SIZE
hello-world     latest  d10dm29djalw    1 week ago  13.3kB

This command creates a container out of an image and runs it in the background. Once the container processed the execution, it will stop.

$ docker run -tid <image-name>

-t to allocate in a tty. (useful for programs that need to be running all the time)

-i to get interactive shell. (important command if you wanna get into the container and modify settings)

-d to detach. (runs the containerized process in the background)

This command downloads a new Fedora image and runs the echo program found inside the container you specified.

$ docker run fedora echo hi

Look at the current status of all created containers.

$ docker ps -a

Attach terminal to a running container.

$ docker attach container_id

Detach from a container terminal without exiting the container process by sending a signal with keypresses:

Press Ctrl+P followed by Ctrl+Q.

Remove a container.

$ docker rm <container-id>

Force remove using -f argument.

Remove an image.

$ docker rmi <image-id>

-f is optional too.

Stop a container.

$ docker stop <container-id>

Rename a container.

$ docker rename <container-id> <new-name>

Rename an image by first duplicating it using tag.

$ docker tag <old-name> <new-name>

Then remove the old tagged image.

$ docker rmi <old-name>

By default Docker saves everything inside /var/lib/docker. If you wanna change the default directory create a new file using root permissions.

# touch /etc/docker/daemon.json

And add this following text.

{
    "graph":"/home/youruser/docker"
}

You will see a newly created directory when the Docker service starts running.

Now you know how to manage services with Docker.

But I don't recommend using Docker, let's learn how to use Podman!

blogimg

Podman

Podman is an open-source project that is available on most Linux platforms and resides on GitHub. Podman is a daemonless container engine for developing, managing, and running Open Container Initiative (OCI) containers and container images on your Linux System. Podman provides a Docker-compatible command line front end that can simply alias the Docker cli, alias docker=podman.

Containers under the control of Podman can either be run by root or by a non-privileged user. Podman manages the entire container ecosystem which includes pods, containers, container images, and container volumes using the libpod library. Podman specializes in all of the commands and functions that help you to maintain and modify OCI container images, such as pulling and tagging. It allows you to create, run, and maintain those containers created from those images in a production environment.

Podman is very similar to Docker but better! Podman uses the same CLI syntax and also the same conventions and standards from the Open Container Initiative (OCI) so you don't have to relearn a new program. systemd inside containers is a possibility, even though I don't recommend it. And containers can run rootless and without a background daemon which makes it better than Docker. Here is an amazing read on rootless systemd inside containers. Here is a good explanation of use cases for systemd inside containers.

And here's a nice explanation why we should use --user argument for rootless containers to add more security layers, even though this practice requires a little more tinkering.

Again, rootless is incredibly important as all images and container data will be saved in user directories with user permissions for more security. Also there's no need to worry about changing default storage directories in comparison to Docker.

Click here for further documentation on rootless mechanism to make it work correctly.

Prerequisites for rootless Podman

To run rootless Podman it's absolutely necessary to have control croup v2 (cgroup v2) enabled in your host kernel. If you run into lots of trouble trying to set up rootless containers with Podman, look here. More info on rootless can be found here.

control group is a kernel mechanism that isolates, measures, and controls the distribution of resources for a collection of processes on a server, such as CPU, memory, I/O, etc. Here is another interesting read on cgroup v2.

Check if your kernel can use cgroup v2. cgroup v2 was adopted in the Linux kernel version 3.16 in Aug 3, 2014 so your kernel probably supports it. Anyways, you should double check in case your kernel is old as shit, WHICH I DOUBT.

$ grep cgroup /proc/filesystems

If your kernel supports it you should see.

nodev cgroup
nodev cgroup2

Disable cgroup v1 completely by adding cgroup_no_v1=all in your kernel commandline. You need to restart the system aftwards.

If you use ZBM it's usually done this way.

# zfs set org.zfsbootmenu:commandline="spl_hostid=$(hostid) cgroup_no_v1=all ro quiet zroot/ROOT

If you use GRUB.

# vim /etc/default/grub

#...
#...

GRUB_CMDLINE_LINUX_DEFAULT="cgroup_no_v1_all loglevel=4"

#...
#...

# update-grub

And then you should open the rc.conf file.

# vim /etc/rc.conf

and add the following text...

In Void Linux:

CGROUP_MODE=unified

In Alpine Linux:

rc_cgroup_mode=unified

Now your init system (runit or OpenRC) will run properly on cgroup v2 mode only. This is recommended, otherwise you will see many ugly error messages on boot of runit conflicting with cgroup v1.

Container runtime

Some older versions of the runc program do not work with cgroup v2. At this date runc 1.0.0 is believed to work perfectly with cgroups v2 if you have them enabled.

runc is a program dependency of Podman written in Go. The runc name stands for run containers. Yes, it's the program that takes care of running containers, duh. If you have Podman installed then you probably have runc installed too. runc follows OCI specification too. Linux distributions, for example Ubuntu, will not serve runc as default runtime but crun.

There is an alternative to runc called crun which is written in C. There isn't much to say about crun, but I prefer using it because it's faster and lightweight. But you won't notice any difference if your programs aren't scaled big. You can read an introduction to crun in the Red Hat sysadmin blog post.

If you wanna doublecheck your cgroups version and more info.

$ podman info

Podman containers configuration

You can opt to use crun or runc by specifying the runtime in a custom file. Of course you need them installed in your system.

$ touch ~/.config/containers/containers.conf

And add the following settings.

[engine]
cgroup_manager = "cgroupfs"
runtime = "crun"

[containers]
init = false
cgroups = "enabled"
ipcns = "private"
netns = "private"
pidns = "private"
utsns = "private"
cgroupns = "private"

Podman storage configuration

Create a new configuration file in your ~/.config/containers directory.

$ touch ~/.config/containers/storage.conf

And add the following settings.

[storage]
driver = "overlay"

[storage.options]
mount_program = "/usr/bin/fuse-overlayfs"

Podman registries configuration

Registries are the domains where you're gonna pull the containers from. It's recommended to add a personal configuration and manage them.

$ touch ~/.config/containers/registries.conf

And add the following settings.

[registries.search]
registries = ['docker.io', 'example_someotherdomain', 'woooot_otherdomain' ]

All the images you pull will be downloaded from docker.io and/or any other domain you add.

Pull an image

Any image you pull (download) will be ready to use in the multiple builds you want to create.

$ podman pull fedora

Build an image

Build a custom image that prints hello world. This image you are building will use the previously pulled image so you don't have to download it again! Amazing isn't it? This is one big and important feature of containers you will see constantly.

FROM fedora
CMD echo hello world

Save that text as a new file called Containerfile.

And then build the image pointing the -f argument to the Containerfile. Containers are named randomly unless you specify it in the build process.

$ podman build -f /path/to/Containerfile

or assign a name.

$ podman build -f /path/to/Containerfile -t <some-name>

Run an image

When you run an image, you are creating a container, therefore it's a process that starts and may or not, end in the same moment.

This simple command creates a container based on the image you specify.

$ podman run <image-name>

Run image with interactive tty. -i for interactive. -t for tty.

$ podman run -it <image-name>

or the same but leave process in the background. -d is for detach.

$ podman run -itd <image-name>

If you wanna assign a name to the container you must add --name.

$ podman run -itd <image-name> --name <container-name>

Containers are created forever unless you remove them.

If you want to remove it after exit you must add --rm. This is a nice practice when you are testing different ways to launch a container, or if you don't plan to keep the container for later.

$ podman run -it --rm <image-name>

Listing

List images.

$ podman images

List all images. This includes all commits.

$ podman images -a

List containers.

$ podman ps

List all containers. This includes all containers that are technically "defunct" and not in use. They remind me of zombie processes in a way.

$ podman ps -a

Start

Start a container that has been stopped or ended its process.

$ podman start <container-name>

In this example we will run 'hello world' again.

$ podman start <fedora_container_that_says_hello>

Attach

Attach to a container that has been initialized with the -i and -t arguments. The container must be running otherwise it won't attach.

$ podman attach <container-name>

Execute

Force a shell login to a container without conflicting with any process. Calling the bash binary for simpler usage.

$ podman exec -it <container-name> bash

You can login to a container as any user you want by adding the --user argument.

$ podman exec -it --user root <container-name>

Pods

Optional, when you have many containers, you can create pods that store containers.

$ podman create pod

$ podman pod list

$ podman pod ps

To run a container inside a pod. A pod is a collection of containers.

$ podman run -tid --pod <pod-id> <my-container-name>

If you wanna enable verbose add this to every command. --log-level debug

Information about Podman

For more information on your system running Podman you can do.

$ podman info --debug

Logs

To get logs in real time of what's happening in the container just point to it with this argument.

$ podman logs -f <container-name>

The -f argument is important.

Removing containers

Remove a container.

$ podman rm <container-name>

Remove all containers.

$ podman rm -a

Remove all containers and force removal.

$ podman rm -af

Removing images

Remove an image.

$ podman rmi <image-name>

Remove all images.

$ podman rmi -a

Remove all images and force removal.

$ podman rmi -af

Reset

This command resets Podman completely. It will not remove configuration stored in in XDG_CONFIG_HOME (usually ~/.config).

$ podman system reset

If by some reason you cannot reset this way, try force removal on images and containers. And then under root remove all rootless containers created by Podman in your user account.

# rm -rf /home/your_user/.local/share/containers

Build & Run

There is no possible way to build a custom Containerfile and run it in the same command. It's not something Podman developers want you to do. However you can try the next command and achieve similar results:

$ podman build -t <my-image-name> -f Containerfile && podman run -it my_image_name

Personal most common used images

Python images

Python tags to choose from.

Latest release.

FROM docker.io/library/python:latest

Specific 3.7 image.

FROM docker.io/library/python:3.7

Alpine Linux images

Alpine tags to choose from.

Stable.

FROM docker.io/library/alpine:latest

Unstable.

FROM docker.io/library/alpine:edge

Void Linux images

Void tags to choose from.

Glibc.

FROM ghcr.io/void-linux/void-linux:latest-mini-bb-x86_64

Musl.

FROM ghcr.io/void-linux/void-linux:latest-mini-bb-x86_64-musl 

Node.js images

Latest release.

FROM docker.io/library/node:latest

LTS version 12.

FROM docker.io/library/node:erbium

Arch Linux images

Latest release.

FROM docker.io/library/archlinux:latest

Good for compiling.

FROM docker.io/library/archlinux:base-devel

blogimg