01. June 2026 • 4 min. read

Managing Podman Containers with Systemd Quadlets

LinuxPodman

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/.container, if you want to run the container rootful or ~/.config/containers/systemd/ for rootless.

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

Search