Quadlet lets you run your Podman containers as systemd services.
This is especially useful for running containers in the background and automatically starting them after a server reboot.
Running Podman containers under systemd isn’t new.
Indeed, this was supported by Podman for a long time with the command podman generate systemd
.
But this command now shows a deprecation warning to migrate to Quadlet.
For some months, I was too lazy to do that migration on my home server.
Why even touch a working system?
But now that I finally found time for it, I really appreciate Quadlet!
I think that Podman finally has a Docker Compose alternative which is even more flexible and powerful!
In this blog post, I will explain how to use Quadlet with rootless Podman and migrate from the old method with podman generate systemd
.
Note
If you wonder why systemd: Something has to start containers when there is no daemon (since Podman is daemonless).
If you are part of the vocal minority hating against systemd, then please leave instead of trolling.
Landscape mode recommended on mobile devices
The deprecated method
Let’s see how the old method worked before comparing it to Quadlet.
You can skip to the Quadlet section though.
First, you had to create a container.
In an older post about Containerized PostgreSQL with rootless Podman, I created a container with a command similar to the following:
podman create
--name test-db
-p 5432:5432
-v ~/volumes/test-db:/var/lib/postgresql/data:Z
-e POSTGRES_PASSWORD=CHANGE_ME
--label "io.containers.autoupdate=registry"
docker.io/library/postgres:16
The command details are explained in the older post.
The only option that isn’t explained there is --label "io.containers.autoupdate=registry"
.
This option enables updating the container image using podman auto-update
which will be explained later in this post.
After creating the container, you can run the following command:
podman generate systemd test-db -fn --new
It creates the systemd service file container-test-db.service
in your current path.
The options of the command aren’t relevant for now, but they are also explained in the older post.
To use this generated service file, you had to place it in the directory ~/.config/systemd/user
.
To enable and start it, you had to run the following command:
systemctl --user enable --now container-test-db
The problem
The problem with the old method is that it required you to run commands to…
- create a container
- generate a service file
- move the service file if not already in the mentioned directory
- enable the service
Especially the command for creating the container is often lengthy.
This means that you had to create a shell script with these commands if you wanted to be able to rerun them later.
To reduce duplication, I created the following fish function to be called in my fish scripts that create the containers:
function podman-default-create
set -l container_name $argv[1]
podman create
--name $container_name
--replace
$argv[2..]; or return 1
podman generate systemd --no-header --new --name $container_name >~/.config/systemd/user/container-$container_name.service; or return 1
systemctl --user enable --now container-$container_name
end
You don’t have to understand the details of the function above.
What I want to demonstrate with it is that the old method was too hacky and involved the usage of redundant commands.
There must be an easier way, you might think.
Especially if you experienced the convenience that Docker Compose provides.
But this is not the only problem.
The old method is very inflexible!
If you want to cutomize the service file and use all systemd features, you need to manually edit it after each generation!
Quadlet
Let’s take a look at the new method with Quadlet.
First, you create the directory ~/.config/containers/systemd
.
Then, you place a .container
file inside it.
For example, here is the test-db.container
file:
[Container]
Image=docker.io/library/postgres:16
AutoUpdate=registry
PublishPort=5432:5432
Volume=%h/volumes/test-db:/var/lib/postgresql/data:Z
Environment=POSTGRES_PASSWORD=CHANGE_ME
[Service]
Restart=always
[Install]
WantedBy=default.target
It is a normal systemd service file but with the special section [Container]
.
This section has many documented options.
Almost all these options map to command line options that can be used to create a container with Podman (podman create
).
The ones that we are interested in for the example are the following:
Image
specifies the image (with tag) to useAutoUpdate=registry
maps to--label "io.containers.autoupdate=registry"
(explained later in this post)PublishPort
maps to-p
Volume
maps to-v
Environment
maps to-e
It is important to use the systemd specifier %h
instead of ~
for the user home directory.
In the [Service]
section, we use the Restart
option and set it to always
to always restart the container (unless stopped manually).
To automatically start the container on boot, we set the WantedBy
option in the [Install]
section to default.target
.
Note
I thought that setting WantedBy
to multi-user.target
would work because it is the default target on servers.
But it doesn’t wo
11 Comments
brirec
It’s funny to me that this post calls out Podman-Compose as “not actively maintained” with its last commit being 5 months ago, then turns around to recommend Podlet (whose last commit was…5 months ago) as an alternative.
Podlet can be useful and helpful, but ultimately it doesn’t support many of the features of Docker Compose and doesn’t always provide a clean translation. In particular, Podlet doesn’t support stacking multiple yaml files (e.g., -f docker-compose.yml -f docker-compose.override.yml)
stryan
Quadlet is one of the best things to have come out of Podman and I highly recommend anyone curious about Podman or switch to container-based workloads to check them out. Being able to slot containers in and treat them like essentially any other system service feels great, plus I don't have to learn some extra orchestration layer to get them to work together or depend on non-container resources. I can just write the same systemd units I'm already writing. The auto-updating and service restart/notify on failure/etc is just icing on the cake. I've seen the equivalent Docker versions before and they're awful; giant messy run commands to try to work around the Docker daemon and half the time you end up with phantom services and containers anyway. Quadlet's end up being much cleaner; plus it means your whole setup (besides volume contents) exists with your other systemd units (/etc/systemd/, .config/systemd, /usr/local/lib/systemd, etc) so it's easy for backups.
The only downside is they're not really an answer to docker-compose on the local development side and the podman team doesn't seem super interested in tackling that segment. User containers are nice for long running local test infra (i.e. a background database) but are too clunky for a normal compile-> docker compose up -> test -> docker compose down loop. The best answer is either .kube Quadlets (kubernetes plays) or using docker compose [0] against the podman socket.
Either way, I've enjoyed using quadlets enough that I've spent the last few months writing a gitops tool for managing them in my spare time. They just feel like the right way of managing containerized servers.
[0] NOT podman-compose, which the article points out as being not very good and under-developed. Podman implements most of the compose spec so you can use docker compose for most situations. I suspect many people who tried Podman when RH first started pushing it ran into Podman 3 being kinda of bleh and podman-compose being awful and bounced off it.
bityard
I think I understand that quadlets are containers managed by systemd. But I'm still in the dark about why they are called that, or why they need a special opaque name.
udev4096
docker compose is way too good to be fiddling around with alternatives. Podman is nice but the quadlet solution doesn't even come close to what docker compose offers
muti
I wanted to try something different when I reset my self host set up several years ago, and went with openSUSE MicroOS. Ultimately it has led to podman containers running under systemd/quadlet and I'm quite happy with the current set up.
Containers auto update with built in podman tooling, getting at logs and monitoring is through the usual systemd tools. When I need to change something, it's easy to work out where the config files are if I have forgotten and they are easy to read and change. Rootless and daemonless is nice too.
I tried a few things along the way, podman compose felt clunky so I'm glad it is deprecated and it's clear quadlets are the way to go.
There was a learning curve and there's less information out there than with docker, so keep that in mind. I would still lean towards docker and docker compose for local dev to bring a stack of services up and down.
mati365
If anyone interested – I made recently Ansible template for Quadlet deployment that shows how easy is it.
GH: https://github.com/Mati365/hetzner-podman-bunjs-deploy
cvhc
The format is clearer than podman generate systemd or kubernetes YAML. And the integration with systemd is great.
What annoys me is Podman upstream doesn't offer a repo for Debian/Ubuntu. I was stuck at version 4.3.1 on Debian stable, missed many new features and eventually decided to go back to Docker compose.
alexellisuk
Interesting to see Qualet on the front page of Hacker News. I don't think it has had enough attention. We had Ygal & Valentin from the project submit a guest post on how to run an inlets tunnel client (think of Ngrok/Cloudflared, but self-hosted without any SaaS limits) – https://inlets.dev/blog/2023/10/03/client-quadlet.html
Rather than using [container] they used [kube] and were able to bring along standard Kubernetes YAML making it quite portable.
eulenteufel
I really like quadlets as they enable using containers like normal system services.
That said the UX for rootless containers does not play well with this conceptualization.
Normally system services run as system users in the system systemd-session, but
for rootless containers the services reside in the user systemd sessions of the system user. I'd love to be able to run rootless quadlets within the system session.
aprilfoo
I'm happily using quadlets as a lightweight container orchestration tool for few months now and i also think it deserves more attention. RH is doing a great integration job pushing the systemd ecosystem.
As many projects still only mention docker/compose, it would be great to have a community maintained quadlet store – something like https://github.com/dwedia/podmanQuadlets?
sunshine-o
I have been trying to adopt Quadlet since before Covid if I remember correctly (it wasn't named Quadlet yet) because this is such a key piece of the puzzle.
I kept using things like Docker Compose for simple services until now but it always felt like a temporary solution.
So I try every year and every time I am not convince this thing is solid/polished enough yet. I am confident 2025 is gonna be a go according to the positive comments here.
My sincere question is: why did it took about 10 years to have a basic working integration between the service manager and containers (and by containers I mean the way we run most non system services nowadays)?
My intuition is there must be some ugly politics involved between IBM/Redhat, Systemd and some other actors but I can't figure it out….