Title: Declaratively manage containers on Linux
Author: Solène
Date: 10 February 2026
Tags: systemd linux containers
Description: In this article, you will learn how to manage your podman
containers declaratively
# Introduction
When you have to deal with containers on Linux, there are often two
things making you wonder how to deal with effectively: how to keep your
containers up to date, and how to easily maintain the configuration of
everything running.
It turns out podman is offering systemd unit templates to declaratively
manage containers, this comes with the fact that podman can run in user
mode. This combination gives the opportunity to create files, maintain
them in git or deploy them with a configuration management tool like
ansible, and keep things separated per user.
It is also very convenient when you want to run a program shipped as a
container on your desktop.
For some reason, this is called "quadlets".
(HTM) podman-systemd.unit man page
In this guide, I will create a kanboard service (a PHP software to run
a kanban) under the kanban user.
# Setup (simple service)
You need to create files that will declare containers and/or networks,
this can be done in various places depending on how you want to manage
the files, the man page gives all the details, but basically you want
to stick with the two following options:
* system-wide configuration: `/etc/containers/systemd/users/$(UID)`
* user configuration: `~/.config/containers/systemd/`
Both will run rootless containers under the user UID, but one keep the
files in `/etc/` which may be more suitable for central management.
As systemd is used to run the containers, if you want to run a
container for a user that is not one where you are logged, you need to
always enable it so its related systemd processes / services are
running, including the containers, this is done by enabling "linger".
```
useradd -m kanban
loginctl enable-linger kanban
```
This will immediately create a session for that user and pop all
related services.
Now, create a file `/etc/containers/systemd/users/1001/` (1001 being
the uid of kanban user) with this content:
```
[Container]
Image=docker.io/kanboard/kanboard:latest
Network=podman
PublishPort=10080:80
Volume=kanboard_data:/var/www/app/data
Volume=kanboard_plugins:/var/www/app/plugins
Volume=kanboard_ssl:/etc/nginx/ssl
[Service]
Restart=always
[Install]
WantedBy=default.target
```
This can exactly map to a very long podman command line that would use
the image `docker.io/kanboard/kanboard:latest` in network `podman` and
declaring three different container volumes and associated mount
points. This generator even allows you to add command line arguments
in case an option is not available with systemd format.
Because the user already runs, the container will not start yet except
if you use `disable-linger` and then `enable-linger` the kanban user,
and that would not be ideal to be honest. There is a better way to
proceed: `systemctl --user --machine kanban@ daemon-reload` which
basically runs `systemctl --user daemon-reload` by the user `kanban`
except we do it as root user which is more convenient for automation.
Running the container this way will trigger exactly the same processes
as if you started it manually with `podman run -v
kanboard_data:/var/www/app/data/ [...]
docker.io/kanboard/kanboard:latest`.
Note that you can skip the `[Install]` section if you do not want to
automatically start the container, and prefer to manually start/stop it
with "systemctl", this is actually useful if you have the container
under your regular user and do not always need it.
# Setup (advanced service)
If you want to run a more complicated service that need a couple of
containers to talk together like a web server, a backend runner and a
database, you only need to configure them in the same network.
If you need them to start the containers of a group in a specific
order, you can add use systemd dependency declaration in `[Install]`
section.
Podman will run a local DNS resolver that translates the container name
into a working hostname, this mean if you have a postgresql container
called "db", then you can refer to the postgresql host as "db" from
another container within the same network. This works the same way as
docker-compose.
# Ops
## Getting into a user shell
To have a working environment for `journalctl` or `systemctl` commands
to work requires to use `machinectl shell kanban@`, otherwise the dbus
environment variables will not be initialized. Note that it works too
when connecting with ssh, but it is not always ideal if you use it
locally.
From this shell, you can run commands like `systemctl --user status
kanboard.container` for our example or `journalctl --user -f -u
kanboard.container`, or run a shell in a container, inspect a volume
etc...
Using `sudo -u user` or `su - user` will not work.
## Disabling a user
If you want to disable the services associated with an user, use this
command:
```
loginctl disable-linger username
```
This will immediately close all its sessions and stop services running
under that user.
## Automatic updates
This is the very first reason I went into using quadlets for local
services using containers, I did not want to have to manually run some
`podman pull` commands over a list then restart related containers that
were running.
Podman gives you a systemd services doing all of this for you, this
works for containers with the parameter `AutoUpdate=registry` within
the section `[Container]`.
Enable the timer of this service with: `systemctl --user enable --now
podman-auto-update.timer` then you can follow the timer information
with `systemctl --user status podman-auto-update.timer` or logs from
the update service with `journalctl --user -u
podman-auto-update.service`.
Make sure to pin your container image to a branch like "stable" or
"lts" or "latest" if you want a development version, the update
mechanism will obviously do nothing if you pin the image to a specific
version or checksum.
# Conclusion
Quadlets made me switch to podman as it allowed me to deploy and
maintain containers with ansible super easily, and also enabled me to
separate each services into different users.
Prior to this, handling containers on a simple server or desktop was an
annoying task to figure what should be running, how to start them and
retrieving command lines from the shell history or use a docker/podman
compose file. This also comes with all the power from systemd like
querying a service status or querying logs with journalctl.
# Going further
There is a program named "podlet" that allow you to convert some file
format into quadlets files, most notably it is useful when getting a
`docker-compose.yml` file and transforming it into quadlet files.
(HTM) podlet GitHub page