In the selfhosted sub in Reddit often the question arises what the best way of running containerized applications with systemd is: Docker (via docker compose) or podman (via quadlets). Personally for me, the best option is docker compose with systemd.
Podman and quadlets
Podman is a free Docker alternative, supported by Redhat, with good systemd integration. Quadlets are a way to express containers in a systemd unit. This way you can easily run podman contains as systemd services, with full lifecycle support and systemd integration.
Docker compose and systemd
Many example for services come with an example file for docker compose, a compose.yml file - that makes it convenient to adopt such a software for own use. Would I use podman, I’d have to convert all the examples into podman, first. Or, use the docker compatibility layer - which is excellent, but does not have 100% feature parity.
A detail of many of those examples I don’t like is many services come with a restart policy restart: unless-stopped … which makes the container start if it crashed, or automatically after a reboot. Auto-restarting sure is necessary, but systemd has way better, more fine-grained ways to express those policies (restart counter, restart duration, etc.).
Systemd can also express dependencies to other services or mount points that are necessary for the service to sucessfully run. So let’s remove the restart: and move the service to systemd:
Systemd unit for docker compose stack
I would like to start the whole compose stack as one service and have systemd manage the lifecycle; thus, the service needs to be started in non-daemon mode, ie. by omitting the -d or --detach flag. Systemd assumes the program (= the stack) is running as long as the docker compose command hasn’t exited.
The second piece is to add the not much widespread flag --abort-on-container-failure. Putting this together results in a system.unit such as
1[Unit]
2Description=Compose service paperless-ngx
3Requires=docker.service
4After=docker.service
5
6[Service]
7Restart=always
8User=root
9Group=docker
10TimeoutStopSec=30
11
12WorkingDirectory=/home/compose/paperless-ngx
13ExecStart=/usr/bin/docker compose up --abort-on-container-failure
14ExecReload=/bin/bash -c '/usr/bin/docker compose pull --quiet && systemd-run --on-active=1s systemctl restart paperless-ngx'
15ExecStop=/usr/bin/docker compose down
16
17[Install]
18WantedBy=multi-user.target
There’s also the option to expose the exit-code of a particular container as the exit-code of docker compose for the stack via --exit-code-from=<container>; I have not incorporated that into my setup - since there’s not so much use for it, since these services are supposed to not stop, actually.
Logging
Another detail is to configure Docker on the host so that all logs are written by the journald-driver, ie. add to /etc/docker/daemon.json:
{
"log-driver": "journald"
}
This should avoid duplicating the logs in Dockers own logstore and journald.