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:

  1. pay the license, or
  2. 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 logo

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.

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 to Containerfile!

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.

Subscribe to my newsletter

What’s new with PäksTech? Subscribe to receive occasional emails where I will sum up stuff that has happened at the blog and what may be coming next.

powered by TinyLetter | Privacy Policy