Podman, a root-less alternative to Docker

Software systems builder
To save some money, I recently destroyed my Docker host DigitalOcean droplet, but it, amongst other things, terminated my purchasing power parity (PPP) feature on my store.

Of course, I didn't want to remove that feature. For me, people living in countries where the USD to their currency is crazy shouldn't have to pay the same price for my courses as someone from, say, the United States or Europe.
My PPP feature works by first turning an IP address into a country and region that I then calculate against a world PPP dataset. It's nothing fancy, but it's something I want to continue supporting.
The easiest way I found to turn an IP address into the data I need is an open-source Go project that comes with a Dockerfile
. Plus, I didn't want to set up an Nginx entry and provision an SSL certificate only for this. So a container is way simpler.
Podman
I tried Podman about two or three years ago. It worked for a while, and then it started crashing. I'm not sure if it was because I'm using WSL v2—yes, I use Windows for accessibility reasons, specifically for my screen reader.
Nonetheless, I decided to give it another spin and installed it on my remaining DO droplet, the one I deploy my web apps on.
The thing I obviously like about Podman is the fact that it's rootless, meaning that it's not running via a system daemon but locally per user.
This distinction is fairly important and more secure. This plays nicely with systemd's local user config; everything is isolated to a user and not system-wide.
The icing on the cake is that Podman is compatible with Docker. So in terms of Dockerfile
s and most of the CLI commands, it's a seamless transition. Even if you're using docker-compose
, Podman has a podman-compose
equivalent.
Since it's not a system daemon, the way it works is by forking the podman
process and running the container in the fork, which is owned by the user that started it. It's kind of brilliant when you think about it.
Familiar Docker Commands
One minor note to say is that some images need to be prefixed with their repository. For instance, some-image:latest
becomes docker.io/repository/some-image:latest
. You need to use fully qualified image names.
Pull an image:
$ podman pull nginx:latest
Run a container
$ podman run -d --name my-app -p 8080:80 image-name
List running containers
$ podman ps
View logs
$ podman logs name-or-id
Remove a container
$podman stop name-or-id$ podman rm name-or-id
I mean, it's mostly just a matter of replacing docker
with podman
, mostly.
Image Building with Buildah
One of the great things about the rootless design is how it splits the concerns of running containers and building images. Podman actually relies on another tool called Buildah to handle image construction.
The beauty of this is that you can build your images without any root privileges, further enhancing the security of your build pipeline.
While you can still use podman build
, under the hood, it's leveraging Buildah. If you want more granular control over your image layers without needing a full Dockerfile
, Buildah is the dedicated tool for that job.
# Podman's way (uses Buildah internally)
$ podman build -t my-app-image .
# Buildah's dedicated way for more advanced scripting
$ buildah bud -t my-app-image .
Running with systemd
Now, the best part of having the container run by podman
is to integrate with systemd
, where your container will restart when the server restarts or any other shutdown mechanism.
The first step is to generate a systemd
unit file:
$ podman generate systemd --new --files --name my-container
This will generate the proper systemd
unit file in the current directory. You'll need to move it where systemd
wants to see those files:
$ mkdir -p ~/.config/systemd/user/
$ mv container-my-container.service ~/.config/systemd/user/
Now, just like we do when we add or modify a system unit file, we need to reload the internal systemd
cache:
$ systemctl --user daemon-reload
Enabling the Service to Run All the Time
Now, this service will need to be able to start even if the user that owns it isn't logged in. Think of if the server restarts for any reason—this service will need to start even if our user isn't logged in.
This is where loginctl enable-linger
comes in.
$ sudo loginctl enable-linger [your-username]
And then, we enable the service to tell systemd
we want to run it all the time.
$ systemctl --user enable container-my-container.service
$ systemctl --user start container-my-container.service
Podman-Compose and Kubernetes
Like I said, if you already have a docker-compose.yaml
, you would need to fully qualify your image names and install podman-compose
, and you should be up and running:
$ pip install podman-compose
It's a Python-based tool. Personally, I stopped using Compose files. I was mainly using them for dev environments when an app required, say, PostgreSQL, Redis, and maybe other things.
These days, I continue to simplify my environment and mostly have a global PostgreSQL running from Docker, and that's mostly it.
But the option exists, and sometimes, no matter if you want to continue using it or have transitioned, at least you have an option to use podman
with your existing Docker Compose files.
Another great benefit is that podman
can consume Kubernetes YAML files for more advanced orchestration with the podman play kube
command:
$ podman play kube
This is a fantastic feature for engineers looking to simplify their local Kubernetes development workflow, allowing you to test complex multi-container setups right on your machine before deploying to a cluster.
So Far, It Works as Advertised
And just like that, I had my IP-to-country service back without installing Docker and everything that comes with it.
My next step will be to retry it in WSL, but that will be for a later time.