Replace Docker with Podman and Buildah
Janne 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 docker
with 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.
Installation
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:
Arch, Manjaro:
sudo pacman -S podman
CentOS:
sudo yum -y install podman
Debian, Ubuntu, Raspberry Pi OS:
sudo apt-get -y install podman
Fedora:
sudo dnf -y install podman
Gentoo:
sudo emerge app-emulation/podman
openSUSE:
sudo zypper install podman
RHEL7
sudo subscription-manager repos --enable=rhel-7-server-extras-rpms sudo yum -y install podman
RHEL8
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
Build images
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.
Dockerfiles
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
Dockerfile
toContainerfile
!
Let’s create a minimal Python API as an example. The Python dependencies are listed in this requirements.txt
file:
fastapi
uvicorn
The API itself is implemented in main.py
:
from fastapi import FastAPI
app = FastAPI()
@app.get("/hello")
def hello():
return "Hello world!"
The 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
Build commands
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
The 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
.
$ 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
Run containers
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 [1]
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 -d
.
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
.
$ 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
.
$ podman logs hello-api
INFO: Started server process [1]
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 [1]
Conclusion
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.
Previous post
Send Teams Notifications from Azure DevOpsNext post
Loops in Azure DevOps Pipelines