Podman Quadlet revolutionizes how containers integrate with systemd. Instead of manually creating service files or using the deprecated podman generate systemd command, Quadlet provides a declarative way to manage containers as native systemd services. This guide shows you how to set up and use Podman Quadlet on Fedora 39.
Time Required: 25-35 minutes
Difficulty Level: Intermediate
Prerequisites:
- Fedora 39 with sudo access
- Basic understanding of containers and systemd
- Podman 4.4+ (included in Fedora 39)
What is Podman Quadlet?
Quadlet is a tool merged into Podman 4.4 that simplifies running containers under systemd using a systemd generator. Unlike docker-compose, Quadlet uses systemd’s native features to manage containers, networks, and volumes through dedicated unit files.
Key Benefits:
- Declarative container definitions
- Native systemd integration
- Automatic service generation
- Support for dependencies between containers
- Auto-update capabilities
- No daemon required
Step 1: Verify Podman Installation
Check your Podman version (must be 4.4 or higher):
podman --version
If you need to update:
sudo dnf update podman -y
Verify Quadlet is available:
ls /usr/libexec/podman/quadlet
Step 2: Understand Quadlet Directory Structure
Quadlet unit files are placed in specific directories:
System-wide (rootful) containers:
/etc/containers/systemd/
User-specific (rootless) containers:
~/.config/containers/systemd/
Create the user directory:
mkdir -p ~/.config/containers/systemd
For this guide, we’ll use rootless containers (recommended for security).
Step 3: Create Your First Quadlet Container
Let’s create a simple web server container using Nginx.
Create the container unit file:
nano ~/.config/containers/systemd/nginx.container
Add this configuration:
[Unit]
Description=Nginx Web Server
After=network-online.target
Wants=network-online.target
[Container]
Image=docker.io/nginx:latest
ContainerName=nginx-web
PublishPort=8080:80
Volume=%h/nginx-html:/usr/share/nginx/html:Z
[Install]
WantedBy=default.target
Configuration Breakdown:
[Unit]section: Standard systemd unit optionsImage: Container image to useContainerName: Name for the running containerPublishPort: Map host port to container port (host:container)Volume: Mount host directory (%h= home directory)[Install]section: Determines when the service starts
Step 4: Reload Systemd and Start the Container
Reload systemd to discover new Quadlet files:
systemctl --user daemon-reload
For systemd to discover the new service file, you need to run systemctl daemon-reload.
Start the container:
systemctl --user start nginx
Check status:
systemctl --user status nginx
Verify container is running:
podman ps
You should see a container named systemd-nginx-web.
Step 5: Enable Container on Boot
To automatically start the container at boot:
systemctl --user enable nginx
For rootless containers to start at boot without user login:
loginctl enable-linger $USER
Verify linger is enabled:
loginctl show-user $USER | grep Linger
Should show: Linger=yes
Step 6: Create a Multi-Container Stack
Let’s set up a WordPress site with MariaDB. We’ll need two containers with dependencies.
Create MariaDB Container
nano ~/.config/containers/systemd/wordpress-db.container
Add:
[Unit]
Description=WordPress Database
After=network-online.target
[Container]
Image=docker.io/mariadb:latest
ContainerName=wordpress-db
Environment=MYSQL_ROOT_PASSWORD=rootpass123
Environment=MYSQL_DATABASE=wordpress
Environment=MYSQL_USER=wpuser
Environment=MYSQL_PASSWORD=wppass123
Volume=%h/wordpress-db:/var/lib/mysql:Z
[Install]
WantedBy=default.target
Create WordPress Container
nano ~/.config/containers/systemd/wordpress.container
Add:
[Unit]
Description=WordPress Application
After=network-online.target
Requires=wordpress-db.service
After=wordpress-db.service
[Container]
Image=docker.io/wordpress:latest
ContainerName=wordpress-app
PublishPort=8081:80
Environment=WORDPRESS_DB_HOST=wordpress-db:3306
Environment=WORDPRESS_DB_USER=wpuser
Environment=WORDPRESS_DB_PASSWORD=wppass123
Environment=WORDPRESS_DB_NAME=wordpress
Network=wordpress.network
[Install]
WantedBy=default.target
Create Network for Communication
nano ~/.config/containers/systemd/wordpress.network
Add:
[Unit]
Description=WordPress Network
[Network]
This creates a Podman network named systemd-wordpress.
Step 7: Start the Multi-Container Stack
Reload systemd:
systemctl --user daemon-reload
Start the database first:
systemctl --user start wordpress-db
Start WordPress (dependencies will auto-start the database):
systemctl --user start wordpress
Check both services:
systemctl --user status wordpress-db
systemctl --user status wordpress
Access WordPress at: http://localhost:8081
Step 8: Configure Container Volumes
Quadlet supports multiple volume types.
Bind mount (host directory):
Volume=/host/path:/container/path:Z
Named volume:
Create volume unit file:
nano ~/.config/containers/systemd/app-data.volume
Add:
[Unit]
Description=Application Data Volume
[Volume]
Reference in container:
Volume=app-data.volume:/data:Z
SELinux context flags:
:Z– Private unshared label:z– Shared label:ro– Read-only
Step 9: Implement Auto-Updates
Podman provides auto-update functionality out of the box by setting AutoUpdate=registry.
Add to your container unit:
[Container]
Image=docker.io/nginx:latest
AutoUpdate=registry
Reload and enable the container:
systemctl --user daemon-reload
systemctl --user start nginx
Run auto-update manually:
podman auto-update
Set up automatic updates with a timer:
systemctl --user enable --now podman-auto-update.timer
Check timer status:
systemctl --user list-timers podman-auto-update.timer
Step 10: Advanced Quadlet Features
Environment Files
Store sensitive data in separate files:
nano ~/.config/containers/env/db.env
Add:
MYSQL_ROOT_PASSWORD=secret123
MYSQL_DATABASE=myapp
Reference in container:
[Container]
Image=docker.io/mariadb:latest
EnvironmentFile=%h/.config/containers/env/db.env
Health Checks
Add health monitoring:
[Container]
Image=docker.io/nginx:latest
HealthCmd=/usr/bin/curl -f http://localhost/ || exit 1
HealthInterval=30s
HealthRetries=3
HealthStartPeriod=60s
Resource Limits
Limit container resources:
[Container]
Image=docker.io/nginx:latest
Memory=512M
MemorySwap=1G
CPUQuota=50%
Labels
Add metadata:
[Container]
Image=docker.io/nginx:latest
Label=app=webserver
Label=version=1.0
Step 11: Manage Quadlet Containers
View all container services:
systemctl --user list-units --type=service | grep container
Stop a container:
systemctl --user stop nginx
Restart a container:
systemctl --user restart nginx
View logs:
journalctl --user -u nginx -f
Or with Podman:
podman logs -f systemd-nginx-web
Disable auto-start:
systemctl --user disable nginx
Step 12: Debug Quadlet Configuration
View generated systemd unit:
/usr/libexec/podman/quadlet -dryrun -user
This dumps the automatically generated systemd unit to your console.
Check for syntax errors:
systemctl --user daemon-reload
Any errors will be displayed during reload.
View container details:
podman inspect systemd-nginx-web
Troubleshooting
Container Not Starting
Check service status:
systemctl --user status nginx
View detailed logs:
journalctl --user -u nginx -n 50
Verify image exists:
podman images
Permission Denied on Volumes
Ensure correct SELinux context:
Volume=/path/to/data:/data:Z
Check directory permissions:
ls -la /path/to/data
Containers Can’t Communicate
Verify network configuration:
podman network ls
podman network inspect systemd-wordpress
Ensure both containers use the same network.
Auto-Update Not Working
Check if label is set:
podman inspect systemd-nginx-web | grep io.containers.autoupdate
Manually run update:
podman auto-update --dry-run
Real-World Examples
Example 1: Redis Cache
nano ~/.config/containers/systemd/redis.container
[Unit]
Description=Redis Cache Server
After=network-online.target
[Container]
Image=docker.io/redis:latest
ContainerName=redis
PublishPort=6379:6379
Volume=redis-data.volume:/data:Z
AutoUpdate=registry
[Install]
WantedBy=default.target
Example 2: PostgreSQL Database
nano ~/.config/containers/systemd/postgres.container
[Unit]
Description=PostgreSQL Database
After=network-online.target
[Container]
Image=docker.io/postgres:16
ContainerName=postgres
PublishPort=5432:5432
Environment=POSTGRES_PASSWORD=secret
Environment=POSTGRES_DB=appdb
Volume=%h/postgres-data:/var/lib/postgresql/data:Z
HealthCmd=pg_isready -U postgres
HealthInterval=10s
[Install]
WantedBy=default.target
Example 3: Nginx Reverse Proxy
nano ~/.config/containers/systemd/proxy.container
[Unit]
Description=Nginx Reverse Proxy
After=network-online.target
[Container]
Image=docker.io/nginx:latest
ContainerName=nginx-proxy
PublishPort=80:80
PublishPort=443:443
Volume=%h/nginx/conf:/etc/nginx/conf.d:Z,ro
Volume=%h/nginx/certs:/etc/nginx/certs:Z,ro
Network=app.network
[Install]
WantedBy=default.target
Quadlet vs Docker Compose
Similarities:
- Declarative configuration
- Multi-container management
- Network and volume support
Quadlet Advantages:
- Native systemd integration
- No daemon required
- Better security (rootless by default)
- Built-in auto-updates
- System-level service management
When to Use Quadlet:
- Production servers
- Single-node deployments
- System services
- Rootless containers
- Integration with existing systemd services
Best Practices
- Use rootless containers – More secure, runs as regular user
- Enable linger for boot start –
loginctl enable-linger $USER - Organize related containers – Use subdirectories in
~/.config/containers/systemd/ - Set resource limits – Prevent containers from consuming all resources
- Use named volumes – Better than bind mounts for persistence
- Implement health checks – Monitor container health automatically
- Enable auto-updates – Keep containers secure with latest images
- Use environment files – Keep secrets out of unit files
- Add proper dependencies – Use
Requires=andAfter=correctly - Test before enabling – Start manually first, enable after verification
Migration from podman generate systemd
If you’re migrating from the deprecated podman generate systemd:
Old method:
podman run -d --name nginx -p 8080:80 nginx
podman generate systemd --new --name nginx > nginx.service
New Quadlet method:
nano ~/.config/containers/systemd/nginx.container
[Container]
Image=docker.io/nginx:latest
PublishPort=8080:80
The tool podlet can help convert podman run commands to Quadlet units, though it’s not officially packaged in Fedora.
Useful Commands Reference
# Reload systemd units
systemctl --user daemon-reload
# List all container services
systemctl --user list-units --type=service | grep container
# Start/stop/restart container
systemctl --user start nginx
systemctl --user stop nginx
systemctl --user restart nginx
# Enable/disable auto-start
systemctl --user enable nginx
systemctl --user disable nginx
# View logs
journalctl --user -u nginx -f
podman logs -f systemd-nginx-web
# Check container status
systemctl --user status nginx
podman ps
# Debug Quadlet generation
/usr/libexec/podman/quadlet -dryrun -user
# Auto-update containers
podman auto-update
systemctl --user list-timers podman-auto-update.timer
# Enable linger (boot without login)
loginctl enable-linger $USER
Conclusion
You’ve successfully set up Podman Quadlet on Fedora 39 for systemd-managed containers. Quadlet provides a modern, declarative approach to container management that integrates seamlessly with systemd. Similar to how we automated ZFS snapshots with Sanoid and configured WireGuard split tunneling, Quadlet brings automation and native integration to container management.
Related Resources
Thank you for visiting our website, TechsBucket. If you liked the article, then share it with others.

