Replace Docker with Podman and BuildahJanne Kemppainen |
Since Docker announced that the licensing terms for Docker Desktop have changed so that large companies need to start paying there has been growing interest towards alternatives. How can you switch to a free and open source solution, or should you?
What does the Docker licensing change actually mean?
On Linux, Docker is still very much free and open source. The Docker Engine is the component that actually handles running and managing the containers. If you’re on Linux, then nothing really changed for you since you’re using Docker Engine.
On Windows and Mac, however, there are licensing implications. While Docker Engine is open source, Docker Desktop is not. Businesses with fewer than 250 employees and less than $10 millioin in annual revenue can continue to use the product for free, while bigger companies need to obtain a paid license for each user of the software. If you’re in the latter group you basically have two options:
- pay the license, or
- look for other solutions.
The real problem here is that the underlying technology requires a Linux kernel in order to run the containers. On Windows and Mac this needs to be provided through virtualization, and Docker Desktop has been the easy way to get everything up and running with transparent integrations between the host OS and the virtual machine. Basically, Docker Desktop handles it so that you don’t really need to worry about the hidden VM.
There is no way to run Docker Engine natively on Windows or Mac. You could set up a separate Linux virtual machine but that can get tiresome. Installing Docker Engine on Windows Subsystem for Linux can be complicated.
What are Podman and Buildah?
Podman stands for Pod Manager, a container engine meant for running Open Container Initiative (OCI) compliant containers on Linux. It aims to be a drop-in replacenment for Docker, so you could alias
podman, and everything should just work.
Buildah is a tool that specialises in building OCI container images. The two projects complement each other, and Podman actually uses Buildah under the hood when you use it to build an image. Still, Buildah is completely independent and can be used separately if you just need to build containers.
They are both open source projects and available for many Linux distributions. Just like Docker, Podman is a tool for running Linux containers, so it doesn’t run natively on other OS’es. On macOS the
podman machine command can handle setting up the needed virtual machine. On Windows you can run Podman inside Windows Subsystem for Linux (WSL2).
Podman doesn’t just reimplement Docker. The design philosophy is actually quite different from the Docker approach.
Docker Engine runs a daemon process (
dockerd) which is a service that constantly runs in the background, managing all the containers on the host. The
docker command that you use to perform container actions is actually a client application that talks to the daemon process over a REST API. So when you execute
docker run, for example, the
docker command actually calls
dockerd via the API to start a new container.
Podman, on the other hand, is daemonless; it doesn’t run a separate service process. When you run a podman command it talks directly to the image registry or the needed Linux kernel processes without any process in the middle. This is potentially more secure since you don’t need root access for running a daemon. It also gets rid of the single point of failure since each container runs as an independent process.
The installation method depends on your operating system. The official detailed instructions can be found from the Podman documentation, but the essentials are also included here.
On Linux you can install
podman normally using your package manager. You may need to enable additional repositories for these commands to work, in which case please refer to the official documentation. Here’s the quick reference:
sudo pacman -S podman
sudo yum -y install podman
Debian, Ubuntu, Raspberry Pi OS:
sudo apt-get -y install podman
sudo dnf -y install podman
sudo emerge app-emulation/podman
sudo zypper install podman
sudo subscription-manager repos --enable=rhel-7-server-extras-rpms sudo yum -y install podman
sudo yum module enable -y container-tools:rhel8 sudo yum module install -y container-tools:rhel8
First, you will need to install Homebrew if you don’t already have it installed.
Then, the installation steps are as follows:
$ brew install podman $ podman machine init $ podman machine start $ podman info
These commands install the
podman command and initialize the virtual machine that is needed to run the containers. This works in a similar way to the Docker Machine that we previously had to use with Docker on Mac.
On Windows you have two alternatives. The first option is to install the Podman remote client and use it to connect to a Linux VM or other host through the network. Alternatively, you can follow these instructions to install Podman with Windows Subsystem for Linux (WSL2).
The prerequisite for this method is that you have already installed and configured WSL.
The first thing that you need to check is that you’re actually running WSL2, and not version 1. Open a PowerShell window and list your WSL environments:
$ wsl --list --verbose NAME STATE VERSION * Ubuntu Running 1
Switch to version two, if needed:
$ wsl --set-version Ubuntu 2 Conversion in progress, this may take a few minutes... For information on key differences with WSL 2 please visit https://aka.ms/wsl2 Conversion complete.
If you haven’t installed a distribution yet, you can set version 2 as the default before setting one up:
$ wsl --set-default-version 2
Podman is available in the default package repository starting from Ubuntu version 20.10. I have written instructions for upgrading Ubuntu on WSL if you’re using an older version. Assuming that you have a recent enough OS version, Podman can be installed with:
$ sudo apt install podman
Check that the installation works:
$ podman info
Both Podman and Buildah can be used to build container images. So which one should you use?
Podman supports a subset of Buildah features for image building, so the decision may depend on the complexity of your use case, or your preferences. Since Podman tries to be a drop in replacement of Docker it’ll probably meet your current needs.
If you’re migrating from the Docker world it’s really easy to switch to Podman as the command stays essentially the same:
$ podman build
The command follows the instructions in your Dockerfile to produce an OCI compliant container image, so nothing really changes from your point of view.
With Buildah you need to use the
buildah bud command instead. It stands for build-using-dockerfile. This emulates the way Docker implemented image builds and also works with Dockerfiles.
If you want to get rid of the Docker nomenclature altogether you can rename your
Let’s create a minimal Python API as an example. The Python dependencies are listed in this
The API itself is implemented in
from fastapi import FastAPI app = FastAPI() @app.get("/hello") def hello(): return "Hello world!"
Containerfile could then look like this.
FROM python:alpine WORKDIR /app COPY . /app RUN python3 -m pip install -r requirements.txt CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "5000"]
If you now run
buildah bud -t my-api or
podman build -t my-api you should get a working container image. It will be included in the list of images:
$ buildah images REPOSITORY TAG IMAGE ID CREATED SIZE localhost/my-api latest 5037cd222bc7 18 seconds ago 127 MB docker.io/library/python alpine 2c167788a673 2 days ago 50.8 MB
Dockerfiles (or Containerfiles) are not the only supported way to create container images with Podman and Buildah. You can alternatively generate images by running a sequence of command line commands that modify an intermediate container.
In principle, build commands are similar to using Dockerfiles. You start with a base image and then you add your own changes on top of it. The difference is that you have full control over the build process, and you can add verifications between steps. Since Docker builds always go from start to finish you cannot really inspect the intermediate steps when developing a container image.
Let’s reproduce the previous Python API example using build commands. It all starts with the
buildah from command. We need to choose which base image to use and then store the command output to a variable so that we can easily reference the working container in later commands.
$ container=$(buildah from python:alpine)
The container name has been captured in a variable called
container. We can use echo to see what it is:
$ echo $container python-working-container
It should also become visible in the list of Buildah containers:
$ buildah containers CONTAINER ID BUILDER IMAGE ID IMAGE NAME CONTAINER NAME b6df4a492e9e * 2c167788a673 docker.io/library/python:alpine python-working-container
buildah config command can be used to update the container settings. Let’s set the working directory to
/app as we did with the Containerfile.
$ buildah config --workingdir /app $container
Next, copy the application files to the container with
buildah add. Since we have already configured the working directory we don’t need to specify the file destination.
$ buildah add $container requirements.txt $ buildah add $container main.py
The add command works with files, URLs and directories. If you specify multiple source files then you also have to specify the destination. File archives are automatically extracted. The
buildah copy command works in a similar way.
Now that the application files are in place we can call
buildah run to install the needed Python dependencies.
$ buildah run $container -- python3 -m pip install -r requirements.txt
We can set the startup command with another
buildah config call.
$ buildah config --cmd '["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "5000"]' $container
Finally, write the changes to a new image with
$ buildah commit $container my-other-api Getting image source signatures Copying blob 4fc242d58285 skipped: already exists Copying blob fbd7d5451c69 skipped: already exists Copying blob 16e3ab2d4dee skipped: already exists Copying blob 0b800261971d skipped: already exists Copying blob b02dd59d34c0 skipped: already exists Copying blob 287a6fbe8473 done Copying config 7843c15707 done Writing manifest to image destination Storing signatures 7843c1570770c84558e238ee6fcb9658bde8b4b317073497e0eb8f62e636b4d4
The new image is now listed.
$ buildah images REPOSITORY TAG IMAGE ID CREATED SIZE localhost/my-other-api latest 7843c1570770 29 seconds ago 127 MB localhost/my-api latest 5037cd222bc7 3 hours ago 127 MB docker.io/library/python alpine 2c167788a673 2 days ago 50.8 MB
Running containers with Podman shouldn’t cause any surprises if you’re familiar with Docker so in essence this is also a crash course to Docker command basics. The images that are built with Buildah are automatically visible for Podman.
Our example API listens on port 5000 so the only thing we need to do is to configure the correct port forwarding settings when calling
podman run. I’m also giving it a name so that the container is easier to reference later on.
$ podman run -p 5000:5000 --name hello-api my-api INFO: Started server process  INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:5000 (Press CTRL+C to quit)
You should see the startup messages printed on the console to see that the application started successfully. If you navigate to http://localhost:5000/hello on your web browser you should see a friendly response from the API. You can also check out http://localhost:5000/docs to see the automatically generated OpenAPI documentation for your app. Press
Ctrl+C to stop the container.
As I already mentioned, you can pretty much replace the
docker command with
podman and it should just work. For example, you can start the container in the background by adding the detached flag
An existing container can be started with
podman start and stopped with
podman stop, it starts automatically in the background.
$ podman start hello-api
The running containers can be listed with
$ podman ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e9d9982e397d localhost/my-api:latest uvicorn main:app ... 37 minutes ago Up 4 seconds ago 0.0.0.0:5000->5000/tcp hello-api
Check the log output with
$ podman logs hello-api INFO: Started server process  INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:5000 (Press CTRL+C to quit) INFO: Shutting down INFO: Waiting for application shutdown. INFO: Application shutdown complete. INFO: Finished server process 
Making the switch from Docker to Podman is not as hard as it might feel at first. Of course there are some differences between the projects but the main things are where you expect them to be.
With some tweaking it’s even possible to get docker-compose working with Podman. With Podman’s support for running containers and pods based on Kubernetes YAML you have options beyond the Docker scope available to you.