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