Using Podman, Compose and BuildKit
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!