Luanti containers, part 3: Comparing podman-compose rootless and systemd containers (quadlet?)
This is part 3 of a short series on comparing various container tech to run my Luanti game server and related services.
I believe quadlet is the term to describe using systemd to manage containers. It's now "bundled into podman," so I think that's the only dependency you need to install.
This is a major change in syntax and file layout, but it makes sense to me: Use systemd components in a systemd world, and work around systemd problems.
Podman compose rootless
files/2026/listings/luanti-rootless-podman-compose.yml (Source)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File: /home/luanti/game-onyx/docker-compose.yml # Author: bgstack15 # Startdate: 2026-02-20-6 08:42 # History: # 2025 # this app in the container will not read the ~/.minetest/minetest.conf, particularly for mcl_keepInventory=true, so I had to modify ~/.minetest/games/mineclonia/minetest.conf # Reference: # https://github.com/linuxserver/docker-luanti # https://github.com/minetest-mapserver/mapserver/blob/master/docker-compose.yml # https://github.com/minetest-mapserver/mapserver/blob/master/doc/install.md more useful # https://github.com/pandorabox-io/pandorabox.io/blob/master/docker-compose.yml#L45-L57 # uid 30000 from https://github.com/luanti-org/luanti/blob/master/Dockerfile#L71 # Alternatives: # image: lscr.io/linuxserver/luanti:latest --- version: "3.5" services: luanti: image: ghcr.io/luanti-org/luanti:latest userns_mode: keep-id:uid=30000,gid=30000 networks: - luanti environment: - MINETEST_USER_PATH=/var/lib/minetest/.minetest - TZ=Etc/UTC command: "--gameid mineclonia --worldname world" volumes: # ghcr.io image needs ./minetest.conf - /home/luanti/game-onyx:/var/lib/minetest/.minetest:z ports: - 30070:30000/udp - 30070:30000/tcp restart: unless-stopped mapserver: image: ghcr.io/minetest-mapserver/mapserver:latest userns_mode: keep-id:uid=30000,gid=30000 restart: always networks: - luanti volumes: - /home/luanti/game-onyx/worlds/world:/minetest:z working_dir: "/minetest" ports: - 30071:8080/tcp ui: image: ghcr.io/minetest-go/mtui:latest restart: always environment: WORLD_DIR: "/world" LOGLEVEL: debug #COOKIE_DOMAIN: "www.example.com" #COOKIE_SECURE: "false" #COOKIE_PATH: "/games/luanti/namibia/ui" SERVER_NAME: "Onyx" #WASM_MINETEST_HOST: "minetest" API_KEY: "ExampleKeyGoesHere" #all features: "api,shell,luashell,minetest_config,docker,modmanagement,signup,chat,minetest_web" # enable features does not work. I had to be an admin from minetest.conf `name=<myname>` and then visit webpage and enable shell, luashell, chat ENABLE_FEATURES: "shell,luashell,chat" volumes: - "/home/luanti/game-onyx/worlds/world:/world:z" #- "postgres_socket:/var/run/postgresql" networks: - luanti ports: - 30072:8080/tcp x-podman: in_pod: false networks: luanti: name: luanti07 ... |
You saw this file in the previous post, and I needed to make very small changes so the converter tool could use it. They were: delete the network custom alias/name, also the x-podman attribute, which I found ironic.
files/2026/listings/luanti-rootless-podman-compose-for-podlet.diff (Source)
1 2 3 4 5 6 7 8 9 10 11 12 |
--- luanti-rootless-podman-compose.yml 2026-03-08 21:42:05.272649939 -0400 +++ luanti-rootless-podman-compose-for-podlet.yml 2026-03-08 21:53:50.416488977 -0400 @@ -64,9 +64,6 @@ - luanti ports: - 30072:8080/tcp -x-podman: - in_pod: false networks: luanti: - name: luanti07 ... |
Podman/systemd rootless
I used a converter tool named podlet, which I found in a copr. To install, I ran this sequence of commands.
sudo dnf copr enable cyqsimon/el-rust-pkgs sudo dnf repo-pkgs copr:copr.fedorainfracloud.org:cyqsimon:el-rust-pkgs list --available sudo dnf install podlet
In order for the user to use systemctl --user, I had to fix my dbus environment variable.
echo 'export XDG_RUNTIME_DIR=/run/user/$(id -u)' >> ~/.bashrc
And then log out and back in.
And now, time for the big conversion process, after the diff from above.
mkdir -p ~/.config/containers/systemd podlet compose docker-compose.yml > ~/.config/containers/systemd/onyx-luanti.container
I then split this file up, and tweaked them by hand to get what I needed. Here are the final files.
files/2026/listings/onyx-luanti.container (Source)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# luanti.container [Container] Environment=MINETEST_USER_PATH=/var/lib/minetest/.minetest TZ=Etc/UTC Exec=--gameid mineclonia --worldname world Image=ghcr.io/luanti-org/luanti:latest Network=onyx.network PublishPort=30070:30000/udp PublishPort=30070:30000/tcp UserNS=keep-id:uid=30000,gid=30000 Volume=/home/luanti/game-onyx:/var/lib/minetest/.minetest:z ContainerName=luanti [Service] Restart=always [Install] WantedBy=default.target |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# mapserver.container [Container] Image=ghcr.io/minetest-mapserver/mapserver:latest Network=onyx.network PublishPort=30071:8080/tcp UserNS=keep-id:uid=30000,gid=30000 Volume=/home/luanti/game-onyx/worlds/world:/minetest:z WorkingDir=/minetest ContainerName=mapserver [Service] Restart=always [Install] WantedBy=default.target |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# ui.container [Container] Environment=WORLD_DIR=/world LOGLEVEL=debug SERVER_NAME=Onyx API_KEY=ExampleKeyGoesHere ENABLE_FEATURES=shell,luashell,chat Image=ghcr.io/minetest-go/mtui:latest Network=onyx.network PublishPort=30072:8080/tcp Volume=/home/luanti/game-onyx/worlds/world:/world:z ContainerName=ui [Service] Restart=always [Install] WantedBy=default.target |
1 2 |
# onyx.network [Network] |
Steps not included by the conversion process
-
ContainerName: Without that, the container names would resemblesystemd-onyx-luanti, which just felt redundant and also slightly insulting to see that name show up everywhere. -
[Install] WantedBy=default.target. This is what tells the system to restart these containers at boot time. I had a little trouble with default.target or multi-user.target, but settled ondefault.target. - File
/home/luanti/.config/user-tmpfiles.d/onyx.confwhich contains these contents.
files/2026/listings/user-tmpfiles.d-onyx.conf (Source)
1 2 |
R! /tmp/storage-run-1009/containers R! /tmp/storage-run-1009/libpod/tmp |
This file contributes to starting the containers at boot time, by cleaning up the previous boot id-connected caches. It's a very systemd problem, solved in a systemd manner.
Final thoughts
I like using the native systemd components which feels more future-proof than Docker on Enterprise Linux derivatives. I didn't like that it was split across multiple files, in multiple directories. Docker-compose just feels much simpler and cleaner. One file, in one directory, which I store alongside the application files.
I also was able to use podman build . in the source directory of the mapserver project, when I was developing a code change. It worked very well, and I then tagged the package with 'mapserver1', so it ended up with name localhost:mapserver1, which I then used in the container defintion and a systemct --user daemon-reload and restart of the container.
References
In case I didn't list these elsewhere.
- Podman: Method to convert docker-compose files to systemd unit files - Server Fault
- containers/podlet: Generate Podman Quadlet files from a Podman command, compose file, or existing object
- podman-systemd.unit — Podman documentation
- Getting podman quadlets talking to each other · Major Hayden
- arch linux - Start a systemd user service at boot - Super User
- Unable to autostart podman container systemd service | Red Hat Customer Portal





