Abstract: This article documents the exploration of achieving service persistence for Podman in rootless mode. Starting with a common failure case of manually creating a systemd
service, it analyzes the root cause and ultimately transitions to using Quadlet, the officially recommended tool from Podman. Through a deep dive into Quadlet’s mechanics, this article explains its declarative service management approach and presents the resulting best practices.
1. The Goal and the Failure of the Initial Attempt#
After migrating from Docker to Podman, one of the core goals was to achieve persistence and auto-start for containerized services.
Initially, I attempted to write a traditional systemd
user service for a podman-compose
project. However, this approach failed because systemd
’s lifecycle management for background processes (like podman-compose up -d
) was incompatible with expectations, leading the service into a “start-stop” infinite loop. This prompted me to turn to Podman’s official solution: Quadlet
.
2. Initial Quadlet Exploration and a New Confusion#
The core idea behind Quadlet is that a user only needs to write a simple .container
file, which is then automatically “translated” into a complex .service
file by a systemd
generator.
Following the compose.yml
file, I created a corresponding Quadlet file ~/.config/containers/systemd/sing-box.container
for the sing-box
container, ensuring it included an [Install]
section to define its auto-start behavior.
However, after running systemctl --user daemon-reload
, my attempt to use systemctl --user enable sing-box.service
was repeatedly met with the error: Failed to enable unit: Unit ... is transient or generated
. Interestingly, systemctl --user start sing-box.service
was able to start the container successfully.
This indicated that the Quadlet file itself was valid, but there was an interaction between it and systemd
’s enable
mechanism that was beyond conventional understanding.
3. Root Cause Analysis: The Inner Workings of the Quadlet Generator and [Install]
#
Through research and repeated experimentation, the root of the problem became clear: Quadlet’s autostart capability is not granted by the systemctl enable
command but is automatically handled when its generator reads the [Install]
section in the .container
file.
The systemd
workflow is as follows:
Write: The user creates a
.container
file with an[Install]
section in the specified directory¹. This is the “autostart directive” given tosystemd
.daemon-reload
triggers: Whensystemctl daemon-reload
² is executed, thequadlet-generator
is activated. It scans the directory, finds the.container
file, and performs two key actions:- Generates a transient service file: In a temporary runtime directory (like
/run/systemd/generator/
), it creates a.service
file that does not include the[Install]
section. This file is used for the actualstart
andstop
operations. - Automatically creates symbolic links: It reads the
[Install]
section from the “blueprint” and directly performs the core task ofenable
on its behalf—creating symbolic links from the appropriate.wants/
directory (based onWantedBy
) to the transient.service
file.
- Generates a transient service file: In a temporary runtime directory (like
The
enable
command issue: After this, when the user manually runssystemctl enable
,systemd
sees that the service has already been “installed” by a generator and points to a transient file. According to its design principles, a user should not directlyenable
a transient unit managed by a generator, so it returns theUnit is transient or generated
error. This error is actually a hint: “I’ve already taken care of this for you.”
¹ Quadlet File Paths: For user services (Rootless), the path is ~/.config/containers/systemd/
. For system services (Rootful), the path is /etc/containers/systemd/
.
² Command Note: For user services, the command is systemctl --user daemon-reload
. For system services, it is sudo systemctl daemon-reload
.
4. [Install]
Configuration and the Final Workflow#
4.1. Correctly Configuring the [Install]
Section#
The value of WantedBy=
depends on the mode you are running Podman in:
Rootless Mode: The service belongs to the user session and should start with the user session (or with the system if
linger
is enabled).1[Install] 2WantedBy=default.target
Rootful Mode: The service belongs to the system and should start when the system enters the multi-user state.
1[Install] 2WantedBy=multi-user.target
4.2. The Final, Simplified Workflow#
Write or Modify the Quadlet source file: Ensure the file syntax is correct and includes the appropriate
[Install]
section for your runtime mode.Apply Changes: After every modification to the
.container
file, execute the following command to notifysystemd
and trigger the generator. This is the only necessary management command.1# For user services 2systemctl --user daemon-reload 3# For system services 4sudo systemctl daemon-reload
Start and Verify: Once
daemon-reload
is complete, the service is already in an “installed” and “enabled” state.1# Start the service (use sudo and/or --user depending on the mode) 2systemctl --user start your-service.service 3 4# Verify that it is enabled 5systemctl --user is-enabled your-service.service 6# Expected output: generated
5. Summary#
This troubleshooting journey, which started from a seemingly simple need for service persistence, led to a deep dive into the complex interaction between systemd
and the Quadlet generator. It revealed the powerful automation behind declarative tools and clarified the changing role of traditional systemctl
commands when interacting with generators.
The final conclusion is that Quadlet achieves highly automated and declarative persistence management by reading the [Install]
configuration in the .container
file and automatically completing the service “installation” (the core work of enable
) during a daemon-reload
. Understanding the difference in [Install]
configuration between rootful
and rootless
modes and following the “edit source file -> daemon-reload
-> start
” workflow is key to mastering Podman container deployment.