From Docker Compose to Quadlets
I recently migrated my home server from Debian 12 to 13 and I decided to ditch Docker for Podman. I've written about some of the differences when moving from Docker to Podman previously.
Here's my old docker-compose.yml file:
services:
plex:
image: lscr.io/linuxserver/plex:latest
container_name: plex
# Host networking is simpler than trying to forward everything
network_mode: host
environment:
- TZ=America/Chicago
- VERSION=docker
- PUID=1000
- PGID=1000
- PLEX_CLAIM=<REDACTED>
volumes:
- /home/server-user/plex/config:/config
- /media/wd-gold/Television:/tv
- /media/wd-gold/Movies:/movies
devices:
- /dev/dri:/dev/dri
restart: unless-stopped
ports:
- 32400:32400/tcp
tiddlywiki:
image: node:lts-alpine
container_name: tiddlywiki
entrypoint: npx --yes tiddlywiki /wiki --listen host=0.0.0.0 port=8080 debug-level=debug
healthcheck:
test: wget -q --spider -o /dev/null 127.0.0.1:8080 || exit 1
interval: 60s
timeout: 5s
volumes:
- /home/server-user/wiki:/wiki
restart: unless-stopped
ports:
- 80:8080/tcp
I've got a Plex server, and a TiddlyWiki running on port 80. This is not a particularly apt use of Docker Compose since I'm only really using it because it's familiar and it lets me bring the server up and down with a single command.
To move to Podman, I tried what I thought would be the path of least resistance: Podman Compose. It wasn't as turn-key as I would have expected. Because Podman doesn't use a root daemon like Docker, all of my containers would die when I logged out of my user. This wasn't an issue when I used Docker since Docker's daemon was always running. To keep my user session alive, I could have chosen something like nohup, screen, or tmux. I chose instead to turn on lingering for the user with systemd.
sudo loginctl enable-linger server-user
Yes, I have to let it linger. RIP Dolores O'Riordan.
This change at least let me continue limping along with Podman Compose until I had to chance to investigate. Because Podman is not daemon-based, restart: unless-stopped doesn't work. It also means that my containers don't come back up automatically if I reboot the system. Also, my Plex container doesn't get write access to my media library, meaning I can't delete things from the Plex interface. I've tried adding :rw and :rw,Z and :rw,z to those volumes without effect. I think it's probably because Podman Compose is automatically putting everything into a "pod", which is composed of a "Linux network namespace, IPC namespace, and (optionally) PID namespace" that can be shared between containers. There are ways to configure your compose file so that Podman doesn't do this, but the version of Podman Compose present in Debian's repos doesn't understand those options. I'd have to install a newer version of Podman Compose through pip, for example.
It was about this point when I figured maybe I was using the wrong tool for the job. I decided to try Quadlets instead. They seem to be the preferred way to orchestrate Podman containers. Podman ships with support for Quadlets included. Quadlets are files that share the syntax of systemd unit files. They are run through a generator that generates actual systemd unit files. From there, the containers are managed just like any other user services, using systemd. What do you expect? Systemd and Podman both come from the folks at Red Hat IBM.
# /home/server-user/.config/containers/systemd/plex.container
[Unit]
Description=Plex Media Server
After=network.target
[Container]
Image=lscr.io/linuxserver/plex:latest
Network=host
UserNS=keep-id
Environment=TZ=America/Chicago
Environment=VERSION=docker
Environment=PLEX_CLAIM=<REDACTED>
Volume=/home/server-user/plex/config:/config:Z
Volume=/media/wd-gold/Television:/tv:Z
Volume=/media/wd-gold/Movies:/movies:Z
AddDevice=/dev/dri:/dev/dri
PublishPort=32400:32400/tcp
[Service]
Restart=always
[Install]
WantedBy=default.target
I don't think the :Z is actually doing anything here because I don't think there are any SELinux labels in those paths, but it seems to be considered good practice and I can't imagine it hurts anything.
This configuration does not exhibit the media deletion issue I experienced with Podman Compose.
# Remember to fix permissions if you're coming from Docker
sudo chown -R server-user:server-user "/home/server-user/plex"
systemctl --user daemon-reload
systemctl --user start plex.service
Here's the one for TiddlyWiki:
# /home/server-user/.config/containers/systemd/tiddlywiki.container
[Unit]
Description=TiddlyWiki
After=network.target
[Container]
Image=docker.io/library/node:lts-alpine
UserNS=keep-id
Exec=npx --yes tiddlywiki /wiki --listen host=0.0.0.0 port=8080
Volume=/home/server-user/wiki:/wiki:Z
PublishPort=80:8080/tcp
[Service]
Restart=always
[Install]
WantedBy=default.target
You may notice that I don't have a health check for this service like I had for Docker Compose. It doesn't seem like Quadlets have any equivalent functionality. I could certainly create a systemd service by hand to check the service's health, but I was never really reporting or reacting to the health check's status, so I ditched it instead.
# Remember to fix permissions if you're coming from Docker
sudo chown -R server-user:server-user "/home/server-user/wiki"
systemctl --user daemon-reload
systemctl --user start tiddlywiki.service
So far, this setup seems to be working. I plan on adding some more services and features in the future, like Traefik to route services to friendlier mDNS hostnames, and an IRC bouncer service. I'll be sure to write up more posts as I add those.
While I was tinkering with these Quadlet files, sometimes I'd make a mistake. When a Quadlet file is invalid, the corresponding service is not generated. After running daemon-reload, trying to start the service would give me a "unit not found" error. To see detailed errors explaining what was wrong with my Quadlet file, I'd run the following command:
/usr/libexec/podman/quadlet --dryrun --user
These posts I write with the #home server tag are mostly just documentation for myself in the future. My previous posts have already come in handy when I make big changes to my system, like upgrading Debian. I highly recommend documenting your own projects for yourself.