Hello everyone,
today I want to talk about podman and quadlets specifically. I’m, hopefully, going to show you how to set up podman using systemd containers on your Linux system.
This won’t be a tutorial for podman, it’s mainly about the systemd container feature.
If you never heard of podman before, it’s a container runtime that allows you to run applications in isolated environments called containers. It’s similar to Docker but has some key differences.
I always wanted to, but actually never really used it before, mainly because all of my Ansible playbooks and roles are specifically designed for Docker containers. It’s also the system I have the most experience with. But, this changed with the new website.
I cannot exactly tell you why I decided to use podman on the webserver (because I don’t remember it myself), but once I deployed it, I realized how easy it would be to maintain using Ansible, especially when using the quadlets. So, what are quadlets? From the documentation:
Podman Quadlets allow users to manage containers, pods, volumes, networks, and images declaratively using systemd unit files.
And those unit files make this whole thing very simple.
Alright, enough small talk. Let’s begin.
Prerequisites
Let’s first install podman on your system. I am using AlmaLinux 9.8, but you can use any other distribution that supports podman and systemd.
# Install podman and podman-compose. The latter is optional but useful.
$ sudo dnf install podman podman-compose
File conversion
As an example of a podman/docker compose file, I will be using umami, since it’s what I deployed on the webserver.
This is the file we will be working with. It’s the podman-compose.yml file you can get on their GitHub page.
version: "3.8"
services:
umami:
container_name: umami
image: ghcr.io/umami-software/umami:postgresql-latest
ports:
- "127.0.0.1:3000:3000"
environment:
DATABASE_URL: ${DATABASE_URL}
DATABASE_TYPE: ${DATABASE_TYPE}
APP_SECRET: ${APP_SECRET}
depends_on:
db:
condition: service_healthy
init: true
restart: always
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/api/heartbeat || exit 1"]
interval: 5s
timeout: 5s
retries: 5
db:
container_name: umami-db
image: docker.io/library/postgres:15-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- umami-db-data:/var/lib/postgresql/data:Z
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 5
volumes:
umami-db-data:
We will convert this into *.container files. There is a tool named podlet that can help with this. It can either convert a compose file directly, or generate quadlet files from already-running containers. The compose conversion did not work in my case, and I did not try the running-container option, so we will do it manually.
The manual conversion is not too complex. I would suggest using a template or similar.
We have to create a single systemd container file, for each podman container. So in this case, it would be umami.container and umami-db.container.
Here are the files. Make sure to adjust the “Volume” path and username / password.
umami.container
[Unit]
Description=Umami Analytics
After=network-online.target umami-db.service
[Container]
Image=ghcr.io/umami-software/umami:postgresql-latest
ContainerName=umami
PublishPort=127.0.0.1:3000:3000
Network=umami-net
Environment=DATABASE_URL=postgresql://umami:<YOUR_PASSWORD>@umami-db:5432/umami
Environment=DATABASE_TYPE=postgresql
Environment=APP_SECRET=<YOUR_SECRET>
HealthCmd=curl -f http://localhost:3000/api/heartbeat || exit 1
HealthInterval=5s
HealthTimeout=5s
HealthRetries=5
[Service]
Restart=always
[Install]
WantedBy=multi-user.target
umami-db.container
[Unit]
Description=Umami PostgreSQL Database
After=network-online.target
[Container]
Image=docker.io/library/postgres:15-alpine
ContainerName=umami-db
Network=umami-net
Volume=/opt/umami/data:/var/lib/postgresql/data:Z
Environment=POSTGRES_DB=umami
Environment=POSTGRES_USER=umami
Environment=POSTGRES_PASSWORD=<YOUR_SECRET>
HealthCmd=pg_isready -U umami -d umami
HealthInterval=5s
HealthTimeout=5s
HealthRetries=5
[Service]
Restart=always
[Install]
WantedBy=multi-user.target
We will move those files into the systemd folders. Either /etc/containers/systemd/
I will be using the rootful path for this example.
# Move the files into the systemd folder
$ mv ~/umami.container ~/umami-db.container /etc/containers/systemd/
Create Podman network
Next, create the Podman network we defined in the container files.
# Create podman network
$ podman network create umami-net
Once that’s done, we have to reload the systemd manager configuration. This will generate the *.service files.
# Reload systemd configuration
$ systemctl daemon-reload
Now we can enable and start the services. This will download the container images and start the containers.
# Enable and start the container services
$ systemctl enable --now umami-db.service
$ systemctl enable --now umami.service
# Check the services and latest logs
$ systemctl status umami-db.service
$ systemctl status umami.service
Check logs and healthcheck
If you want to check the logs of the containers directly, use the following command.
# Check logs
$ podman logs umami
To verify the healthchecks, run the following commands.
# Check healthcheck
$ podman healthcheck run umami ; echo $status
# 0 means healthy
Alternatively, you can execute this command to check the actual output.
# Check healthcheck output
$ podman inspect umami-db --format "{{.State.Health}}"
{healthy 0 [{2026-06-01T09:23:56.993088212+02:00 2026-06-01T09:23:57.02155953+02:00 0 /var/run/postgresql:5432 - accepting connections
} {2026-06-01T09:24:02.997346186+02:00 2026-06-01T09:24:03.020578446+02:00 0 /var/run/postgresql:5432 - accepting connections
} {2026-06-01T09:24:08.33550115+02:00 2026-06-01T09:24:08.361606311+02:00 0 /var/run/postgresql:5432 - accepting connections
} {2026-06-01T09:24:13.99350031+02:00 2026-06-01T09:24:14.020579326+02:00 0 /var/run/postgresql:5432 - accepting connections
} {2026-06-01T09:24:20.00419122+02:00 2026-06-01T09:24:20.055567613+02:00 0 /var/run/postgresql:5432 - accepting connections
}]}
That’s it. Managing this using Ansible should be very straightforward. I think I will create a post for this soon.
Till next time.
Comments