Managing Podman Containers with Systemd - Traditional Methods vs. Quadlet
Efficiently managing containers in production environments requires reliable automation and integration with the host system’s service management. For Linux systems using systemd, there are two primary approaches to running Podman containers as services: the traditional systemd service file approach and the newer, more declarative Quadlet method. This comprehensive guide explores both approaches, helping you choose the right strategy for your container management needs.
Understanding Container Management with Systemd
Systemd’s unit file system provides a powerful framework for service management, offering features like dependency handling, automatic restarts, and parallel execution. When combined with Podman’s daemonless container engine, it creates a robust solution for running containers that:
- Start automatically at boot time
- Restart on failure
- Manage dependencies between services
- Provide logging through journald
- Allow for fine-grained resource control
- Support for rootless containers
graph TD A[Systemd Service Management] --> B[Container Runtime] B --> C[Podman]
C --> D["Traditional Approach<br>(podman generate systemd)"] C --> E["Quadlet Approach<br>(declarative .container files)"]
D --> F[Manual Service Files] E --> G[Automatic Service Generation]
F --> H[systemctl operations] G --> H
H --> I[Container Lifecycle Management]
Traditional Approach: Podman with Custom Systemd Service Files
The traditional approach involves creating a systemd service file that defines how your container should run. This method gives you direct control over the service configuration.
Step 1: Creating Your Container
First, define the container you want to run:
podman run -d \ --name ubuntu-service \ --restart=always \ ubuntu:latest \ tail -f /dev/null
Step 2: Creating the Systemd Service File
Create a systemd service file in your user’s systemd directory for rootless containers:
mkdir -p ~/.config/systemd/user/
Then create a service file with detailed container specifications:
vi ~/.config/systemd/user/ubuntu-service.service
[Unit]Description=Ubuntu Service ContainerWants=network-online.targetAfter=network-online.targetRequiresMountsFor=%t/containers
[Service]Environment=PODMAN_SYSTEMD_UNIT=%nRestart=on-failureTimeoutStopSec=70ExecStartPre=/bin/rm -f %t/%n.ctr-idExecStart=/usr/bin/podman run \ --cidfile=%t/%n.ctr-id \ --cgroups=no-conmon \ --rm \ --sdnotify=conmon \ --replace \ --name ubuntu-service \ ubuntu:latest \ tail -f /dev/nullExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-idExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-idType=notifyNotifyAccess=all
[Install]WantedBy=default.target
Step 3: Enabling and Starting the Service
# Reload systemd to recognize the new servicesystemctl --user daemon-reload
# Enable the service to start on bootsystemctl --user enable ubuntu-service
# Start the servicesystemctl --user start ubuntu-service
# Enable lingering for your user (allows services to run without login)loginctl enable-linger $USER
Step 4: Managing Your Service
# Check service statussystemctl --user status ubuntu-service
# View logsjournalctl --user -u ubuntu-service
# Stop the servicesystemctl --user stop ubuntu-service
# Restart the servicesystemctl --user restart ubuntu-service
Generating Service Files Automatically
Podman can generate systemd service files for existing containers:
# Create a container firstpodman run -d --name webapp nginx:latest
# Generate a systemd service filepodman generate systemd --name webapp > webapp.service
Modern Approach: Podman with Quadlet
Quadlet is a newer approach that allows you to define container services declaratively through simple configuration files. It’s now built into Podman (since version 4.0) and serves as a more elegant solution for container service management.
Step 1: Creating the Directory Structure
For user services (rootless):
mkdir -p ~/.config/containers/systemd/
For system services (root):
sudo mkdir -p /etc/containers/systemd/
Step 2: Creating the Quadlet Container File
Create a file with the .container
extension:
vi ~/.config/containers/systemd/ubuntu-service.container
[Unit]Description=Ubuntu Service ContainerAfter=network-online.target
[Container]Image=docker.io/library/ubuntu:latest# Keep container runningExec=tail -f /dev/null
# Resource limits (optional)Memory=1GCPUQuota=100%
# Environment variables (optional)Environment=MY_VAR=value
# Volume mounts (optional)Volume=/path/on/host:/path/in/container:Z
[Service]Restart=alwaysTimeoutStartSec=900
[Install]WantedBy=default.target
Step 3: Enabling and Starting the Service
# Reload systemd to detect the new container configurationsystemctl --user daemon-reload
# Enable and start the servicesystemctl --user enable --now container-ubuntu-service
Step 4: Managing Your Service
# Check service statussystemctl --user status container-ubuntu-service
# View logsjournalctl --user -u container-ubuntu-service
# Restart the servicesystemctl --user restart container-ubuntu-service
Comparison: Traditional vs. Quadlet Approach
Let’s compare these two approaches across various dimensions:
Feature | Traditional Approach | Quadlet Approach |
---|---|---|
Configuration Style | Imperative (full service file) | Declarative (focused on container parameters) |
Maintenance | Manual updates required when container changes | Automatic service generation |
Complexity | More complex service files | Simplified configuration |
Control | Fine-grained control over all aspects | Container-focused with reasonable defaults |
Integration | Manual integration with systemd | Built-in integration |
Updates | May need regeneration after Podman updates | Handles Podman updates transparently |
Storage | Lives in systemd directories | Lives in container configuration directories |
Features | All systemd features available | Most common container features pre-configured |
Learning Curve | Steeper (requires systemd knowledge) | Gentler (focuses on container parameters) |
Advanced Quadlet Features
Quadlet supports more than just containers. It has several specialized file types for different container resources:
Volume Management
Create a persistent named volume with a .volume
file:
[Volume]Driver=localDevice=tmpfsUID=1000GID=1000
Network Configuration
Define a custom network with a .network
file:
[Network]Driver=bridgeSubnet=192.168.100.0/24Gateway=192.168.100.1IPRange=192.168.100.0/24
Pod Management
Create a pod with multiple containers using a .pod
file:
[Pod]Name=web-app
[Container]Name=webImage=nginx:latestVolume=/var/www/html:/usr/share/nginx/html:ro,Z
[Container]Name=redisImage=redis:alpine
Kubernetes-Style Deployments
Deploy applications defined in Kubernetes YAML with a .kube
file:
[Unit]Description=Kubernetes-style Deployment
[Kube]Yaml=./deployment.yaml
Practical Examples
Example 1: Web Server with Persistent Storage
Here’s how to create a NGINX web server with persistent storage using Quadlet:
[Unit]Description=NGINX Web ServerAfter=network-online.target
[Container]Image=docker.io/library/nginx:latestPublishPort=8080:80Volume=web-data:/usr/share/nginx/html:Z
[Service]Restart=always
[Install]WantedBy=default.target
[Volume]Driver=local
Example 2: Database Server with Resource Limits
[Unit]Description=PostgreSQL DatabaseAfter=network-online.target
[Container]Image=docker.io/library/postgres:14Environment=POSTGRES_PASSWORD=secretEnvironment=POSTGRES_USER=myappEnvironment=POSTGRES_DB=myapp_dbVolume=postgres-data:/var/lib/postgresql/data:ZPublishPort=5432:5432Memory=2GCPUQuota=200%
[Service]Restart=alwaysTimeoutStartSec=120
[Install]WantedBy=default.target
[Volume]Driver=local
Example 3: Multi-Container Application with a Network
[Network]Driver=bridge
[Unit]Description=Web ApplicationAfter=network-online.targetAfter=container-redis-cache.serviceRequires=container-redis-cache.service
[Container]Image=docker.io/myapp:latestPublishPort=8080:80Network=app-networkEnvironment=REDIS_HOST=redis-cacheEnvironment=REDIS_PORT=6379
[Service]Restart=always
[Install]WantedBy=default.target
[Unit]Description=Redis CacheAfter=network-online.target
[Container]Image=docker.io/library/redis:alpineNetwork=app-networkAddHost=redis-cache:127.0.0.1
[Service]Restart=always
[Install]WantedBy=default.target
Best Practices
Security Considerations
-
Use rootless containers when possible:
[Container]User=1000:1000 -
Apply SELinux labels to volumes:
[Container]Volume=/path/on/host:/path/in/container:Z -
Limit container capabilities:
[Container]DropCapability=ALLAddCapability=NET_BIND_SERVICE
Resource Management
-
Set memory limits to prevent OOM issues:
[Container]Memory=1GMemorySwap=2G -
Configure CPU constraints:
[Container]CPUShares=1024CPUQuota=50%
Service Dependencies
-
Ensure network is available:
[Unit]After=network-online.targetWants=network-online.target -
Create service dependencies:
[Unit]After=container-database.serviceRequires=container-database.service
Observability
- Configure healthchecks:
[Container]HealthCmd=/bin/sh -c "wget -q --spider http://localhost:8080 || exit 1"HealthInterval=30sHealthTimeout=10sHealthStartPeriod=5sHealthRetries=3
Migrating from Traditional to Quadlet
If you’re currently using traditional systemd service files for Podman containers, here’s how to migrate to Quadlet:
Step 1: Identify Current Container Parameters
Extract the container run parameters from your existing service file:
# From a traditional service file like this:ExecStart=/usr/bin/podman run \ --name webapp \ -v /data:/app/data:Z \ -p 8080:80 \ -e DATABASE_URL=postgres://user:pass@db/mydb \ nginx:latest
Step 2: Create an Equivalent Quadlet File
[Unit]Description=Web Application
[Container]Image=nginx:latestVolume=/data:/app/data:ZPublishPort=8080:80Environment=DATABASE_URL=postgres://user:pass@db/mydb
[Service]Restart=always
[Install]WantedBy=default.target
Step 3: Test and Switch Over
# Stop and disable the old servicesystemctl --user stop webappsystemctl --user disable webapp
# Enable and start the new Quadlet-based servicesystemctl --user daemon-reloadsystemctl --user enable --now container-webapp
Troubleshooting
Service Won’t Start
If your Quadlet-managed container won’t start, check these common issues:
-
Permission problems:
Terminal window # Check journalctl for permission errorsjournalctl --user -u container-webapp -n 50 -
Image not found:
Terminal window # Pull the image manually to verify accesspodman pull nginx:latest -
Port conflicts:
Terminal window # Check if ports are already in usess -tulpn | grep 8080
Container Crashes
If your container starts but then crashes:
-
Check container logs:
Terminal window # Look at the container logs directlypodman logs $(podman ps -a --filter name=webapp --format "{{.ID}}") -
View systemd journal:
Terminal window # Examine journal logsjournalctl --user -u container-webapp -f -
Increase debug output:
Terminal window # Add debug output to your container file[Service]Environment=PODMAN_LOG_LEVEL=debug
Resource Constraints
If your container is hitting resource limits:
-
Check container resource usage:
Terminal window podman stats -
Adjust limits in your Quadlet file:
[Container]Memory=2G # Increase from previous valueCPUQuota=200% # Allow use of 2 full CPUs
Conclusion
Both the traditional method and Quadlet approach offer effective ways to integrate Podman containers with systemd, but they serve different needs:
- Traditional Approach: Offers maximum flexibility and control but requires more maintenance and deeper systemd knowledge
- Quadlet Approach: Provides a simpler, more declarative method that’s easier to maintain and more focused on container-specific parameters
For most users, especially those building new container deployments, Quadlet represents the future of Podman-systemd integration with its simplified syntax, automatic service generation, and built-in support for complex container resources like pods and networks. The traditional approach remains valuable for users who need very specific systemd configurations or who are maintaining existing deployments.
Whether you choose the traditional method or Quadlet, integrating your containers with systemd brings considerable benefits in terms of reliability, lifecycle management, and integration with the host system—making your containerized applications easier to manage and more resilient in production environments.