Skip to main content
Background Image
  1. Posts/

Exploring Persistence for Podman Rootless Containers: A Deep Dive into Quadlets

··880 words·5 mins· loading · loading ·
yuzjing
Author
yuzjing
Table of Contents

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:

  1. Write: The user creates a .container file with an [Install] section in the specified directory¹. This is the “autostart directive” given to systemd.

  2. daemon-reload triggers: When systemctl daemon-reload² is executed, the quadlet-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 actual start and stop operations.
    • Automatically creates symbolic links: It reads the [Install] section from the “blueprint” and directly performs the core task of enable on its behalf—creating symbolic links from the appropriate .wants/ directory (based on WantedBy) to the transient .service file.
  3. The enable command issue: After this, when the user manually runs systemctl 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 directly enable a transient unit managed by a generator, so it returns the Unit 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
#

  1. Write or Modify the Quadlet source file: Ensure the file syntax is correct and includes the appropriate [Install] section for your runtime mode.

  2. Apply Changes: After every modification to the .container file, execute the following command to notify systemd 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
    
  3. 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.