1. 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".
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.
2. 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.
3. 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.
4. Ops §
4.1. 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.
4.2. 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.
4.3. 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.
5. 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.
6. 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.
podlet GitHub page