You need containers that survive reboots without a daemon
You deployed a web service on Fedora Server using podman run. It worked perfectly in the terminal. You closed the SSH session, and the container stopped. You added a cron job to restart it, but now you have a race condition where the network isn't ready when the container tries to bind a port. You need a way to manage containers like system services, with automatic restarts, proper logging, and dependency ordering, without running everything as root.
Fedora Server ships with Podman, not Docker. Podman is daemonless. It runs containers as child processes. This is great for security but means there is no background daemon to keep containers alive across reboots or user sessions. Systemd is the init system. It manages services. Quadlets bridge the gap. A Quadlet is a simple configuration file that Podman translates into a full systemd unit file. You write the container config, Quadlet generates the systemd unit, and systemd handles the lifecycle. This gives you the reliability of systemd with the simplicity of container configuration.
How Quadlets work
Podman includes a generator that reads .container files and produces .service files for systemd. The generator runs when you invoke podman quadlet install. It parses the Quadlet syntax, applies defaults, and writes a unit file that systemd understands. You never edit the generated unit file. The generated file is ephemeral. Running podman quadlet install again overwrites it. Always edit the Quadlet source.
The workflow is straightforward. You create a Quadlet file in a designated directory. You run the install command to generate the unit. You reload systemd. You start and enable the service. Systemd now owns the container. It restarts the container on failure. It logs the output to journald. It handles shutdown gracefully.
Deploy a rootless container with a Quadlet
Rootless containers run as your user account. They cannot bind to ports below 1024. They do not require root privileges. This is the recommended approach for production workloads on Fedora Server. It limits the blast radius if a container is compromised.
Create the configuration directory for user-level Quadlets. User-level units run as the current user. They integrate with the user's systemd instance.
# Create the config directory for user-level Quadlets
# User-level units run as the current user, which is safer for production workloads
mkdir -p ~/.config/containers/systemd
# Write the Quadlet configuration
# The .container extension tells Podman this defines a container
cat > ~/.config/containers/systemd/myapp.container <<EOF
[Container]
# Pull the image from the registry
# Pin the tag to a specific version for reproducible builds
Image=quay.io/podman/hello:latest
# Map host port 8080 to container port 80
# Format is host_port:container_port
# Host port must be above 1024 for rootless containers
PublishPort=8080:80
# Set a restart policy so systemd can manage the lifecycle
# Without this, systemd treats the container as a one-shot task
RestartPolicy=always
# Add a health check to detect deadlocks
# Systemd will restart the container if this command fails
HealthcheckCmd=curl -f http://localhost:80/ || exit 1
HealthcheckInterval=30s
EOF
Install the Quadlet to generate the systemd unit. The install command reads the file and creates container-myapp.service in the systemd user directory.
# Generate the systemd unit file from the Quadlet
# This creates container-myapp.service in the systemd user directory
# The generator applies defaults and validates the configuration
podman quadlet install myapp.container
# Reload the systemd user manager to pick up the new unit
# daemon-reload is required whenever you add or modify unit files
systemctl --user daemon-reload
# Start the container service
# The service name follows the pattern container-<name>.service
systemctl --user start container-myapp.service
# Enable the service to start automatically on user login
# For headless servers, see the lingering section below
systemctl --user enable container-myapp.service
Reload systemd after every Quadlet change. The generator runs once, not continuously.
Handle headless servers with lingering
Fedora Server often runs headless. User services might not start on boot unless a user logs in. Systemd user instances are tied to user sessions. If no one logs in, the session never starts, and the services never run. You need to enable lingering. Lingering keeps the user session alive after logout so systemd user services can run on boot.
# Enable lingering for the current user
# This allows user services to start on boot without a login
loginctl enable-linger $(whoami)
# Verify lingering is enabled
# Look for "Linger: yes" in the output
loginctl show-user $(whoami) | grep Linger
Enable lingering for user services on headless servers. Boot scripts won't wait for a login.
Deploy a system-wide container
System-wide containers run as root. They can bind to privileged ports. They start on boot without lingering. Use system-wide deployment when the container requires root capabilities or when you want the service managed by the system manager directly.
System-wide Quadlets go in /etc/containers/systemd/. The directory might not exist by default. Create it and place the Quadlet there.
# Create the system-wide Quadlet directory
# System-wide units run as root and start on boot without lingering
sudo mkdir -p /etc/containers/systemd
# Write the system-wide Quadlet configuration
# This example binds to port 80, which requires root privileges
sudo cat > /etc/containers/systemd/webserver.container <<EOF
[Container]
Image=quay.io/podman/hello:latest
PublishPort=80:80
RestartPolicy=always
EOF
# Install the system-wide unit
# This generates the unit in /usr/lib/systemd/system/
sudo podman quadlet install /etc/containers/systemd/webserver.container
# Reload the system systemd manager
sudo systemctl daemon-reload
# Start and enable the system service
sudo systemctl start container-webserver.service
sudo systemctl enable container-webserver.service
Firewalld blocks incoming traffic by default. Open the port and reload the firewall rules.
# Add a permanent rule for the service port
# The --permanent flag saves the rule to the configuration file
sudo firewall-cmd --add-port=80/tcp --permanent
# Reload the firewall to apply the new rule
# Runtime and persistent configs diverge if you skip this step
sudo firewall-cmd --reload
Run firewall-cmd --reload after every rule change. Otherwise the runtime config and the persistent config diverge.
Verify the deployment
Check the service status and recent logs. Systemd integrates with Podman's journald driver. Container output goes to the journal. You can query it with journalctl.
# Check the service status and recent logs
# The -u flag filters for the specific unit
systemctl --user status container-myapp.service
# View container logs managed by systemd
# journalctl integrates with Podman's journald driver by default
# The -n flag limits output to the last 20 lines
journalctl --user -u container-myapp.service -n 20
# Check if the container is listening on the expected port
# ss shows socket statistics and confirms the port binding
ss -tlnp | grep 8080
Check journalctl -u container-myapp.service. The logs tell you why the container died.
Common pitfalls and errors
You might see Error: port is already allocated. Another service is using the host port. Change the host port in the Quadlet. You might see SELinux denials in journalctl -t setroubleshoot. Quadlets handle most SELinux contexts automatically. If you bind-mount a custom volume, check the context. Use the :z suffix on volumes to share the content between containers. Don't disable SELinux. Fix the context.
You might see Failed to start container-myapp.service. The image pull failed. Check your network configuration. You might see Permission denied when starting a rootless container. The user does not have permission to access the storage driver. Run podman system reset to reinitialize the storage, or check the containers.conf settings.
You might use the latest tag in production. The tag can change unexpectedly. Pin the image to a specific digest or version tag. Use skopeo inspect to find the digest.
# Inspect the image to get the digest
# The digest is a unique identifier for the image content
skopeo inspect docker://quay.io/podman/hello:latest | grep Digest
Pin images to digests in production. Tags are mutable. Digests are immutable.
When to use Quadlets vs alternatives
Use Quadlets when you want systemd to manage container lifecycle, restarts, and logging. Use podman run directly when you are debugging interactively or testing a one-off command. Use Kubernetes or a container orchestrator when you need multi-node scaling, service discovery, or complex rolling updates. Use Docker Compose via podman-compose when you have a multi-container application with complex networking and shared volumes that you want to manage as a single stack.