Using Podman, Compose and BuildKit 2025-02-23
For my day job, I need to build and run a Docker Compose project. However, because Docker doesn’t play well with nftables and I prefer a rootless + daemonless approach, I’m using Podman.
Podman supports Docker Compose projects with two possible solutions: either by connecting the official Docker Compose CLI to a Podman socket, either by using their own drop-in replacement. They ship a small wrapper to select one of these options. (The wrapper has the same name as the replacement, which makes things confusing.)
Unfortunately, both options have downsides. When using the official Docker Compose CLI, the classic builder is used instead of the newer BuildKit builder. As a result, some features such as additional contexts are not supported. When using the podman-compose replacement, some other features are missing, such as !reset , configs and referencing another service in additional contexts. It would be possible to add these features to podman-compose, but that’s an endless stream of work (Docker Compose regularly adds new features) and I don’t really see the value in re-implementing all of this (the fact that it’s Python doesn’t help me getting motivated).
I’ve started looking for a way to convince the Docker Compose CLI to run under Podman with BuildKit enabled. I’ve tried a few months ago and never got it to work, but it seems like this recently became easier! The podman-compose wrapper force-disables BuildKit, so we need to use directly the Docker Compose CLI without the wrapper. On Arch Linux, this can be achieved by enabling the Podman socket and creating a new Docker context (same as setting DOCKER_HOST , but more permanent):
pacman -S docker-compose docker-buildx systemctl --user start podman.socket docker context create podman --docker host=unix://$XDG_RUNTIME_DIR/podman/podman.sock docker context use podman
With that, docker compose just works! It turns out it automagically creates a buildx_buildkit_default container under-the-hood to run the BuildKit daemon. Since I don’t like automagical things, I immediately tried to run BuildKit daemon myself:
pacman -S buildkit systemctl --user start buildkit.service docker buildx create --name local unix://$XDG_RUNTIME_DIR/buildkit/rootless docker buildx use local
Now docker compose uses our systemd-managed BuildKit service. But we’re not done yet! One of the reasons I like Podman is because it’s daemonless, and we’ve got a daemon running in the background. This isn’t the end of the world, but it’d be nicer to be able to run the build without BuildKit.
Fortunately, there’s a way around this: any Compose project can be turned into a JSON description of the build commands called Bake. docker buildx bake --print will print that JSON file (and the Docker Compose CLI will use Bake files if COMPOSE_BAKE=true is set since v2.33). Note, Bake supports way more features (e.g. HCL files) but we don’t really need these for our purposes (and the command above can lower fancy Bake files into dumb JSON ones).
The JSON file is pretty similar to the podman build CLI arguments. It’s not that hard to do the translation, so I’ve written Bakah, a small tool which does exactly this. It uses Buildah instead of shelling out to Podman (Buildah is the library used by Podman under-the-hood to build images). A few details required a bit more attention, for instance dependency resolution and parallel builds, but it’s quite simple. It can be used like so:
docker buildx bake --print >bake.json bakah --file bake.json
Bakah is still missing the fancier Bake features (HCL files, inheritance, merging/overriding files, variables, and so on), but it’s enough to build complex Compose projects. I plan to use it for soju-containers in the future, to better split my Dockerfiles (one for the backend, one for the frontend) and remove the CI shell script (which contains a bunch of Podman CLI invocations). I hope it can be useful to you as well!