How to Configure Per-Application Volume on Fedora

Configure per-application volumes on Fedora by creating Quadlet unit files in ~/.config/containers/systemd/ to define and mount persistent storage.

You need persistent storage for a containerized app

You deployed a database container and realized the data vanishes when the container stops. Or you are tired of copy-pasting a massive podman run command with volume mounts every time you restart a service. You need a way to define persistent storage for an application and have systemd manage the whole lifecycle automatically. Fedora uses Podman and systemd integration to solve this.

The solution is a Quadlet. A Quadlet is a simple text file that describes a container or a volume. You drop the file in a config directory, reload systemd, and the service appears. You get the reliability of systemd with the simplicity of a configuration file. No shell scripts. No generated unit files to maintain manually.

How Quadlets bridge Podman and systemd

Podman ships with a systemd generator. When you run systemctl --user daemon-reload, the generator scans ~/.config/containers/systemd/ for files ending in .container, .volume, .network, or .kube. It parses these files and creates temporary systemd unit files in the runtime directory.

This separation is the safety net. You edit the Quadlet source files. The generator produces the units. If you break a generated unit, a reload fixes it. If you edit the generated unit directly, your changes vanish on the next reload. Always edit the source Quadlet.

The [Volume] section creates a named volume. Named volumes live in ~/.local/share/containers/storage/volumes/ for user services. This isolation keeps your home directory clean and separates container data from your personal files. The [Container] section references the volume. The Volume= key mounts the storage into the container filesystem. Systemd ensures the volume exists before the container starts because the generator creates a dependency chain automatically.

Edit the Quadlet, not the service. Reload to apply. The generator is the source of truth.

Create the volume and container definitions

Start by ensuring the config directory exists. Podman uses this path for user-level Quadlet definitions.

mkdir -p ~/.config/containers/systemd
# Create the user-level config directory if it doesn't exist.
# Podman looks here for Quadlet definitions owned by the current user.

Create a volume definition file. The filename determines the systemd unit name. Use a descriptive name that matches your application.

# ~/.config/containers/systemd/myapp.volume
[Volume]
Name=myapp-data
# Defines the name of the volume as Podman will see it.
# This name must match the reference in the container file.

Create the container definition file. The filename without the extension becomes the service name. The Volume= key links the storage to the container.

# ~/.config/containers/systemd/myapp.container
[Container]
Image=myimage:latest
Volume=myapp-data:/data
# Pulls the image if missing and mounts the volume.
# The left side is the volume name, right side is the mount path inside the container.

The Volume= key supports multiple formats. The example above references a named volume by name. You can also specify a host path directly, which creates a bind mount. If you use a host path, Podman mounts that directory into the container. The distinction matters for backups and SELinux contexts. Named volumes are managed by Podman. Bind mounts point to existing host directories.

Convention aside: User services live in ~/.config/containers/systemd/. System-wide services live in /etc/containers/systemd/. Use the user directory for personal apps. Use the system directory only when the service requires root privileges or access to restricted host resources.

Reload and start the service

Systemd does not watch the directory for changes. You must tell it to scan for new files. Run the reload command to trigger the generator.

systemctl --user daemon-reload
# Tells systemd to scan the config directory for new Quadlet files.
# This generates the underlying .service unit from the .container file.

Start the service. The --user flag is essential. It tells systemd to manage the service in your user session. Omitting the flag causes systemd to look for a root service, which fails for user Quadlets.

systemctl --user start myapp
# Starts the container service.
# Systemd ensures the volume is created before the container launches.

If you want the service to start automatically when you log in, enable it.

systemctl --user enable myapp
# Creates a symlink in the user runtime directory.
# The service starts automatically on login or when systemd-user starts.

Reload before you start. The generator runs on reload, not on start. Skipping the reload leaves systemd pointing at stale or missing units.

Verify the mount and service state

Check the service status first. The status command shows the active state and recent log lines in one view.

systemctl --user status myapp
# Shows the active state and recent log lines.
# Look for "active (running)" and no error messages in the journal output.

Verify the volume exists and is mounted correctly. Use Podman to inspect the volume list.

podman volume ls
# Lists all volumes managed by Podman for the current user.
# Confirm myapp-data appears in the output with the correct driver.

If the container is running, inspect the mount inside the container to confirm the path.

podman exec myapp mount | grep /data
# Checks the mount table inside the running container.
# The output should show myapp-data mounted at /data.

Convention aside: journalctl --user -xeu myapp reads better than journalctl --user -u myapp. The x flag adds explanatory text for error codes, and the e flag jumps to the end of the log. Most sysadmins type this muscle-memory style when debugging user services.

Run the status check. Read the journal. Guessing the error wastes time.

Common errors and how to fix them

Name mismatches are the most frequent failure. If the Name= field in the volume file does not match the left side of the Volume= key in the container file, the container fails to start. The error appears in the journal.

Error: volume myapp-data not found

Check the spelling and case. Volume names are case-sensitive. MyApp-Data is not myapp-data. Fix the name in the volume file, reload, and restart.

Missing reload causes unit not found errors. If you create the files but forget daemon-reload, systemd has no idea the service exists.

Failed to start myapp.service: Unit myapp.service not found.

Run systemctl --user daemon-reload and try again.

User flag confusion breaks everything. If you run systemctl start myapp without --user, systemd looks in the root configuration. Your user Quadlet lives in ~/.config/. The root generator does not see it.

Unit myapp.service could not be found.

Always use systemctl --user for services defined in your home directory.

SELinux denials can block access if you use bind mounts with incorrect labels. Quadlets handle labels for named volumes automatically. If you use a bind mount and see permission errors, check the SELinux context.

journalctl --user -t setroubleshoot
# Shows SELinux denial summaries with one-line explanations.
# Read the summary before disabling SELinux or changing labels blindly.

Check the journal first. The error message usually points to the exact cause.

Choose the right storage method

Use Quadlet files when you want systemd to manage the container lifecycle and need persistent storage without writing complex shell scripts.

Use podman run directly when you are testing an image quickly and don't need the service to survive a reboot.

Use a bind mount in the Quadlet when the application needs access to files on the host filesystem rather than a managed container volume.

Use the root-level /etc/containers/systemd/ directory when the service must run as root or access host resources restricted to the superuser.

Use podman volume create manually when you need to set specific driver options that the Quadlet syntax doesn't expose yet.

Trust the package manager. Manual file edits drift, snapshots stay. In this context, trust the generator. Manual unit edits drift, Quadlets stay.

Where to go next